Picasso源碼解析 一

前面寫的5篇博客讓我們對Picasso的用法有了很詳細的瞭解,Android的框架層出不窮,每年都會開源出大量的優秀的框架,如果我們只是站在一個使用者的角度去不斷的接觸框架,而不去學習框架底層的實現原理,那樣頂多也只能一直當新手,而不是老司機了,下面我們就從最基本的入手,從源碼的角度去剖析Picasso的工作原理(該分析都是基於Picasso 2.5.2版本).Let’s Go.

官方給出的一個最簡單的用法,一個鏈式調用就能實現一張圖片的請求及展示:

 Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);

只需這一行代碼,Picasso就能從我們提供的URL地址中去請求圖片,然後將圖片展示到targetImageView中,我們就從最簡單的開始,一層層的還原Picasso最底層的本質.

Picasso.with(context)

public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }

這段代碼就是爲了初始化Picasso,獲取一個Picasso的實例,該類代碼具有很大的共性,很多框架在獲取實例時基本都是採用了 單例模式+建造者模式

  • 單例模式特點:
    • 採用後加載機制,保證實例只有在被使用的時候才被創建
    • 採用雙重檢驗鎖模式,保證了在多個線程同時請求的過程中,只創建唯一的單例對象
    • 同步對象爲類本身,保證了該對象的同步範圍在整個java虛擬機中,也就是全局的
  • 利用建造者模式創建一個默認的Picasso對象,一般來說使用建造者模式,是因爲構建該對象本身時需要創建其他的類對象,也是保證了多個類的初始化,看代碼new Builder(context).build()
 /** Create the {@link Picasso} instance. */
    public Picasso build() {
      Context context = this.context;

      if (downloader == null) {
        downloader = Utils.createDefaultDownloader(context);
      }
      if (cache == null) {
        cache = new LruCache(context);
      }
      if (service == null) {
        service = new PicassoExecutorService();
      }
      if (transformer == null) {
        transformer = RequestTransformer.IDENTITY;
      }

      Stats stats = new Stats(cache);

      picasso.Dispatcher dispatcher = new picasso.Dispatcher(context, service, HANDLER, downloader, cache, stats);

      return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
          defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
    }
  }

在該方法中主要對初始化了以下參數:

  • Downloader
    • 下載工具類,該Utils.createDefaultDownloader(context);如果OKHttp可用的話,會返回一個OkHttpDownloader,否則的話會返回一個UrlConnectionDownloader(內部使用HttpURLConnection實現)
  • Cache

    • 該類的默認實現爲LruCache,也就是利用最近最少使用算法進行緩存,內部是一個LinkedHashMap,這裏要注意的是,一般我們在初始化LinkedHashMap時,會限制initialCapacity容量值,但此處是在初始化LruCache時通過調用Utils.calculateMemoryCacheSize(context)來計算出最大的堆內存值然後劃分出7分之一,也就是15%作爲容量大小,所以在每次調用set()方法進行緩存時,都會調用trimToSize()不停的計算當前可使用的空間大小,超出範圍就刪除之前保存的數據;

      • Utils.calculateMemoryCacheSize(context)的代碼塊
      static int calculateMemoryCacheSize(Context context) {
      ActivityManager am = getService(context, ACTIVITY_SERVICE);
      //計算出最大的堆內存值,也就是可用大小值
      boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
      int memoryClass = am.getMemoryClass();
      if (largeHeap && SDK_INT >= HONEYCOMB) {
      memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
      }
      //分配各15%的大小
      // Target ~15% of the available heap.
      return 1024 * 1024 * memoryClass / 7;
      }
  • ExecutorService

    • 默認實現類PicassoExecutorService,也就是ThreadPoolExecutor子類而已,默認線程數是3,但是PicassoExecutorService會在不同的網絡狀態下調整線程數,wifi下爲4個線程,4G狀態爲3個線程,3G狀態下爲2個線程,2G狀態下爲1個線程.
  • RequestTransformer
  • Stats
    • 主要記錄一些信息,比如下載的總大小值,緩存大小值,在之前的博客中有用到過,詳見Picasso最全使用教程 四,用到了StatsSnapshot
  • Dispatcher

    • 負責分發和處理 Action,包括提交、暫停、繼續、取消、網絡狀態變化、重試等等。這個類是我們着重要分析的類,我們先來看一下默認的操作都有哪些

      • 在構造方法中,將之前的ExecutorService,Downloader,Cache,Stats進行了賦值

      • 在構造方法第一行就創建了DispatcherThread類,啓動了一個線程

      • 在構造方法中創建一個DispatcherHandler,並將DispatcherThread.getLooper()作爲參數傳入該handler的構造函數中,意味着該Handler處理的數據都是從DispatcherThread發出的

      • 初始化並添加了一個對網絡監聽的廣播

上面就是關於.with(context)所做的分析,我們繼續向下分析.

.load(String url)方法源碼解析

我們現在看.load(String url)方法都做了什麼操作,其實.load(url)最後調用的依然是.load(Uri uri)

