Android 圖片加載框架Picasso基本使用和源碼徹底解析(鉅細無比)

寫在以前java

本來打算是每週更新一篇博文,同時記錄一週的生活狀態,可是稍微工做忙一點就顧不上寫博客了。悲催
仍是說下最近的情況,最近兩週一直在接公司申請的計費點, 沃商店,銀貝殼,微信等等,而後就是不停的被人催促催促,真是一個頭兩個大。在這期間項目組還搞了個App會員登陸系統,接受第三方登陸,而後應用到現有的App當中,而後又是一陣狂Coding。
說道第三方登陸,那必然會包含第三方用戶信息,好比頭像,暱稱等,本來項目中就使用了Picasso框架,以前也沒有太深刻的研究它,藉此機會就認真的研究了一下它的源碼實現,由此產生了這篇博文。python

正文android

說到Picasso,相信Android開發人員毫不陌生,它是Square公司開發的一款圖片加載神器。使用過它的coder絕對是愛不釋手:對它自己而言,輕量安全,有效加載圖片並防止OOM;對咱們開發者來講,簡單方便,一行代碼搞定圖片加載。所以它備受Android開發人員的鐘愛。緩存

關於它的更多好處優勢,相信不用我介紹你也很是的清楚,在這裏咱們廢話很少說,直接的進入主題來說解它的使用和從源碼的角度解析它的加載原理。安全

Picasso基本使用

一 工程引入微信

在咱們的項目build.gradle中添加對Picasso框架的依賴:網絡

compile 'com.squareup.picasso:picasso:2.5.2'

在這裏我使用的是2.5.2的最新版本。app

二 添加權限框架

由於加載圖片須要訪問網絡,由此咱們在Manifest中添加訪問網絡的權限:ide

<uses-permission android:name="android.permission.INTERNET"/>

三 建立加載圖片佈局文件

在咱們的MainActivity的佈局文件中添加一個Button和一個ImageView:

四 MainActivity中點擊加載圖片

在這裏咱們點擊Button讓它加載一張百度Logo的圖片並顯示在ImageView控件中。

五 加載圖片

點擊Button加載圖片,來看結果:

ok,已加載出來了,可是咱們到底作了什麼呢?

基本加載

其實咱們所作的事情很是的簡單,那就是在Button的點擊事件中添加一行代碼:

Picasso.with(MainActivity.this).load(url).into(headerImage);

沒錯就是一行代碼搞定圖片加載。至於它是怎麼實現的,咱們會在後面的源碼分析中詳細的解析它,如今來看看它的其餘使用。

佔位圖

不光加載圖片是很是簡單的事情,並且還能夠在加載過程當中使用佔位圖,能夠很友好的告訴用戶它正在加載,而不是加載中顯示空白的界面。

Picasso.with(MainActivity.this)
.load(url)
.placeholder(R.mipmap.ic_launcher)
.into(headerImage);

使用placeholder爲還沒有加載到圖片的ImageView設置佔位圖,這是一種友好的顯示方式。

異常圖

不光能夠設置佔位圖,並且還能夠在圖片加載不出來,或是找不到要加載的圖片後,能夠爲ImageView設置異常圖:

Picasso.with(MainActivity.this)
       .load(url)
       .placeholder(R.mipmap.ic_launcher)
       .error(R.drawable.error)
       .into(headerImage);

使用error能夠爲加載異常的ImageView設置異常圖,修改咱們的圖片URL,使它沒法獲取到正確的圖片地址,而後來看結果:

轉換器

不只如此,咱們還能夠對加載到的圖片進行從新調整,好比改變圖片的大小,顯示形狀等,可使用transform方法,例如:

首先咱們先自定義一個Transformation:

private class customTransformer implements Transformation{

        @Override
        public Bitmap transform(Bitmap source) {
            //在這裏能夠對Bitmap進行操做,好比改變大小,形狀等

            return source;
        }
        @Override
        public String key() {
            return null;
        }
    }

而後在transform方法中進行處理:

Picasso.with(MainActivity.this)
        .load(url)
        .placeholder(R.mipmap.ic_launcher)
        .transform(new customTransformer())
        .error(R.drawable.error)
        .into(headerImage);

這樣的話就能夠對圖片進行必定意義上的控制和選擇,使它更加符合咱們的需求。

