1. 程式人生 > >玩轉Android之Picasso使用詳詳詳詳詳詳解,從入門到原始碼剖析!!!!

玩轉Android之Picasso使用詳詳詳詳詳詳解,從入門到原始碼剖析!!!!

Picasso是Squareup公司出的一款圖片載入框架,能夠解決我們在Android開發中載入圖片時遇到的諸多問題,比如OOM,圖片錯位等,問題主要集中在載入圖片列表時,因為單張圖片載入誰都會寫。如果我們想在ListView或者GridView或者RecyclerView中載入圖片牆,那麼這個時候對原圖片的二次處理就顯得非常重要了,否則就會出現我們上文說的OOM或者圖片錯位等。不過,如果你使用了Picasso來載入圖片的話,那麼所有問題都會變得很簡單。OK,那我們今天就來看看Picasso的使用。

1.基本使用

Picasso載入一張網路圖片,最簡單的一行程式碼就搞定:

Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);

如果你想對這張圖片進行剪裁,可以使用resize方法:
Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .resize(200,200)
                .into(iv);

注意這裡的200表示200px,如果你想在resize時指定dp,可以使用如下方法:
Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)
                .into(iv);

在dimen檔案中定義寬高即可:
<dimen name="iv_width">200dp</dimen>
    <dimen name="iv_height">200dp</dimen>

其實我們看看resizeDimen的原始碼就知道它是怎麼設定dp了:
  /** Resize the image to the specified dimension size. */
  public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) {
    Resources resources = picasso.context.getResources();
    int targetWidth = resources.getDimensionPixelSize(targetWidthResId);
    int targetHeight = resources.getDimensionPixelSize(targetHeightResId);
    return resize(targetWidth, targetHeight);
  }

一句話,它就是把dp讀取成px然後呼叫resize方法實現的。

OK,很多時候我還可以給Picasso下載的圖片設定縮放模式,也就是ImageView的ScaleType屬性(不瞭解的請移步這裡),但是注意,縮放模式centerCrop和centerInside要和resize一起使用,否則會拋異常,而縮放模式fit不可以和resize一起使用,如下:

使用fit:

Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .fit()
                .into(iv);

使用centerCrop:
        Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)
                .centerCrop()
                .into(iv);

很多時候我們在圖片加載出來之前需要先顯示一張預設圖片,也即佔位圖,而在圖片加載出錯的時候我們可能想顯示一張錯誤圖,這個Picasso也是支援的:

Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                //佔位圖,圖片加載出來之前顯示的預設圖片
                .placeholder(R.mipmap.ic_launcher)
                //錯誤圖,圖片加載出錯時顯示的圖片
                .error(R.mipmap.ic_launcher)
                .into(iv);

很多時候,我們可能想顯示一個使用者影象,但是這個使用者影象是個圓形圖片,這個用Picasso該怎麼實現呢?首先定義一個Transformation,在transform方法中對圖片進行二次處理,包括剪裁重新處理等等,那我這裡想把原圖變為一個圓形圖,就可以按下面的寫法來:
Transformation transformation = new Transformation() {
            @Override
            public Bitmap transform(Bitmap source) {
                int width = source.getWidth();
                int height = source.getHeight();
                int size = Math.min(width, height);
                Bitmap blankBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                Canvas canvas = new Canvas(blankBitmap);
                Paint paint = new Paint();
                paint.setAntiAlias(true);
                canvas.drawCircle(size / 2, size / 2, size / 2, paint);
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                canvas.drawBitmap(source, 0, 0, paint);
                if (source != null && !source.isRecycled()) {
                    source.recycle();
                }
                return blankBitmap;
            }

            @Override
            public String key() {
                return "squareup";
            }
        };

paint的setXfermode表示最終顯示的圖形取所繪製圖形的交集,我這裡先繪製了圓形,又繪製了一個矩形的Bitmap,圓形沒有Bitmap大,所以交集肯定是圓形,所以最終顯示結果就為圓形,在載入圖片的時候可以通過transform屬性來使用自定義的這個transformation,如下:
 Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .transform(transformation)
                .into(iv);

最終顯示結果如下:

依照這個思路,你想把影象做成什麼形狀都可以了吧!