public RequestCreator load(String path) {
   //對path進行判空,省略部分代碼
    ....

    return load(Uri.parse(path));
  }

//其實調用的是這個方法
 public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
  }

所以加載圖片的本質就是我們通過Picasso調用了load(url)返回了一個new出來的RequestCreator示例,同時將Picasso ,uri地址, 還有resourceId也就是0,作爲構造參數傳入,下面我們就看看該構造函數都做了哪些操作;

 RequestCreator(Picasso picasso, Uri uri, int resourceId) {
    if (picasso.shutdown) {
      throw new IllegalStateException(
          "Picasso instance already shut down. Cannot submit new requests.");
    }
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
  }
  1. 先判斷Picasso是否已經啓動,如果沒有啓動,就直接拋出異常
  2. 對picasso進行賦值
  3. 這裏又用到了建造者模式使用Request.Builder()將創建的Builder賦值給data,我們來看該部分的代碼

    • 代碼如下:

        this.uri = uri;
        this.resourceId = resourceId;
        this.config = bitmapConfig;
      }
      • 我們看到,這裏只是做了賦值操作,並沒有做其他事,Builder還有其他幾個構造方法,也都只進行了賦值操作,並未做其他處理,等我們分析完之後,就會明白此處代碼的用意. 貼出來兩個,另一個Builder (Request request)就不在貼出,部分代碼如下

        public Builder(Uri uri) { setUri(uri); }

        public Builder(int resourceId) { setResourceId(resourceId); }

最重要的方法.into(target)方法解析

.into(target)方法是ReqeustCreator中的方法,所以我們去一窺究竟,其實.into(target)還是在內部調用的.into(target,callback),看代碼

 public void into(ImageView target) {
    into(target, null);
  }

下面我們就着重看一下.into(target,callback)都做了哪些操作,代碼較多,我們逐一進行分析

public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    //1 檢測是否在主線程中執行,如果不是就拋異常
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
    //2 該判斷是只有在uri爲null,並且resourceId爲0的情況下才會執行相應邏輯,取消請求顯示默認圖片,然後結束
    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }
    //3 是否延時加載,默認爲false,只有當調用了 .fit() 纔會執行該邏輯
    if (deferred) {
      //調用 .fit() 之前,不能調用 .resize(w,h) ,否則拋出異常
      if (data.hasSize()) {
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      //target是否已經完全繪製出來,如果沒有,則執行相關操作
      if (width == 0 || height == 0) {
        //是否設置了默認圖片,在進行延時請求前先展示默認圖片
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        // 加入延時隊列,同時創建一個DeferredRequestCreator去測量target的寬高,結束任務
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      //target已經繪製完成,調用resize方法,並繼續向下執行
      data.resize(width, height);
    }
    //4 創建一個request,並根據request生產一個requestKey
    Request request = createRequest(started);
    String requestKey = createKey(request);
    //5 判斷可否從內存中讀取資源,默認爲可以
    if (shouldReadFromMemoryCache(memoryPolicy)) {
      //從內存中獲取,如果能獲取到就取消請求操作,並執行callback.onSuccess()的回調,結束任務
      Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
      if (bitmap != null) {
        picasso.cancelRequest(target);
        setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
        if (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }
    //6 判斷是否設置了默認圖片,在進行請求前先展示默認圖片
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
    //7 這時是的確需要聯網請求了,生成對應的 ImageViewAction,並將 action 添加到 Picasso的請求隊列中
    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }

部分功能說明已在代碼中標出,但還是用一張示意圖來進行解析比較簡單易懂

圖畫的比較醜,但是看着還算比較清晰,我們在按照代碼,圖示,再用文字描述一遍,讓更有深的認識;

  1. 檢測是否在主線程中執行,如果不是就拋異常,可能是要更新UI顯示,所以要在主線程中執行代碼邏輯
  2. 該判定方法data.hasImage()容易讓人誤解爲是否已經有圖片獲取好了,但其實是爲了判斷在Builder創建的時候,是否設置了uri或者resourceId,如果沒有設置uri並且resourceId爲0,說明該請求是一個錯誤請求,就可以加載默認圖片並取消請求操作;
  3. 是否延時加載,默認爲false,只有當調用了 .fit() 纔會執行該邏輯,如果有同學不太理解.fit()的作用,可翻看我之前寫的博客,其實就是爲了重新計算target的寬高值,而且不能在調用了.resize()之後再次調用.fit()
  4. 創建一個request,並根據request生產一個requestKey
  5. 判斷可否從內存中讀取資源,默認爲true,如果有同學不知道怎麼設置跳過內存讀取這一步驟,可翻看我之前寫的博客
  6. 判斷是否設置了默認圖片,在進行請求前先展示默認圖片
  7. 這時是的確需要聯網請求了,生成對應的 ImageViewAction,並將 action 添加到 Picasso的請求隊列中

OK,到現在我們已經大致瞭解了Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);都做了哪些操作,至於第7步之後又做了哪些操作,我們將繼續分析,還原她的本質,願大家都有美好的一天…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章