固然Picasso中還有不少其餘的應用,好比能夠設置加載大小,使用resizeDimen方法,填充方式使用centerCrop,fit等等,你們若是有須要的話能夠本身嘗試使用。這裏就不要一一的介紹了。

Picasso 源碼解析

ok,上面介紹了Picasso的部分基礎使用,很是的簡單,一行代碼搞定你的所需,那麼下面咱們從源碼的角度來解析下它究竟是怎麼實現咱們的圖片加載和使用的。

如下面最簡潔的加載爲示例:

Picasso.with(MainActivity.this).load(url).into(headerImage);

with

首先Picasso會調用靜態with方法,那麼咱們來看看with方法是怎麼實現的:

由上面的源碼咱們能夠看到,在with方法中主要作了一件事,那就是返回一個Picasso實例,固然這個實例也不是那麼簡單的建立的,爲了防止Picasso的屢次建立,這裏使用了雙重加鎖的單例模式來建立的,主要目的是爲了保證線程的安全性。可是它又不是直接的使用單例模式建立的,在建立實例的過程當中使用了Builder模式,它可使Picasso在建立時初始化不少對象,以便後期使用,那麼咱們就來看看這個Builder是怎麼操做的:

在Builder的構造方法中就只是獲取到當前應用級別的上下文,也就說明了Picasso是針對應用級別的使用,不會是隨着Activity或是Fragment的生命週期而產生變化,只有噹噹前的應用退出或是銷燬時Picasso纔會中止它的行爲。

那麼接下來看下build方法中作了哪些操做呢:

這裏代碼也是很簡單,主要是初始化了downloader,cache,service,dispatcher等幾個實例變量,而這幾個變量值也是已設置的:

public Builder downloader(Downloader downloader) {
      if (downloader == null) {
        throw new IllegalArgumentException("Downloader must not be null.");
      }
      if (this.downloader != null) {
        throw new IllegalStateException("Downloader already set.");
      }
      this.downloader = downloader;
      return this;
    }

    public Builder executor(ExecutorService executorService) {
      if (executorService == null) {
        throw new IllegalArgumentException("Executor service must not be null.");
      }
      if (this.service != null) {
        throw new IllegalStateException("Executor service already set.");
      }
      this.service = executorService;
      return this;
    }

    public Builder memoryCache(Cache memoryCache) {
      if (memoryCache == null) {
        throw new IllegalArgumentException("Memory cache must not be null.");
      }
      if (this.cache != null) {
        throw new IllegalStateException("Memory cache already set.");
      }
      this.cache = memoryCache;
      return this;
    }

    ...

這些設置就像咱們日常使用AlertDialog同樣,貨到到Builder以後分別進行設置就行。

ok,咱們如今來看看初始化中幾個很是重要的變量:

downloader 下載器

首先看下downloader,builder中首先判斷downloader是否爲空值,當爲空值時就爲它初始化默認值,如:

if (downloader == null) {
        downloader = Utils.createDefaultDownloader(context);
}

來看下Utils中是怎麼實現downloader 的初始化的:

createDefaultDownloader方法中首先使用java反射機制來查找項目中是否使用了okhttp網絡加載框架,若是使用了則會使用okhttp做爲圖片的加載方式,若是沒有使用,則會使用內置的封裝加載器UrlConnectionDownloader。

注:因爲okhttp3的包名已更換,因此在這裏都是使用內置的封裝下載器,這個是一個小bug等待完善。當修復以後Picasso+okhttp3則是最理想的加載方式。

service 線程池

一樣的使用,首先判斷是否爲空,若是爲空則初始化默認對象:

if (service == null) {
    service = new PicassoExecutorService();
}

咱們在來看看PicassoExecutorService的源碼:

PicassoExecutorService直接繼承與ThreadPoolExecutor線程池,在構造方法中初始化了主線程大小,最大線程等,如:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;

固然,在Picasso線程池中主線程和最大線程數是可變的,根據用戶使用的網絡類型來設置線程數量,後面會詳細說明它的應用。

dispatcher 事務分發器

dispatcher 在Picasso中扮演着十分重要的角色,能夠說它是整個圖片加載過程當中的中轉站,主線程和子線程來回的切換主要都是依賴它的存在。下面咱們未來仔細的研究下它的設計,理解好它的存在對整個Picasso框架的理解也基本明朗。