Picasso還可以通過開啟指示器,讓你看到這個圖片是從記憶體載入來的還是從SD卡載入來的還是從網路載入來的,設定方式如下:

 Picasso picasso = Picasso.with(this);
        //開啟指示器
        picasso.setIndicatorsEnabled(true);
        picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .into(iv);

開啟之後,圖片的載入效果如下:

左上角會有一個藍色的三角符號,不同的顏色表示圖片的來源不同,紅、藍、綠三種顏色分別代表網路、SD卡和記憶體。

現在大部分的圖片快取框架都是支援三級快取的,在Picasso中,我們也可以手動設定快取策略,比如說當我們檢視一張大圖的時候,可能由於圖片太大,不想將其快取在記憶體中,那麼可以自定義快取策略,如下:

        Picasso picasso = Picasso.with(this);
        //開啟指示器
        picasso.setIndicatorsEnabled(true);
        picasso
                .load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                //第一個引數是指圖片載入時放棄在記憶體快取中查詢
                //第二個引數是指圖片載入完不快取在記憶體中
                .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
                .into(iv);

當然,如果你想給圖片載入過程設定一個監聽器也是可以的,如下:
        Picasso picasso = Picasso.with(this);
        //開啟指示器
        picasso.setIndicatorsEnabled(true);
        picasso
                .load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                //第一個引數是指圖片載入時放棄在記憶體快取中查詢
                //第二個引數是指圖片載入完不快取在記憶體中
                .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)
                .into(iv, new Callback() {
                    @Override
                    public void onSuccess() {
                        Log.d("google_lenve_fb", "onSuccess: 圖片載入成功!");
                    }

                    @Override
                    public void onError() {
                        Log.d("google_lenve_fb", "onSuccess: 圖片載入失敗!");
                    }
                });

在ListView或者RecyclerView中載入圖片時,當列表處於滑動狀態的時候,我們可以停止圖片的載入,當列表停止滾動的時候,我們又可以繼續載入圖片,如下:
        Object tag = new Object();
        Picasso with = Picasso.with(this);
        with.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")
                .into(iv);
        //暫停載入
        with.pauseTag(tag);
        //恢復載入
        with.resumeTag(tag);

這裡要傳遞的物件可以是任意物件,這兩個方法的使用需要我們自己去監聽ListView或者GridView的滑動狀態。OK,以上這些都屬於Picasso的一個基本使用,接下來我們來看看一些高階使用技巧。

2.自定義快取位置

既然我們知道Picasso自帶三級快取,那麼問題就來了,儲存在SD卡的圖片到底儲存在哪裡呢?在手機的內部儲存中,即  /data/data/應用包名/cache  目錄下,這個目錄如果你有root許可權就可以檢視,可是有的時候我們需要自定義快取位置,即不想將圖片快取在這裡,又該怎麼辦?說到這裡,我們不得不來看看Picasso的原始碼,with方法原始碼如下:

  public static Picasso with(Context context) {
    if (singleton == null) {
      synchronized (Picasso.class) {
        if (singleton == null) {
          singleton = new Builder(context).build();
        }
      }
    }
    return singleton;
  }
大家看到,with方法返回了一個Picasso的單例,在建立Picasso的過程中,呼叫了new Builder(context).build()方法,說明Picasso例項建立的程式碼在build方法中,那我們再來看看這個build方法:

    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);

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

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

我們先不急著看build中的其他方法,先來看看downloader這個判斷(如果我使用with方法downloader肯定為null),如果downloader為null,則系統會幫我們建立一個預設的downloader,那我們來看看這個預設的downloader是怎麼建立的:
  static Downloader createDefaultDownloader(Context context) {
    try {
      Class.forName("com.squareup.okhttp.OkHttpClient");
      return OkHttpLoaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    return new UrlConnectionDownloader(context);
  }

啊哈,這裡就很明白了,系統通過反射來檢查我們在專案中是否使用了OkHttp,如果使用了,就使用OkHttp來建立一個下載器,否則就使用HttpUrlConnection來建立一個下載器,可是大家注意Class.forName("com.squareup.okhttp.OkHttpClient");這個方法的引數,這是OkHttp3以前的寫法,現在我們都是使用OkHttp3了,OkHttp3的包名就不是這個樣子,而是okhttp3.OkHttpClient,所以即使你在專案中引用了OkHttp3,Picasso還是會把HttpUrlConnection當作下載器來下載圖片的,這個問題估計Picasso會在以後的版本中修正吧!OK,那如果我們想要使用自己的下載器又該怎麼做呢?其實很簡單,首先不使用with這個方法來初始化Picasso,而是使用Builder來初始化,在初始化的過程中傳入自己的下載器,自己的下載器我們可以模仿Picasso裡邊的這個下載器來寫,也可以自定義,我們來看一個Demo:
Picasso picasso = new Picasso.Builder(this)
                .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))
                .build();
        Picasso.setSingletonInstance(picasso);
        picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);