首先,來看看它的登場亮相:

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

它的登場就是建立一個Dispatcher 對象實例,固然,它傳遞了在Builder中所初始化的對象實例,好比downloader下載器,用於圖片的下載,固然下載是不能再主線程進行的,因此這裏也傳遞了service線程池,而下載好的資源也是不能直接在子線程中進行更新UI的,因此同時也把主線程中的HANDLER傳遞進去,同時應用級別的上下文context,和緩存cache,狀態變化stats等也傳遞進去進行相對應的業務操做。

通過上面的分析,咱們能夠很清楚的看出,就這麼簡單的一個建立實例已經很明確的表達出了Dispatcher存在的意義,並且咱們也明確了它大概的職責。

那麼咱們接着看看Dispatcher的構造方法中具體的作了哪些操做:

在Dispatcher的構造方法中我把它分爲了5個部分,下面來詳細的解析下:

①:dispatcherThread,它是一個HandlerThread線程,如:

static class DispatcherThread extends HandlerThread {
    DispatcherThread() {
      super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
    }
  }

它的建立主要是開啓一個子線程供Dispatcher調用,目的就是爲了在子線程中去執行耗時的圖片下載操做。

②:對象實例的接受,主要是接受在Picasso中初始化的對象實例,這個沒有什麼好說的。

③:建立用於保存數據、對象的集合,也就是爲了保存對象或狀態用的。

④:DispatcherHandler,這是一個Handler,而且是做用在dispatcherThread線程中的Handler,它用於把在dispatcherThread子線程的操做轉到到Dispatcher中去,它的構造方法中接受了Dispatcher對象:

咱們在來看看他的handleMessage方法是怎麼處理消息的:

@Override public void handleMessage(final Message msg) {
      switch (msg.what) {
        case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;
        }
        case REQUEST_CANCEL: {
          Action action = (Action) msg.obj;
          dispatcher.performCancel(action);
          break;
        }
        case TAG_PAUSE: {
          Object tag = msg.obj;
          dispatcher.performPauseTag(tag);
          break;
        }
        case TAG_RESUME: {
          Object tag = msg.obj;
          dispatcher.performResumeTag(tag);
          break;
        }
        case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }
        case HUNTER_RETRY: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performRetry(hunter);
          break;
        }
        case HUNTER_DECODE_FAILED: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performError(hunter, false);
          break;
        }
        case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }
        case NETWORK_STATE_CHANGE: {
          NetworkInfo info = (NetworkInfo) msg.obj;
          dispatcher.performNetworkStateChange(info);
          break;
        }
        case AIRPLANE_MODE_CHANGE: {
          dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);
          break;
        }
        default:
          Picasso.HANDLER.post(new Runnable() {
            @Override public void run() {
              throw new AssertionError("Unknown handler message received: " + msg.what);
            }
          });
      }
    }

而從它的處理消息的handleMessage中咱們能夠看出,全部的消息並無被直接進行處理而是轉移到了dispatcher中,在dispatcher中進行相應的處理。

⑤:監聽網絡變化操做,它是用於監聽用戶手機網絡變化而存在的。咱們主要來看看NetworkBroadcastReceiver這個類:

在構造參數中也接收到Dispatcher對象,並有註冊廣播和銷燬廣播的方法,固然它也沒有直接的處理,也是傳遞到Dispatcher中進行消化的。

咱們在來看看當用戶的網絡發生變化時,它會作哪些操做:

咱們能夠看到主要分爲兩個,一個是航班模式,一個是正常的網絡狀態變化,航班模式咱們先不用理會,主要看下網絡變化的操做:

void dispatchNetworkStateChange(NetworkInfo info) {  
         handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info));
}

這裏的handler就是DispatcherHandler,那麼咱們看看它又是怎麼作的:

case NETWORK_STATE_CHANGE: {
          NetworkInfo info = (NetworkInfo) msg.obj;
          dispatcher.performNetworkStateChange(info);
          break;
        }

調用dispatcher的performNetworkStateChange方法來處理:

當網絡變化時,它會傳遞給咱們的PicassoExecutorService線程池,在adjustThreadCount方法中判斷用戶是使用的那類型網絡,如wifi,4G等,而後爲線程池設置相應的線程數。來看:

void adjustThreadCount(NetworkInfo info) {
    if (info == null || !info.isConnectedOrConnecting()) {
      setThreadCount(DEFAULT_THREAD_COUNT);
      return;
    }
    switch (info.getType()) {
      case ConnectivityManager.TYPE_WIFI:
      case ConnectivityManager.TYPE_WIMAX:
      case ConnectivityManager.TYPE_ETHERNET:
        setThreadCount(4);
        break;
      case ConnectivityManager.TYPE_MOBILE:
        switch (info.getSubtype()) {
          case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
          case TelephonyManager.NETWORK_TYPE_HSPAP:
          case TelephonyManager.NETWORK_TYPE_EHRPD:
            setThreadCount(3);
            break;
          case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
          case TelephonyManager.NETWORK_TYPE_CDMA:
          case TelephonyManager.NETWORK_TYPE_EVDO_0:
          case TelephonyManager.NETWORK_TYPE_EVDO_A:
          case TelephonyManager.NETWORK_TYPE_EVDO_B:
            setThreadCount(2);
            break;
          case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
          case TelephonyManager.NETWORK_TYPE_EDGE:
            setThreadCount(1);
            break;
          default:
            setThreadCount(DEFAULT_THREAD_COUNT);
        }
        break;
      default:
        setThreadCount(DEFAULT_THREAD_COUNT);
    }
  }

  private void setThreadCount(int threadCount) {
    setCorePoolSize(threadCount);
    setMaximumPoolSize(threadCount);
  }

就如上面所說,根據用戶使用不一樣的網絡類型分別設置線程的數量,好比當用戶使用的是wifi,線程數量將會設置爲4個,4G的話設爲3個等,這樣根據用戶的具體狀況來設計線程數量是很是人性化的,也是值得咱們效仿的。

ok,到此咱們重要的Dispatcher對象的構造方法已徹底的解析完成了,從上面的解析中咱們很清楚的看到了Dispatcher做爲中轉站存在的意義,幾乎全部的線程轉換操做都是由Dispatcher來操控的,固然可能還有小夥伴們並不清楚它是怎麼運做的,怎麼進入子線程的,那是由於咱們的講解尚未進行到進入子線程的步驟而已,下面將會進一步的講解。

總結下Dispatcher中所包含的重要對象實例:

①:PicassoExecutorService線程池
②:downloader 下載器
③:針對DispatcherThread線程的DispatcherHandler處理器
④:NetworkBroadcastReceiver網絡監聽器
⑤:mainThreadHandler主線程的Handler
⑥:保存數據的集合:hunterMap,pausedActions,batch等

理解好了上面這些對象存在的意義將對下面的理解有着巨大的好處,請沒有徹底理解的在仔細的閱讀一遍(很是重要)。

ok,知道了Dispatcher包含哪些重要的對象實例以後,讓咱們再回到Picasso的Builder中,在build方法中最終返回的是一個Picasso的對象實例:

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

在Picasso的構造方法中值得咱們只要注意的是針對requestHandlers的應用:

這裏把Picasso能都應用的RequestHandler都添加到集合中,而後根據具體的需求運用相對應的Handler進行業務的處理。

ok,到此Picasso中with方法中所作的事情已經完徹底全的展現在您的面前了,相信您對這一步應用理解的很透徹了。

那麼來總結下,Picasso如今擁有了那些重要的對象實例:

①:dispatcher,重要性不言而喻,上面已充分的展現了它的重要性,做爲中轉站,Picasso是必需要擁有了的。最終目的是讓咱們的主線程進入子線程中去進行耗時的下載操做。

②:requestHandlers,它的存在是爲了選擇一種請求處理方式,好比說,下載網絡圖片須要使用NetworkRequestHandler這個請求器。

③:HANDLER,這是主線程的Handler,用來處理返回結果的,好比說圖片下載成功後要更新UI就是經過它來完成的,這會在後面圖片下載完成後更新UI時詳細講解

④:一些配置對象實例等,如:緩存cache,圖片加載默認配置defaultBitmapConfig,狀態存儲stats等等。

load