使用Builder來構建一個Picasso,在構建的過程中傳入自己的下載器,這個下載器我沒有自己來寫,使用GitHub上的開源專案https://github.com/JakeWharton/picasso2-okhttp3-downloader,裡邊的程式碼也都很簡單,只有一個類,拷貝到你的專案中就可使用,不贅述。這樣修改之後,Picasso的圖片快取位置就發生了改變,存到了  /storage/sdcard/Android/data/應用包名/cache   資料夾中,不同手機這個地址前面一部分可能會有一點點差異。使用這個方法初始化的時候,還呼叫了setSingletonInstance方法,我們來看看這個方法:
  public static void setSingletonInstance(Picasso picasso) {
    synchronized (Picasso.class) {
      if (singleton != null) {
        throw new IllegalStateException("Singleton instance already exists.");
      }
      singleton = picasso;
    }
  }

這個主要是用來檢查Picasso的單例模式,如果Picasso不是單例的,則LruCache會失效,原因很簡單,如果Picasso不是單例的,每一個Picasso都有自己的LruCache,那麼LruCache本身的功能當然會失效。這一點需要注意。

3.自定義下載執行緒池

關於Android開發中執行緒池,如果你還不瞭解,可以參考Android開發之執行緒池使用總結,使用Picasso下載圖片的時候,系統內部也是有一個執行緒池,想看這個,我們還是得回到build方法:

    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);

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

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

在build方法中還有一個判斷,如果service為null,則新建立一個PicassoExecutorService,我們來看看這個PicassoExecutorService:
class PicassoExecutorService extends ThreadPoolExecutor {
  private static final int DEFAULT_THREAD_COUNT = 3;

  PicassoExecutorService() {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
  }

  ....
  ....
} 

大家看到,這個PicassoExecutorService繼承自ThreadPoolExecutor這個執行緒池,執行緒池中的核心執行緒數為3,執行緒池的最大執行緒數也為3,說明執行緒池中沒有非核心執行緒,執行緒佇列使用了PriorityBlockingQueue,說明所有載入進來的任務都將實現Comparator介面。OK,這是系統預設幫我們建立的執行緒池,如果你想修改,可以在建立Picasso例項的時候傳入自己的執行緒池:
        int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CPU_COUNT + 1, CPU_COUNT * 2 + 1,
                1, TimeUnit.MINUTES, new PriorityBlockingQueue<Runnable>());
        Picasso picasso = new Picasso.Builder(this)
                .executor(threadPoolExecutor)
                .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))
                .build();
        Picasso.setSingletonInstance(picasso);
        picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);

對執行緒池的建立如果還不瞭解的話,請參考Android開發之執行緒池使用總結

4.自定義下載進度條

我在之前的一篇文章中專門介紹過自定義進度條,沒看過的小夥伴請戳這裡Android自定義View之ProgressBar出場記。那我們今天就給Picasso載入圖片的過程也來一個進度條,先來看看顯示效果吧:

整體思路其實很簡單,最關鍵是你要會用OkHttp。

經過上文的講解,小夥伴們已經知道,我可以在構造一個Picasso例項的時候給它設定一個下載器,這個下載器是由OkHttp實現的,在這個下載器中我可以修改Picasso所載入圖片的儲存位置,同理,下載器中我也可以傳遞一個OkHttpClient作為構造引數(上文使用了快取資料夾作為構造引數),我們來看看:

    public OkHttp3Downloader(OkHttpClient client) {
        this.client = client;
        this.cache = client.cache();
    }

大家看到,如果我使用OkHttpClient作為構造引數,那麼快取位置則為OkHttpClient的快取地址。而在OkHttpClient中有一個攔截器,我們可以在攔截器中來計算當前下載百分比,整體思路就是這樣,我們來看看實現過程:

首先我來定義一個介面,這個介面用來更新我的進度條:

public interface ProgressListener {
    //定義介面,取值範圍為0~100
    public void update(@IntRange(from = 0, to = 100) int progress);
}
然後定義一個OkHttpClient物件,在定義的過程中給OkHttpClient新增攔截器:
OkHttpClient client = new OkHttpClient.Builder()
                .addNetworkInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Response response = chain.proceed(chain.request());
                        return response.newBuilder()
                                .body(new MyProgressbarResponseBody(new ProgressListener() {
                                    @Override
                                    public void update(@IntRange(from = 0, to = 100) final int progress) {
                                        //更新進度條
                                        runOnUiThread(new Runnable() {
                                            @Override
                                            public void run() {
                                                Log.d("google_lenve_fb", "run: " + progress);
                                                myPb.setSweepAngle(progress * 360f / 100);
                                            }
                                        });
                                    }
                                }, response.body()))
                                .build();
                    }
                })
                //設定快取位置,Picasso下載的圖片將快取在這裡
                .cache(new Cache(this.getExternalCacheDir(), 10 * 1024 * 1024))
                .build();

大家看到,這裡核心的程式碼要算addNetworkInterceptor中的程式碼了,OkHttp中的攔截器有點類似於JavaWeb中的過濾器 ,在所有的請求到達Servlet之前,先對其進行一個簡單的處理。而OkHttp中的攔截器,我們可以觀察,修改請求和響應,大多數情況下我們使用攔截器來新增、移除、轉換請求或者響應的頭資訊。OK,那麼在本案例中我重新修改了Response的body屬性,給它傳入兩個引數,一個就是剛剛定義的監聽器,還有一個就是response的body,我們來看看這個MyProgressbarResponseBody,如下:
public class MyProgressbarResponseBody extends ResponseBody {
    private ResponseBody responseBody;
    private ProgressListener progressListener;
    private BufferedSource bufferedSource;

    public MyProgressbarResponseBody(ProgressListener progressListener, ResponseBody responseBody) {
        this.progressListener = progressListener;
        this.responseBody = responseBody;
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }
    private Source source(Source source) {

        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                if (progressListener != null) {
                    progressListener.update(
                            ((int) ((100 * totalBytesRead) / responseBody.contentLength())));
                }
                return bytesRead;
            }
        };
    }
}

MyProgressbarResponseBody繼承自ResponseBody,並重寫它裡邊的三個方法,分別返回資料型別,資料大小等資訊,在source方法中我們來統計當前下載百分比,並且回撥監聽器中的介面。最後再來看一眼自定義的ProgressBar,對這個如果還不瞭解,請參考Android自定義View之ProgressBar出場記
public class MyProgressBar extends View {
    /**
     * View預設的寬
     */
    private static final int DEFAULTWIDTH = 200;
    /**
     * View預設的高度
     */
    private static final int DEFAULTHEIGHT = 200;
    private Paint sweepPaint;
    private int padding = 20;
    /**
     * 內層實體圓的顏色
     */
    private int sweepColor = getResources().getColor(R.color.pbColor);
    /**
     * 開始繪製的角度
     */
    private int startAngle = -90;
    /**
     * 已經繪製的角度
     */
    private float sweepAngle = 0;

    public MyProgressBar(Context context) {
        this(context, null);
    }

    public MyProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setAlpha(0.8f);
        sweepPaint = new Paint();
        sweepPaint.setColor(sweepColor);
        sweepPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取寬的測量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //獲取寬的測量值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //獲取高的測量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //獲取高的測量值
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                //如果寬為wrap_content,則給定一個預設值
                widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());
                break;
        }
        widthSize = heightSize = Math.min(widthSize, heightSize);
        //設定測量結果
        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (sweepAngle != 360 && sweepAngle != 0) {
            RectF oval = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
            Log.d("google_lenve_fb", "onDraw: " + sweepAngle);
            canvas.drawArc(oval, startAngle, sweepAngle, true, sweepPaint);
        }
    }

    public void setSweepAngle(float sweepAngle) {
        this.sweepAngle = sweepAngle;
        if (Build.VERSION.SDK_INT > 15) {
            postInvalidateOnAnimation();
        } else {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
}

最後,載入一張網路圖片幾個,注意下載器的建立方式:

     Picasso picasso = new Picasso
                .Builder(this)
                .downloader(new OkHttp3Downloader(client))
                .build();

5.Picasso原始碼剖析

其實我們在上文已經涉及到一些原始碼方面的東西了,那麼接下來我們就來理一理Picasso載入圖片的整體思路,首先還是先從with方法開始,進入到build方法中:

    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);

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

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