with方法中主要是作了一個基礎的配置工做,好比Picasso的配置,Dispatcher的配置,這些都是很是重要的前提工做,只有作好了這些配置咱們使用起來才能顯得絕不費勁。

下面咱們就來看看它的應用吧。在load方法中須要咱們傳遞一個參數,這個參數能夠是Url,能夠是一個path路徑,也能夠是一個文件,一個資源佈局等:

①:url
public RequestCreator load(Uri uri) {
    return new RequestCreator(this, uri, 0);
}

②:path
public RequestCreator load(String path) {
    if (path == null) {
      return new RequestCreator(this, null, 0);
    }
    if (path.trim().length() == 0) {
      throw new IllegalArgumentException("Path must not be empty.");
    }
    return load(Uri.parse(path));
}

③:file
public RequestCreator load(File file) {
    if (file == null) {
      return new RequestCreator(this, null, 0);
    }
    return load(Uri.fromFile(file));
}

④:resourceId
public RequestCreator load(int resourceId) {
   if (resourceId == 0) {
      throw new IllegalArgumentException("Resource ID must not be zero.");
    }
    return new RequestCreator(this, null, resourceId);
}

無論傳遞的是一個什麼參數,它都是返回一個請求構造器RequestCreator,咱們來看看它的構造方法作了哪些事情:

主要是獲取到Picasso對象,並Builder模式構建一個Request中的Builder對象:

Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) {
      this.uri = uri;
      this.resourceId = resourceId;
      this.config = bitmapConfig;
    }

這個Builder對象主要接受了一些參數信息,包括url,資源佈局,和默認的圖片配置。

因爲load方法返回的是一個RequestCreator對象,因此咱們可使用方法鏈的形式進行調用其餘的方法,好比給請求添加佔位圖,異常圖,轉換器等等,具體的能夠看源碼。

ok,整體來講這load方法中主要是建立了一個RequestCreator對象,而且RequestCreator中構造了一個Request.Builder對象。

那麼來看看RequestCreator擁有哪些重要的對象實例:

①:picasso對象
②:Request.Builder對象實例data,它裏面包括咱們請求的URL地址,資源文件以及圖片默認配置。

into

在load方法中主要建立了一個RequestCreator對象,並獲取到了要加載的url/path/file/resourceId資源地址路徑,那麼接下來要作的就是在into方法中來加載圖片了。先來看看into方法中作了哪些事情:

public void into(ImageView target, Callback callback) {
    long started = System.nanoTime();
    checkMain();

    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }

    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }

    if (deferred) {
      if (data.hasSize()) {
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      if (width == 0 || height == 0) {
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }

    Request request = createRequest(started);
    String requestKey = createKey(request);

    if (shouldReadFromMemoryCache(memoryPolicy)) {
      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;
      }
    }

    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }

    Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

    picasso.enqueueAndSubmit(action);
  }

如上源碼能夠知道into仍是作了不少的事情,下面一一解析:

①:checkMain():首先檢查主否在主線程運行,若是不是在主線程就會拋出一個應該在主線程運行的異常:throw new IllegalStateException("Method call should happen from the main thread.");這說明到這一步仍是在主線程運行的。

②:data.hasImage():這裏的data是在以前初始化的Request.Builder對象,它裏面包含url地址,resourceId和默認配置,這裏是判斷uri或resourceId是否爲空爲0,若是是的話就取消imageview的請求:picasso.cancelRequest(target);

③:deferred:延遲,若是須要延遲的話就會獲得執行,而後會去獲取data中圖片的大小,若是沒有的話,就獲得target的寬高來從新設置要加載的圖片的尺寸:data.resize(width, height);

④:createRequest:在data.build()方法中建立Request對象,該對象將會包含uri或resourceId以及默認圖片config。而後在獲得的Request對象後進行轉換,該轉換主要是與咱們在Picasso構建時是否自定義了RequestTransformer有關。

⑤:createKey:它主要的是返回已String類型key,主要目的是創建ImageView和key的關聯,能夠經過key來獲取到Imageview的狀態,好比說是否已緩存

⑥:shouldReadFromMemoryCache:看方法名也能知道,它的做用是是否從內存緩存中讀取數據,若是是的話,就從緩存中讀取數據,假如獲取到數據則會取消當前ImageView的請求並直接給它設置Bitmap,並且若是有回調的話將會回調成功的方法。