build方法中前面幾個if判斷我們剛才已經說過了,這幾個判斷中的變數我們在建立Picasso例項的時候都可以自定義,也可以使用系統預設建立的,我們再來總結一下:

downloader   建立一個下載器

cache 建立圖片的快取器,預設使用LruCache,這個我們一般不做修改,最多重新配置一下LruCache

service 建立圖片下載的執行緒池

transformer 對Request進行轉換,預設不做任何出處理,事實上我們一般也不需要做任何處理。

接下來就是建立一個Stats例項,這個stats主要是用來統計快取,下載數量等資料,一言以蔽之,就是儲存圖片的一些狀態資訊。再之後,則是建立一個Dispatcher,建立Dispatcher的時候還傳入了一個HANDLER,這個Handler我們在後文再說,dispatcher顧名思義就是分發,事實上dispatcher主要用來任務排程,這個一會再說,最後new一個Picasso例項返回:

  Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
      RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
      Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
    this.context = context;
    this.dispatcher = dispatcher;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformer = requestTransformer;
    this.defaultBitmapConfig = defaultBitmapConfig;

    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
    List<RequestHandler> allRequestHandlers =
        new ArrayList<RequestHandler>(builtInHandlers + extraCount);

    // ResourceRequestHandler needs to be the first in the list to avoid
    // forcing other RequestHandlers to perform null checks on request.uri
    // to cover the (request.resourceId != 0) case.
    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
      allRequestHandlers.addAll(extraRequestHandlers);
    }
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    this.stats = stats;
    this.targetToAction = new WeakHashMap<Object, Action>();
    this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
    this.referenceQueue = new ReferenceQueue<Object>();
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    this.cleanupThread.start();
  }

OK,大家看到在Picasso的構造方法裡主要進行了一些變數的初始化,也初始化了RequestHandler,初始化RequestHandler時首先將我們提交進來的requestHandler加入到集合中,然後還往allRequestHandlers中提交了其它的RequestHandler,這些不同的RequestHandler,分別用來處理不同的資源,比如載入相簿的圖片、載入資產資料夾中的圖片、載入網路圖片等。

OK,那麼到目前為止,我們所看到的都是build方法中引出的原始碼,執行完build之後,我們接下來該做的就是呼叫load方法了,不管你在load中傳入了什麼,最終都會到達下面這個方法:

  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);
  }

shutdown屬性是判斷Picasso例項是否已經停止執行,如果已經shutdown則拋異常,否則將我們即將要載入的圖片資訊儲存在data中,data是一個Request.Builder物件,裡邊儲存了我們所有的圖片載入的配置資訊,比如你呼叫了centerCrop方法:
  public RequestCreator centerCrop() {
    data.centerCrop();
    return this;
  }

大家看到這些方法不過都是修改data裡邊的變數,當所有的配置資訊都完成之後,接下載就到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方法檢查程式是否執行在主執行緒,接下來target不能為空,這個不用多說,簡單。data.hasImage表示是否設定了要載入的圖片資源,如果設定了,則返回true,否則返回false。返回false時進入到if判斷中,這個時候首先取消載入,然後如果設定了佔位圖片,就將其顯示出來。接下來進入到if(deferred)的判斷中,deferred這個變數是在哪裡進行初始化的呢?我們來看看這裡:

  public RequestCreator fit() {
    deferred = true;
    return this;
  }

  /** Internal use only. Used by {@link DeferredRequestCreator}. */
  RequestCreator unfit() {
    deferred = false;
    return this;
  }

是在我們呼叫了fit方法的時候,也就是說,如果我們希望我們的圖片在載入的過程中能夠自由縮放以填滿整個ImageView的話,那麼就會進入到這個分支中,進來之後首先是判斷data.hasSize,我們知道這個是判斷圖片是否有寬高,我們來看看hasSize方法:
    boolean hasSize() {
      return targetWidth != 0 || targetHeight != 0;
    }