⑦:ImageViewAction:構建一個ImageViewAction對象,值得注意的是在構建對象時,會把ImageView添加到RequestWeakReference進行存儲,以便於使用時查找,RequestWeakReference是一個WeakReference類型的弱引用。同時ImageViewAction也是在完成圖片加載時真正更新UI的關鍵類,這在後面會進行詳細講解。

⑧:enqueueAndSubmit:調用picasso的enqueueAndSubmit方法進行提交任務。

在來看submit方法的操做:

void submit(Action action) {
    dispatcher.dispatchSubmit(action);
}

在這裏再次使用到了dispatcher任務分發器,把咱們的任務action提交到Dispatcher中進行處理。而後在來看下dispatchSubmit方法:

void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }

由Dispatcher的構造方法咱們能夠知道此時的handler是DispatcherHandler,那麼咱們看下它是怎麼處理咱們的任務行爲的:

case REQUEST_SUBMIT: {
          Action action = (Action) msg.obj;
          dispatcher.performSubmit(action);
          break;

在handleMessage中直接獲取到咱們的任務行爲Action,而後調用performSubmit方法:

void performSubmit(Action action, boolean dismissFailed) {
    if (pausedTags.contains(action.getTag())) {
      pausedActions.put(action.getTarget(), action);
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),
            "because tag '" + action.getTag() + "' is paused");
      }
      return;
    }

    BitmapHunter hunter = hunterMap.get(action.getKey());
    if (hunter != null) {
      hunter.attach(action);
      return;
    }

    if (service.isShutdown()) {
      if (action.getPicasso().loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");
      }
      return;
    }

    hunter = forRequest(action.getPicasso(), this, cache, stats, action);
    hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);
    if (dismissFailed) {
      failedActions.remove(action.getTarget());
    }

    if (action.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());
    }
  }

performSubmit方法是真正意義上的任務提交的具體地方,咱們來解讀下它的源碼:

①:首先根據action的標誌來查詢是否已存在於暫停列表中,若是存在將會把action存放到pausedActions的集合列表中,以便等待喚醒請求。

②:而後經過action的key從hunterMap集合中查詢是否已存在該hunterMap的請求項,若是存在將會調用hunter的attach方法,進行合併請求,避免一個ImageView進行屢次的重複請求:

把action存放到ArrayList當中,若是有相同的action根據ArrayList的不重複性將會保存一個action,而且更新新的屬性值priority。

③:當線程池service沒有關閉的時候,經過forRequest方法獲取一個Runnable類型的BitmapHunter線程,來看下forRequest的源碼:

分別從action和picasso獲取到Request請求和全部的requestHandlers請求處理器,而後遍歷全部的請求器獲取每個請求處理器,調用canHandleRequest嘗試看是否該處理器可以處理,來看下canHandleRequest方法:

public abstract boolean canHandleRequest(Request data);

它是一個抽象方法,須要到子類去查找,而實現它的子類都是根據如下的約束條件來判斷是否能夠處理該請求的:

String scheme = data.uri.getScheme();

也就是說經過url地址的Scheme約束條件進行判斷的,而以咱們如今的action中的Request獲取到url,是以http/https開頭的,那麼來看下NetworkRequestHandler中的canHandleRequest源碼:

private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";

@Override 
public boolean canHandleRequest(Request data) {
    String scheme = data.uri.getScheme();
    return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
  }

能夠看出正好的匹配,由此得出結論,將要處理咱們請求數據的將是NetworkRequestHandler處理器。

匹配好了請求處理器,將會返回一個BitmapHunter的線程,獲取到線程以後會在線程池進行開啓線程,並把該線程存放到hunterMap線程集合中以便屢次請求能夠合併相同的線程:

hunter.future = service.submit(hunter);
    hunterMap.put(action.getKey(), hunter);

ok,如今能夠到咱們的線程池PicassoExecutorService中看看它究竟是怎麼執行的,submit源碼以下:

@Override
  public Future<?> submit(Runnable task) {
    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
  }

用把咱們的線程task封裝到PicassoFutureTask 中,PicassoFutureTask 是一個更便於咱們控住處理的線程,而後調用execute開啓線程,以後會把咱們的線程轉移到addWorker方法中,在addWorker中開啓線程start:

boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }

上述源碼是執行在ThreadPoolExecutor線程池中的,能夠看下源碼。

當線程開啓後,在哪執行呢?

咱們知道咱們的封裝的線程最初始的是BitmapHunter,那麼咱們就到它裏面來看看是怎麼執行的:

在BitmapHunter的run方法中,先修改線程名稱,而後執行hunt方法,把執行結果存放到result中,那咱們先看看hunt方法是怎麼執行的:

Bitmap hunt() throws IOException {
    Bitmap bitmap = null;

    if (shouldReadFromMemoryCache(memoryPolicy)) {
      bitmap = cache.get(key);
      if (bitmap != null) {
        stats.dispatchCacheHit();
        loadedFrom = MEMORY;
        if (picasso.loggingEnabled) {
          log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
        }
        return bitmap;
      }
    }

    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
    RequestHandler.Result result = requestHandler.load(data, networkPolicy);
    if (result != null) {
      loadedFrom = result.getLoadedFrom();
      exifRotation = result.getExifOrientation();

      bitmap = result.getBitmap();

      // If there was no Bitmap then we need to decode it from the stream.
      if (bitmap == null) {
        InputStream is = result.getStream();
        try {
          bitmap = decodeStream(is, data);
        } finally {
          Utils.closeQuietly(is);
        }
      }
    }

    if (bitmap != null) {
      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_DECODED, data.logId());
      }
      stats.dispatchBitmapDecoded(bitmap);
      if (data.needsTransformation() || exifRotation != 0) {
        synchronized (DECODE_LOCK) {
          if (data.needsMatrixTransform() || exifRotation != 0) {
            bitmap = transformResult(data, bitmap, exifRotation);
            if (picasso.loggingEnabled) {
              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
            }
          }
          if (data.hasCustomTransformations()) {
            bitmap = applyCustomTransformations(data.transformations, bitmap);
            if (picasso.loggingEnabled) {
              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
            }
          }
        }
        if (bitmap != null) {
          stats.dispatchBitmapTransformed(bitmap);
        }
      }
    }

    return bitmap;
  }

解析下源碼:

①:仍是首先獲取到action行爲對應key,經過key從緩存cache中查找bitmap是否存在,若是存在修改stats狀態並直接的把bitmap返回。

②:若是緩存中不存在則是要去網絡加載requestHandler.load();咱們經過上面的分析知道能處理當前requesr的requestHandler是NetworkRequestHandler,那麼咱們去NetworkRequestHandler的load方法中查看:

這裏很清晰的能夠看到是直接調用downloader的load方法,而咱們的downloader在Picasso構建Builder的時候也很清晰的說明是UrlConnectionDownloader,那麼在去UrlConnectionDownloader的load方法看看:

protected HttpURLConnection openConnection(Uri path) throws IOException {
    HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection();
    connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS);
    connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS);
    return connection;
  }

  @Override public Response load(Uri uri, int networkPolicy) throws IOException {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      installCacheIfNeeded(context);
    }

    HttpURLConnection connection = openConnection(uri);
    connection.setUseCaches(true);

    if (networkPolicy != 0) {
      String headerValue;

      if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        headerValue = FORCE_CACHE;
      } else {
        StringBuilder builder = CACHE_HEADER_BUILDER.get();
        builder.setLength(0);

        if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
          builder.append("no-cache");
        }
        if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
          if (builder.length() > 0) {
            builder.append(',');
          }
          builder.append("no-store");
        }

        headerValue = builder.toString();
      }

      connection.setRequestProperty("Cache-Control", headerValue);
    }

    int responseCode = connection.getResponseCode();
    if (responseCode >= 300) {
      connection.disconnect();
      throw new ResponseException(responseCode + " " + connection.getResponseMessage(),
          networkPolicy, responseCode);
    }

    long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
    boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));

    return new Response(connection.getInputStream(), fromCache, contentLength);
  }

這裏的代碼相信咱們都很熟悉,就是構建一個HttpURLConnection 去進行網絡請求,固然還有就是根據networkPolicy進行一些網絡緩存的策略。最後把結果存放到Response對象中。