那麼targetWidth和targetHeight又是在什麼地方呼叫的呢?我們不由得想到了resize方法:
    public Builder resize(int targetWidth, int targetHeight) {
      if (targetWidth < 0) {
        throw new IllegalArgumentException("Width must be positive number or 0.");
      }
      if (targetHeight < 0) {
        throw new IllegalArgumentException("Height must be positive number or 0.");
      }
      if (targetHeight == 0 && targetWidth == 0) {
        throw new IllegalArgumentException("At least one dimension has to be positive number.");
      }
      this.targetWidth = targetWidth;
      this.targetHeight = targetHeight;
      return this;
    }

沒錯,是這裡,那我們在這裡可以得出結論了,如果在載入一張圖片的是否使用了fit這種縮放模式的話,那麼不可以給圖片設定resize屬性,否則會拋一個Fit cannot be used with resize異常,其實這個也很好理解,你設定了fit就是希望圖片自由縮放以便將ImageView填充滿,結果又給圖片設定了固定大小,那麼你到底想怎樣?。接下來系統來獲取ImageView的寬和高,如果ImageView的寬和高為0的話,則首先把佔位圖片設定上,然後去監聽ImageView的target.getViewTreeObserver().addOnPreDrawListener(this);介面,當ImageView的寬高被賦值之後,繼續載入。否則直接設定ImageView的寬高為圖片的寬高。OK,以上還都是在做準備工作,一個網路請求還是沒有發起。接下來我們就要開始構造請求了,在into方法的第33行,我們構建一個請求,接下來是一個shouldReadFromMemoryCache,看名字就知道是否該從記憶體中讀取圖片,如果是,則根據key從Cache中讀取一張圖片出來,不知道大家是否還記得我們的Cache實際上就是LruCache。
如果從內從中讀取到了圖片,就取消請求,並把圖片設定給ImageView。同時,如果我們設定了回撥,則呼叫回撥的onSuccess方法。

接下來55行建立Action,並且將Action新增到一個Picasso的enqueueAndSubmit方法中。接下來我們就來看看這個請求入隊的方法:

  void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
      // This will also check we are on the main thread.
      cancelExistingRequest(target);
      targetToAction.put(target, action);
    }
    submit(action);
  }

首先獲取action裡邊的target,其實就是我們的ImageView,如果這個ImageView不為空,並且該ImageView已經有了一個Action,則取消已經存在的請求,然後重新給該target設定Action,完了之後就是submit了,我們來看看這個submit:
void submit(Action action) {
    dispatcher.dispatchSubmit(action);
  }

咦,dispatcher,大家還記不記得我們是在哪裡初始化的dispatcher呢?沒錯,build方法中,這裡呼叫了dispatcher的dispatchSubmit方法,點選去再看:
void dispatchSubmit(Action action) {
    handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
  }

哦,原來是使用了Handler,傳送了一條訊息,那我們來找找handler初始化的地方,在Dispatcher類中,Handler通過如下方式初始化:
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
竟然不是new一個Handler,DispatcherHandler是什麼鬼?來看看:
  private static class DispatcherHandler extends Handler {
    private final Dispatcher dispatcher;

    public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
      super(looper);
      this.dispatcher = dispatcher;
    }

    @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);
            }
          });
      }
    }
  }

DispatcherHandler繼承自Handler重寫了它裡邊的方法,順藤摸瓜,找到屬於我們的case,點進去,最終來到了這個方法:
  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());
    }
  }
一進來,首先判斷該請求是否該暫停,接下來關鍵的是24行,呼叫forRequest方法給hunter賦值,我們來看看這個forRequest方法:
  static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
      Action action) {
    Request request = action.getRequest();
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();

    // Index-based loop to avoid allocating an iterator.
    //noinspection ForLoopReplaceableByForEach
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }

    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }

這裡有一個for迴圈,for迴圈中的東西就是我們所有的RequestHandler,然後通過一個if來匹配,看使用那個RequestHandler來處理我們的圖片載入。