而後NetworkRequestHandler的load方法又會從Response對象中獲取數據,並把它存放到Result對象中。而後返回給BitmapHunter中hunt方法的RequestHandler.Result result中,從result中獲取輸入流,解析輸入流轉化爲Bitmap並返回。

到此咱們已從網絡中下載了數據,並轉化爲bitmap了,而後在返回咱們的BitmapHunter中的run方法中,在run方法中咱們獲取到了bitmap,而後調用dispatcher進行事物分發,成功獲取則調用dispatchComplete,不然調用dispatchFailed。

下面咱們看看dispatcher中的dispatchComplete方法:

void dispatchComplete(BitmapHunter hunter) {
    handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
  }

一樣的道理,這裏的handler仍是DispatcherHandler,那麼來看看它的處理:

case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }

轉到dispatcher的performComplete方法中:

這裏首先把咱們的結果保存在cache緩存中,而後從hunterMap集合中移除BitmapHunter對應的key,緣由是請求已完成。而後調用 batch(hunter);方法:

首先判斷BitmapHunter是否已取消,而後把BitmapHunter存放在一個List< BitmapHunter > batch集合中,最後經過DispatcherHandler發送一個空的延遲消息,目的是爲了延遲下下一個網絡加載以便處理當前的bitmap工做,來看下它是怎麼處理的:

case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }

進入performBatchComplete中查看:

void performBatchComplete() {
    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
    batch.clear();
    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
    logBatch(copy);
  }

performBatchComplete中首先獲取存放BitmapHunter的集合,而後調用mainThreadHandler發送消息。

還記得mainThreadHandler是怎麼嗎?

在Picasso類中構建Builder的build方法中,建立一個Dispatcher對象,裏面傳進一個HANDLER的參數,請看:

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

這個HANDLER就是mainThreadHandler。

那麼它發送的消息是怎麼處理的呢?

獲取到BitmapHunter集合進行遍歷,而後直接調用Picasso中的complete方法:

void complete(BitmapHunter hunter) {
    Action single = hunter.getAction();
    List<Action> joined = hunter.getActions();

    boolean hasMultiple = joined != null && !joined.isEmpty();
    boolean shouldDeliver = single != null || hasMultiple;

    if (!shouldDeliver) {
      return;
    }

    Uri uri = hunter.getData().uri;
    Exception exception = hunter.getException();
    Bitmap result = hunter.getResult();
    LoadedFrom from = hunter.getLoadedFrom();

    if (single != null) {
      deliverAction(result, from, single);
    }

    if (hasMultiple) {
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, n = joined.size(); i < n; i++) {
        Action join = joined.get(i);
        deliverAction(result, from, join);
      }
    }

    if (listener != null && exception != null) {
      listener.onImageLoadFailed(this, uri, exception);
    }
  }

在complete方法中,直接從BitmapHunter中獲取原已封裝的Action,Uri,Bitmap等等數據信息,而後調用deliverAction方法:

這裏進行一系列的判斷,當action沒有取消,而且Bitmap不爲空時,將會調用action.complete(result, from);來完成操做。

那還記得咱們在建立action時的操做嗎?

Action action =
        new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
            errorDrawable, requestKey, tag, callback, noFade);

那麼很清晰的就知道咱們的Action 其實就是ImageViewAction呢

那麼咱們來看下ImageViewAction的complete是怎麼操做的呢?

target就是咱們在建立ImageViewAction時傳遞的ImageView,如今獲取到它,而後調用PicassoDrawable.setBitmap方法來完成設置圖片:

當咱們看到target.setImageDrawable(drawable);時,是否是終於鬆了一口氣呢,終於看到了爲ImageView設置圖片的信息了。

好了,到了這一步整個Picasso加載圖片的源碼執行流程就已徹底的解析完畢了,相信您能夠很是清楚的瞭解了整個框架的加載過程,同時我也相信可能不多人能作到我這麼詳細的完徹底全的分析整個執行過程,並且整個過程當中並無給你們留下什麼盲點,是真真正正的源碼大解析。

各位若是還有哪裏不明白的,或是我這裏講的還不夠透徹,亦或是講錯了的地方請留言指正,讓咱們共同進步,謝謝

同時,請你們掃一掃關注個人微信公衆號,雖然寫的不是很勤,可是每一篇都有質量保證,讓您學習到真正的知識。

相關文章
相關標籤/搜索