第25行建立一個BitmapHunter,並在執行緒池中執行請求,執行緒池中傳入的物件是hunter,那毫無疑問,hunter肯定是實現了Runnable介面的,那接下來就去看看這個BitmapHunter的run方法:

  @Override public void run() {
    try {
      updateThreadName(data);

      if (picasso.loggingEnabled) {
        log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));
      }

      result = hunt();

      if (result == null) {
        dispatcher.dispatchFailed(this);
      } else {
        dispatcher.dispatchComplete(this);
      }
    } catch (Downloader.ResponseException e) {
      if (!e.localCacheOnly || e.responseCode != 504) {
        exception = e;
      }
      dispatcher.dispatchFailed(this);
    } catch (NetworkRequestHandler.ContentLengthException e) {
      exception = e;
      dispatcher.dispatchRetry(this);
    } catch (IOException e) {
      exception = e;
      dispatcher.dispatchRetry(this);
    } catch (OutOfMemoryError e) {
      StringWriter writer = new StringWriter();
      stats.createSnapshot().dump(new PrintWriter(writer));
      exception = new RuntimeException(writer.toString(), e);
      dispatcher.dispatchFailed(this);
    } catch (Exception e) {
      exception = e;
      dispatcher.dispatchFailed(this);
    } finally {
      Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
  }
一進來,先更新執行緒名稱,然後是第9行呼叫了hunt方法,獲取到一個result,這個result是一個Bitmap,如果獲取到了Bitmap則呼叫dispatcher.dispatchComplete方法,否則呼叫dispatcher.dispatchFailed方法,這兩個實際上都是呼叫了Handler的sendMessage方法,來發送不同的訊息做不同處理,我們這裡就來看看hunt()方法,看看這個Bitmap到底是怎麼獲取的:
  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;
  }

首先是判斷是否可以從記憶體中獲取這張圖片,如果可以,將圖片加載出來並返回,並更新stats中相關變數,否則就會來到第17行,從一個RequestHandler中讀取,那麼RequestHandler是我們在new一個Picasso的時候傳入了多個RequestHandler,這裡到底是使用哪一個RequestHandler呢?這就和我們上文說的匹配RequestHandler有關了,毫無疑問,我們下載網路圖片,當然是匹配NetworkRequestHandler,那我們看看NetworkRequestHandler裡邊的load方法:
  @Override public Result load(Request request, int networkPolicy) throws IOException {
    Response response = downloader.load(request.uri, request.networkPolicy);
    if (response == null) {
      return null;
    }

    Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;

    Bitmap bitmap = response.getBitmap();
    if (bitmap != null) {
      return new Result(bitmap, loadedFrom);
    }

    InputStream is = response.getInputStream();
    if (is == null) {
      return null;
    }
    // Sometimes response content length is zero when requests are being replayed. Haven't found
    // root cause to this but retrying the request seems safe to do so.
    if (loadedFrom == DISK && response.getContentLength() == 0) {
      Utils.closeQuietly(is);
      throw new ContentLengthException("Received response with 0 content-length header.");
    }
    if (loadedFrom == NETWORK && response.getContentLength() > 0) {
      stats.dispatchDownloadFinished(response.getContentLength());
    }
    return new Result(is, loadedFrom);
  }

這個方法裡首先呼叫了downloader裡邊的load方法,獲取一個Response物件,然後再拿到這個response物件裡邊的Bitmap返回,downloader就是我們在上文說的那個downloader,我們就看那個原始碼吧,反正和Piasso自帶的差不多,看看它裡邊的load方法:
    @Override
    public Response load(Uri uri, int networkPolicy) throws IOException {
        CacheControl cacheControl = null;
        if (networkPolicy != 0) {
            if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
                cacheControl = CacheControl.FORCE_CACHE;
            } else {
                CacheControl.Builder builder = new CacheControl.Builder();
                if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
                    builder.noCache();
                }
                if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
                    builder.noStore();
                }
                cacheControl = builder.build();
            }
        }

        Request.Builder builder = new Request.Builder().url(uri.toString());
        if (cacheControl != null) {
            builder.cacheControl(cacheControl);
        }

        okhttp3.Response response = client.newCall(builder.build()).execute();
        int responseCode = response.code();
        if (responseCode >= 300) {
            response.body().close();
            throw new ResponseException(responseCode + " " + response.message(), networkPolicy,
                    responseCode);
        }

        boolean fromCache = response.cacheResponse() != null;

        ResponseBody responseBody = response.body();
        return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());
    }
哈哈,在這裡我們總算看到了網路訪問的程式碼了,就是大家熟悉的OkHttp網路請求了,下載到資料之後,再重新new一個Response物件返回。just so so。。。。

現在我們再回到BitmapHunter的run方法中,當成功獲取到bitmap之後,接下來呼叫dispatcher.dispatchComplete(this);傳送一條訊息:

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

又是Handler,再找:
case HUNTER_COMPLETE: {
          BitmapHunter hunter = (BitmapHunter) msg.obj;
          dispatcher.performComplete(hunter);
          break;
        }

這裡又呼叫了dispatcher.performComplete方法,點選去看看:
  void performComplete(BitmapHunter hunter) {
    if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
      cache.set(hunter.getKey(), hunter.getResult());
    }
    hunterMap.remove(hunter.getKey());
    batch(hunter);
    if (hunter.getPicasso().loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");
    }
  }

首先判斷了是否該將Bitmap寫入到記憶體快取中,需要的話就寫入,然後是batch方法:
private void batch(BitmapHunter hunter) {
    if (hunter.isCancelled()) {
      return;
    }
    batch.add(hunter);
    if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
      handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
    }
  }

首先判斷如果hunter已經被取消,則直接返回,否則將hunter加入到batch中,然後判斷Handler中是否有一條HUNTER_DELAY_NEXT_BATCH訊息,沒有的話就發一條,OK,發一條之後,我們來找到相關的case:
case HUNTER_DELAY_NEXT_BATCH: {
          dispatcher.performBatchComplete();
          break;
        }

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

在這裡將batch存入到一個新的List集合中,然後mainThreadHandler又傳送一條訊息,這個mainThreadHandler是什麼鬼?不知道大家是否還記得在build方法中我們建立Dispatch例項的時候傳入了一個Handler,就是那個在主執行緒中建立的Handler,在Picasso那個類裡邊,我們找到了HUNTER_BATCH_COMPLETE這個case:
case HUNTER_BATCH_COMPLETE: {
          @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
          //noinspection ForLoopReplaceableByForEach
          for (int i = 0, n = batch.size(); i < n; i++) {
            BitmapHunter hunter = batch.get(i);
            hunter.picasso.complete(hunter);
          }
          break;
        }

這個case中我們來一條一條的處理batch中的訊息,交給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);
    }
  }

在這裡,14行我們拿到Bitmap,17行去派發Action,如果有合併的Action則在25行進行派發,我們來看看這個派發操作:
  private void deliverAction(Bitmap result, LoadedFrom from, Action action) {
    if (action.isCancelled()) {
      return;
    }
    if (!action.willReplay()) {
      targetToAction.remove(action.getTarget());
    }
    if (result != null) {
      if (from == null) {
        throw new AssertionError("LoadedFrom cannot be null.");
      }
      action.complete(result, from);
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);
      }
    } else {
      action.error();
      if (loggingEnabled) {
        log(OWNER_MAIN, VERB_ERRORED, action.request.logId());
      }
    }
  }
第8行,如果Bitmap不為空,則會執行第12行,呼叫action的complete方法,Action是我們在into方法中建立的,當時new了一個ImageViewAction,所以我們去找ImageViewAction的complete方法:
  @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
    if (result == null) {
      throw new AssertionError(
          String.format("Attempted to complete action with no result!\n%s", this));
    }

    ImageView target = this.target.get();
    if (target == null) {
      return;
    }

    Context context = picasso.context;
    boolean indicatorsEnabled = picasso.indicatorsEnabled;
    PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

    if (callback != null) {
      callback.onSuccess();
    }
  }

獲取到所有資訊之後,然後呼叫PicassoDrawable的setBitmap方法:
  static void setBitmap(ImageView target, Context context, Bitmap bitmap,
      Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
    Drawable placeholder = target.getDrawable();
    if (placeholder instanceof AnimationDrawable) {
      ((AnimationDrawable) placeholder).stop();
    }
    PicassoDrawable drawable =
        new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
    target.setImageDrawable(drawable);
  }

終於看到了給target設定圖片的程式碼了,這裡的程式碼都很簡單,不多說。

OK,這就是對Picasso做了一個簡單介紹,有問題的小夥伴歡迎留言討論。

以上。


參考資料