詳談Picasso圖片快取庫特點及用法

范特西_jay發表於2017-07-30

本文采用研究生論文格式編寫,方便大家閱讀。作者:譚東


摘要

Picasso是美國SQUARE移動支付公司開源的圖片快取載入庫。可以實現圖片下載和快取功能,效率和效能都很不錯。


Square公司官方部落格:http://square.github.io/

Square公司Github地址:https://github.com/square

Square公司Picasso的Wiki地址:http://square.github.io/picasso/

Square公司Picasso的Github地址:https://github.com/square/picasso


1 緒論

Picasso的用法是鏈式呼叫,和Glide用法很像。當然也支援個性化設定,是目前比較流行的圖片快取庫之一。


1.1 Picasso特點

Picasso是全尺寸下載圖片,也就是把要載入的圖片原圖下載快取下來。Picasso預設的快取格式為ARGB_888,相對於RGB_565記憶體佔用高了一半左右,當然圖片質量顯示要高一些。下面看下Picasso的主要特點:

①鏈式呼叫,使用簡單;

②具有一般圖片框架的基礎功能;

③方便的圖片轉換;

④載入過程監聽和錯誤處理;

⑤自動新增磁碟和記憶體二級快取;

⑥支援多種資料來源載入。

Picasso預設不支援Gif圖片載入。Picasso庫很小,類也很少,庫僅118KB大小。最新版目前是2.5.2。


2 Picasso基本用法


2.1 引入Picasso

Jar包下載:https://search.maven.org/remote_content?g=com.squareup.picasso&a=picasso&v=LATEST

Gradle引用:

[java] view plain copy
  1. compile 'com.squareup.picasso:picasso:2.5.2'  
Maven引用:
[java] view plain copy
  1. <dependency>  
  2. <groupId>com.squareup.picasso</groupId>  
  3. <artifactId>picasso</artifactId>  
  4. <version>2.5.2</version>  
  5. </dependency>  


2.2 基本載入圖片

圖2.1 Picasso載入圖片


鏈式呼叫,邏輯上比較清晰。

[java] view plain copy
  1. Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);  

改變圖片尺寸和填充方式:

[java] view plain copy
  1. Picasso.with(context)  
  2.   .load(url)  
  3.   .resize(5050)  
  4.   .centerCrop()  
  5.   .into(imageView)  
圖片個性化轉換,例如把圖片顯示載入成正方形,那麼你就要自己寫具體邏輯。繼承Transformation,很簡單,只需要重新按照你的要求去繪製和建立個Bitmap即可:

[java] view plain copy
  1. public class CropSquareTransformation implements Transformation {  
  2.   @Override public Bitmap transform(Bitmap source) {  
  3.     int size = Math.min(source.getWidth(), source.getHeight());  
  4.     int x = (source.getWidth() - size) / 2;  
  5.     int y = (source.getHeight() - size) / 2;  
  6.     Bitmap result = Bitmap.createBitmap(source, x, y, size, size);  
  7.     if (result != source) {  
  8.       source.recycle();  
  9.     }  
  10.     return result;  
  11.   }  
  12.   
  13.   @Override public String key() { return "square()"; }  
  14. }  
加入載入中圖片和載入出錯圖片:

[java] view plain copy
  1. Picasso.with(context)  
  2.     .load(url)  
  3.     .placeholder(R.drawable.user_placeholder)  
  4.     .error(R.drawable.user_placeholder_error)  
  5.     .into(imageView);  
針對不同來源的圖片載入,如檔案、網路圖片、Assets目錄、Resource、Content Provider等:

[java] view plain copy
  1. Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);  
  2. Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);  
  3. Picasso.with(context).load(new File(...)).into(imageView3);  
圖片的Log日誌模式和顯示圖片的快取來源的標識設定:

[java] view plain copy
  1. Picasso picasso = Picasso.with(this);  
  2.        picasso.setIndicatorsEnabled(true);//藍色:從本地快取讀取的圖片,紅色:從網路載入的圖片,綠色:從記憶體快取載入的圖片  
  3.        picasso.setLoggingEnabled(true);//日誌除錯模式  

圖2.2 Picasso載入圖片來源標識


圖片快取顏色格式配置:

[java] view plain copy
  1. Picasso.with(context).load("http://square.github.io/picasso/static/sample.png").config(Bitmap.Config.RGB_565)  
  2.                 .into(imageView);  
ARGB_8888要比RGB_565佔用記憶體大一半左右,但影象質量差距不大一般。


3 Picasso高階用法

下面的程式碼是實現:

1.自定義Picasso全域性執行緒池
 2.自定義Picasso預設快取目錄
 3.自定義監聽Picasso載入圖片進度,結合OKHTTP


[java] view plain copy
  1. package com.tandong.picassodemo;  
  2.   
  3. import android.app.Application;  
  4.   
  5. import com.tandong.picassodemo.utils.OkHttp3Downloader;  
  6. import com.tandong.picassodemo.utils.ProgressListener;  
  7. import com.squareup.picasso.Picasso;  
  8.   
  9. import java.io.IOException;  
  10. import java.util.concurrent.PriorityBlockingQueue;  
  11. import java.util.concurrent.ThreadPoolExecutor;  
  12. import java.util.concurrent.TimeUnit;  
  13.   
  14. import okhttp3.Cache;  
  15. import okhttp3.Interceptor;  
  16. import okhttp3.OkHttpClient;  
  17. import okhttp3.Response;  
  18.   
  19. /** 
  20.  * Created by Administrator on 2017/4/12. 
  21.  */  
  22.   
  23. /** 
  24.  * 1.自定義Picasso全域性執行緒池 
  25.  * 2.自定義Picasso預設快取目錄 
  26.  * 3.自定義監聽Picasso載入圖片進度,結合OKHTTP 
  27.  */  
  28. public class BaseApplication extends Application {  
  29.     private ThreadPoolExecutor threadPoolExecutor;  
  30.     private int cpu = 0;  
  31.     private Picasso picasso;  
  32.     private OkHttpClient okHttpClient;  
  33.     public static ProgressListener progress;  
  34.   
  35.     @Override  
  36.     public void onCreate() {  
  37.         super.onCreate();  
  38.         initPicasso();  
  39.     }  
  40.   
  41.     private void initPicasso() {  
  42.         okHttpClient = new OkHttpClient.Builder().addNetworkInterceptor(new Interceptor() {  
  43.             @Override  
  44.             public Response intercept(Chain chain) throws IOException {  
  45.                 Response response = chain.proceed(chain.request());  
  46.                 return response.newBuilder().body(new ProgressResponseBody(progress, response.body())).build();  
  47.             }  
  48.         }).cache(new Cache(getExternalCacheDir(), 10 * 1024 * 1024)).build();  
  49.         cpu = Runtime.getRuntime().availableProcessors();  
  50.         threadPoolExecutor = new ThreadPoolExecutor(cpu + 1, cpu * 2 + 11, TimeUnit.MINUTES, new PriorityBlockingQueue<Runnable>());  
  51.         picasso = new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//預設3個執行緒  
  52.         Picasso.setSingletonInstance(picasso);  
  53.     }  
  54.   
  55.     public static void setProgressListener(ProgressListener progressListener) {  
  56.         progress = progressListener;  
  57.     }  
  58. }  

build.gradle引用:

[java] view plain copy
  1. compile 'com.squareup.picasso:picasso:2.5.2'  
  2. compile 'com.squareup.okhttp3:okhttp:3.6.0'  


Okhttp3下載器Okhttp3Downloader:

[java] view plain copy
  1. package com.tandong.picassodemo.utils;  
  2.   
  3. import android.content.Context;  
  4. import android.net.Uri;  
  5. import android.os.StatFs;  
  6.   
  7. import com.squareup.picasso.Downloader;  
  8. import com.squareup.picasso.NetworkPolicy;  
  9.   
  10. import java.io.File;  
  11. import java.io.IOException;  
  12.   
  13. import okhttp3.Cache;  
  14. import okhttp3.CacheControl;  
  15. import okhttp3.Call;  
  16. import okhttp3.OkHttpClient;  
  17. import okhttp3.Request;  
  18. import okhttp3.ResponseBody;  
  19.   
  20. /** 
  21.  * Created by office on 2017/4/10. 
  22.  */  
  23. public final class OkHttp3Downloader implements Downloader {  
  24.     private static final String PICASSO_CACHE = "picasso-cache";  
  25.     private static final int MIN_DISK_CACHE_SIZE = 5 * 1024 * 1024// 5MB  
  26.     private static final int MAX_DISK_CACHE_SIZE = 50 * 1024 * 1024// 50MB  
  27.   
  28.     private static File defaultCacheDir(Context context) {  
  29.         File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);  
  30.         if (!cache.exists()) {  
  31.             //noinspection ResultOfMethodCallIgnored  
  32.             cache.mkdirs();  
  33.         }  
  34.         return cache;  
  35.     }  
  36.   
  37.     private static long calculateDiskCacheSize(File dir) {  
  38.         long size = MIN_DISK_CACHE_SIZE;  
  39.   
  40.         try {  
  41.             StatFs statFs = new StatFs(dir.getAbsolutePath());  
  42.             long available = ((long) statFs.getBlockCount()) * statFs.getBlockSize();  
  43.             // Target 2% of the total space.  
  44.             size = available / 50;  
  45.         } catch (IllegalArgumentException ignored) {  
  46.         }  
  47.   
  48.         // Bound inside min/max size for disk cache.  
  49.         return Math.max(Math.min(size, MAX_DISK_CACHE_SIZE), MIN_DISK_CACHE_SIZE);  
  50.     }  
  51.   
  52.     /** 
  53.      * Creates a {@link Cache} that would have otherwise been created by calling 
  54.      * {@link #OkHttp3Downloader(Context)}. This allows you to build your own {@link OkHttpClient} 
  55.      * while still getting the default disk cache. 
  56.      */  
  57.     public static Cache createDefaultCache(Context context) {  
  58.         File dir = defaultCacheDir(context);  
  59.         return new Cache(dir, calculateDiskCacheSize(dir));  
  60.     }  
  61.   
  62.     private static OkHttpClient createOkHttpClient(File cacheDir, long maxSize) {  
  63.         return new OkHttpClient.Builder()  
  64.                 .cache(new Cache(cacheDir, maxSize))  
  65.                 .build();  
  66.     }  
  67.   
  68.     private final Call.Factory client;  
  69.     private final Cache cache;  
  70.   
  71.     /** 
  72.      * Create new downloader that uses OkHttp. This will install an image cache into your application 
  73.      * cache directory. 
  74.      */  
  75.     public OkHttp3Downloader(Context context) {  
  76.         this(defaultCacheDir(context));  
  77.     }  
  78.   
  79.     /** 
  80.      * Create new downloader that uses OkHttp. This will install an image cache into the specified 
  81.      * directory. 
  82.      * 
  83.      * @param cacheDir The directory in which the cache should be stored 
  84.      */  
  85.     public OkHttp3Downloader(File cacheDir) {  
  86.         this(cacheDir, calculateDiskCacheSize(cacheDir));  
  87.     }  
  88.   
  89.     /** 
  90.      * Create new downloader that uses OkHttp. This will install an image cache into your application 
  91.      * cache directory. 
  92.      * 
  93.      * @param maxSize The size limit for the cache. 
  94.      */  
  95.     public OkHttp3Downloader(final Context context, final long maxSize) {  
  96.         this(defaultCacheDir(context), maxSize);  
  97.     }  
  98.   
  99.     /** 
  100.      * Create new downloader that uses OkHttp. This will install an image cache into the specified 
  101.      * directory. 
  102.      * 
  103.      * @param cacheDir The directory in which the cache should be stored 
  104.      * @param maxSize  The size limit for the cache. 
  105.      */  
  106.     public OkHttp3Downloader(File cacheDir, long maxSize) {  
  107.         this(createOkHttpClient(cacheDir, maxSize));  
  108.     }  
  109.   
  110.     public OkHttp3Downloader(OkHttpClient client) {  
  111.         this.client = client;  
  112.         this.cache = client.cache();  
  113.     }  
  114.   
  115.     public OkHttp3Downloader(Call.Factory client) {  
  116.         this.client = client;  
  117.         this.cache = null;  
  118.     }  
  119.   
  120.     @Override  
  121.     public Response load(Uri uri, int networkPolicy) throws IOException {  
  122.         CacheControl cacheControl = null;  
  123.         if (networkPolicy != 0) {  
  124.             if (NetworkPolicy.isOfflineOnly(networkPolicy)) {  
  125.                 cacheControl = CacheControl.FORCE_CACHE;  
  126.             } else {  
  127.                 CacheControl.Builder builder = new CacheControl.Builder();  
  128.                 if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {  
  129.                     builder.noCache();  
  130.                 }  
  131.                 if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {  
  132.                     builder.noStore();  
  133.                 }  
  134.                 cacheControl = builder.build();  
  135.             }  
  136.         }  
  137.   
  138.         Request.Builder builder = new Request.Builder().url(uri.toString());  
  139.         if (cacheControl != null) {  
  140.             builder.cacheControl(cacheControl);  
  141.         }  
  142.   
  143.         okhttp3.Response response = client.newCall(builder.build()).execute();  
  144.         int responseCode = response.code();  
  145.         if (responseCode >= 300) {  
  146.             response.body().close();  
  147.             throw new ResponseException(responseCode + " " + response.message(), networkPolicy,  
  148.                     responseCode);  
  149.         }  
  150.   
  151.         boolean fromCache = response.cacheResponse() != null;  
  152.   
  153.         ResponseBody responseBody = response.body();  
  154.         return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());  
  155.     }  
  156.   
  157.     @Override  
  158.     public void shutdown() {  
  159.         if (cache != null) {  
  160.             try {  
  161.                 cache.close();  
  162.             } catch (IOException ignored) {  
  163.             }  
  164.         }  
  165.     }  
  166. }  
官方地址:https://github.com/JakeWharton/picasso2-okhttp3-downloader/blob/master/src/main/java/com/jakewharton/picasso/OkHttp3Downloader.java

自定義ResponseBody,實現對資料下載進度的監聽處理:

[java] view plain copy
  1. package com.tandong.picassodemo;  
  2.   
  3. import android.util.Log;  
  4.   
  5. import com.tandong.picassodemo.utils.ProgressListener;  
  6.   
  7. import java.io.IOException;  
  8.   
  9. import okhttp3.MediaType;  
  10. import okhttp3.ResponseBody;  
  11. import okio.Buffer;  
  12. import okio.BufferedSource;  
  13. import okio.ForwardingSource;  
  14. import okio.Okio;  
  15. import okio.Source;  
  16.   
  17. /** 
  18.  * Created by Administrator on 2017/4/14. 
  19.  */  
  20. public class ProgressResponseBody extends ResponseBody {  
  21.     private ResponseBody responseBody;  
  22.     private BufferedSource bufferedSource;  
  23.     private ProgressListener progressListener;  
  24.   
  25.     public ProgressResponseBody(ProgressListener progressListener,ResponseBody responseBody) {  
  26.         this.responseBody = responseBody;  
  27.         this.progressListener=progressListener;  
  28.     }  
  29.   
  30.     @Override  
  31.     public MediaType contentType() {  
  32.         return responseBody.contentType();  
  33.     }  
  34.   
  35.     @Override  
  36.     public long contentLength() {  
  37.         return responseBody.contentLength();  
  38.     }  
  39.   
  40.     @Override  
  41.     public BufferedSource source() {  
  42.         if (bufferedSource == null) {  
  43.             bufferedSource = Okio.buffer(source(responseBody.source()));  
  44.         }  
  45.         return bufferedSource;  
  46.     }  
  47.   
  48.     private Source source(Source source) {  
  49.         ForwardingSource forwardingSource = new ForwardingSource(source) {  
  50.             long totalBytesRead = 0L;  
  51.   
  52.             @Override  
  53.             public long read(Buffer sink, long byteCount) throws IOException {  
  54.                 long bytesRead = super.read(sink, byteCount);  
  55.                 totalBytesRead += bytesRead != -1 ? bytesRead : 0;  
  56.                 progressListener.update((int) ((100 * totalBytesRead) / responseBody.contentLength()));  
  57.                 Log.i("info""進度:" + (int) ((100 * totalBytesRead) / responseBody.contentLength()));  
  58.                 return bytesRead;  
  59.             }  
  60.   
  61.             @Override  
  62.             public void close() throws IOException {  
  63.                 super.close();  
  64.             }  
  65.         };  
  66.         return forwardingSource;  
  67.     }  
  68. }  
進度介面:

[java] view plain copy
  1. package com.tandong.picassodemo.utils;  
  2.   
  3. import android.support.annotation.IntRange;  
  4.   
  5. /** 
  6.  * Created by Administrator on 2017/4/14. 
  7.  */  
  8. public interface ProgressListener {  
  9.     public void update(@IntRange(from = 0, to = 100int progress);  
  10. }  
自定義帶進度的ImageView:

[java] view plain copy
  1. package com.tandong.picassodemo.view;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.graphics.RectF;  
  8. import android.support.annotation.IntRange;  
  9. import android.support.v4.view.ViewCompat;  
  10. import android.util.AttributeSet;  
  11. import android.widget.ImageView;  
  12.   
  13. /** 
  14.  * Created by office on 2017/4/10. 
  15.  */  
  16. public class PicassoImageView extends ImageView {  
  17.     private final int MAX_PROGRESS = 100;  
  18.     private Paint mArcPaint;  
  19.     private RectF mBound;  
  20.     private Paint mCirclePaint;  
  21.     private int mProgress = 0;  
  22.   
  23.     public PicassoImageView(Context context) {  
  24.         this(context, null0);  
  25.     }  
  26.   
  27.     public PicassoImageView(Context context, AttributeSet attrs) {  
  28.         this(context, attrs, 0);  
  29.     }  
  30.   
  31.     public PicassoImageView(Context context, AttributeSet attrs, int defStyleAttr) {  
  32.         super(context, attrs, defStyleAttr);  
  33.         init();  
  34.     }  
  35.   
  36.     private void init() {  
  37.         mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  38.         mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);  
  39.         mArcPaint.setStrokeWidth(dpToPixel(0.1f, getContext()));  
  40.         mArcPaint.setColor(Color.argb(1200xff0xff0xff));  
  41.         mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  42.         mCirclePaint.setStyle(Paint.Style.STROKE);  
  43.         mCirclePaint.setStrokeWidth(dpToPixel(2, getContext()));  
  44.         mCirclePaint.setColor(Color.argb(1200xff0xff0xff));  
  45.         mBound = new RectF();  
  46.     }  
  47.   
  48.     public void setProgress(@IntRange(from = 0, to = MAX_PROGRESS) int mProgress) {  
  49.         this.mProgress = mProgress;  
  50.         ViewCompat.postInvalidateOnAnimation(this);  
  51.     }  
  52.   
  53.     @Override  
  54.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  55.         super.onSizeChanged(w, h, oldw, oldh);  
  56.         int min = Math.min(w, h);  
  57.         int max = w + h - min;  
  58.         int r = min / 5;  
  59.         //set up a square in the imageView  
  60.         //設定了在圖片中間的一個小圓  
  61.         mBound.set(max / 2 - r, min / 2 - r, max / 2 + r, min / 2 + r);  
  62.     }  
  63.   
  64.     @Override  
  65.     protected void onDraw(Canvas canvas) {  
  66.         super.onDraw(canvas);  
  67.         //畫進度條, Paint是畫筆,詳見原始碼  
  68.         if (mProgress != MAX_PROGRESS && mProgress != 0) {  
  69.             float mAngle = mProgress * 360f / MAX_PROGRESS;  
  70.             //畫扇型  
  71.             canvas.drawArc(mBound, 270, mAngle, true, mArcPaint);  
  72.             //畫圓  
  73.             canvas.drawCircle(mBound.centerX(), mBound.centerY(), mBound.height() / 2, mCirclePaint);  
  74.         }  
  75.     }  
  76.   
  77.     private static float scale;  
  78.   
  79.     public static int dpToPixel(float dp, Context context) {  
  80.         if (scale == 0) {  
  81.             scale = context.getResources().getDisplayMetrics().density;  
  82.         }  
  83.         return (int) (dp * scale);  
  84.     }  
  85. }  


Picasso原始碼原理解析:

先看流程圖:



Picasso的原始碼很小,類也很少,相對簡單些。

我們主要關注以下標紅的幾個類。


不是太想寫太多的文字,那就看原始碼分析吧。

先看構造單例模式的Piasso例項:

[java] view plain copy
  1. /** 
  2.          * with方式 
  3.          */  
  4.         Picasso.with(this).load("http://square.github.io/picasso/static/sample.png").centerCrop().into(imageView);  
  5.         /** 
  6.          * new with方式 
  7.          */  
  8.         Picasso picasso = Picasso.with(this);  
  9.         /** 
  10.          * builder方式 
  11.          */  
  12.         picasso = new Picasso.Builder(this).executor(threadPoolExecutor).downloader(new OkHttp3Downloader(okHttpClient)).build();//預設3個執行緒  
  13.         Picasso.setSingletonInstance(picasso);  

這幾種方式通過檢視,都是通過一個方式實現的構造,那就是builder方式。

我們按照這句最基本的方式進行原始碼分析。

[java] view plain copy
  1. Picasso.with(this).load("http://square.github.io/picasso/static/sample.png").centerCrop().into(imageView);  

點選with方法進去。

[java] view plain copy
  1. public static Picasso with(Context context) {  
  2.     if (singleton == null) {  
  3.       synchronized (Picasso.class) {  
  4.         if (singleton == null) {  
  5.           singleton = new Builder(context).build();  
  6.         }  
  7.       }  
  8.     }  
  9.     return singleton;  
  10.   }  
  11.   
  12.   
  13.     /** Create the {@link Picasso} instance. */  
  14.     public Picasso build() {  
  15.       Context context = this.context;  
  16.       //是否自定義下載器,無的話使用預設下載器  
  17.       if (downloader == null) {  
  18.         downloader = Utils.createDefaultDownloader(context);  
  19.       }  
  20.       //是否自定義快取方式,無的話使用預設LruCache  
  21.       if (cache == null) {  
  22.         cache = new LruCache(context);  
  23.       }  
  24.       //是否自定義執行緒池,無的話使用預設執行緒池  
  25.       if (service == null) {  
  26.         service = new PicassoExecutorService();  
  27.       }  
  28.       //是否自定義轉換器,無的話使用預設轉換器  
  29.       if (transformer == null) {  
  30.         transformer = RequestTransformer.IDENTITY;  
  31.       }  
  32.       //統計器,統計快取的命中率  
  33.       Stats stats = new Stats(cache);  
  34.       //建立Dispather,分發排程器  
  35.       Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  
  36.       //建立Picasso例項  
  37.       return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,  
  38.           defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  
  39.     }  
  40.   }  

可以看出都是通過builder方式例項化的Picasso類。這裡例項化了Dispatcher和Picasso。

接下來點選load方法進去:

[java] view plain copy
  1. /** 
  2.   * Start an image request using the specified path. This is a convenience method for calling 
  3.   * {@link #load(Uri)}. 
  4.   * <p> 
  5.   * This path may be a remote URL, file resource (prefixed with {@code file:}), content resource 
  6.   * (prefixed with {@code content:}), or android resource (prefixed with {@code 
  7.   * android.resource:}. 
  8.   * <p> 
  9.   * Passing {@code null} as a {@code path} will not trigger any request but will set a 
  10.   * placeholder, if one is specified. 
  11.   * 
  12.   * @see #load(Uri) 
  13.   * @see #load(File) 
  14.   * @see #load(int) 
  15.   * @throws IllegalArgumentException if {@code path} is empty or blank string. 
  16.   */  
  17.  public RequestCreator load(String path) {  
  18.    if (path == null) {  
  19.      return new RequestCreator(thisnull0);  
  20.    }  
  21.    if (path.trim().length() == 0) {  
  22.      throw new IllegalArgumentException("Path must not be empty.");  
  23.    }  
  24.    return load(Uri.parse(path));  
  25.  }  
  26.   
  27. /** 
  28.   * Start an image request using the specified URI. 
  29.   * <p> 
  30.   * Passing {@code null} as a {@code uri} will not trigger any request but will set a placeholder, 
  31.   * if one is specified. 
  32.   * 
  33.   * @see #load(File) 
  34.   * @see #load(String) 
  35.   * @see #load(int) 
  36.   */  
  37.  public RequestCreator load(Uri uri) {  
  38.    return new RequestCreator(this, uri, 0);  
  39.  }  
  40.   
  41.  RequestCreator(Picasso picasso, Uri uri, int resourceId) {  
  42.    if (picasso.shutdown) {  
  43.      throw new IllegalStateException(  
  44.          "Picasso instance already shut down. Cannot submit new requests.");  
  45.    }  
  46.    this.picasso = picasso;  
  47.    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);  
  48.  }  
可以看出,這裡例項化了一個RequestCreator,我們看下這個原始碼類幹嘛的。

[java] view plain copy
  1. /** Fluent API for building an image download request. */  
  2. @SuppressWarnings("UnusedDeclaration"// Public API.  
  3. public class RequestCreator {  
  4.   private static final AtomicInteger nextId = new AtomicInteger();  
  5.   
  6.   private final Picasso picasso;  
  7.   private final Request.Builder data;  
  8.   
  9.   private boolean noFade;  
  10.   private boolean deferred;  
  11.   private boolean setPlaceholder = true;  
  12.   private int placeholderResId;  
  13.   private int errorResId;  
  14.   private int memoryPolicy;  
  15.   private int networkPolicy;  
  16.   private Drawable placeholderDrawable;  
  17.   private Drawable errorDrawable;  
  18.   private Object tag;  
看下基本的宣告,就是進行我們個性化配置儲存的類。

我們接下來點選centerCrop方法進去:

[java] view plain copy
  1. /** 
  2.  * Crops an image inside of the bounds specified by {@link #resize(int, int)} rather than 
  3.  * distorting the aspect ratio. This cropping technique scales the image so that it fills the 
  4.  * requested bounds and then crops the extra. 
  5.  */  
  6. public RequestCreator centerCrop() {  
  7.   data.centerCrop();  
  8.   return this;  
  9. }  

這個配置也是操作我們的RequestCreator。

最後點選into方法。

[java] view plain copy
  1. /** 
  2.  * Asynchronously fulfills the request into the specified {@link ImageView}. 
  3.  * <p> 
  4.  * <em>Note:</em> This method keeps a weak reference to the {@link ImageView} instance and will 
  5.  * automatically support object recycling. 
  6.  */  
  7. public void into(ImageView target) {  
  8.   into(target, null);  
  9. }  
  10. **  
  11.  * Asynchronously fulfills the request into the specified {@link ImageView} and invokes the  
  12.  * target {@link Callback} if it's not {@code null}.  
  13.  * <p>  
  14.  * <em>Note:</em> The {@link Callback} param is a strong reference and will prevent your  
  15.  * {@link android.app.Activity} or {@link android.app.Fragment} from being garbage collected. If  
  16.  * you use this method, it is <b>strongly</b> recommended you invoke an adjacent  
  17.  * {@link Picasso#cancelRequest(android.widget.ImageView)} call to prevent temporary leaking.  
  18.  */  
  19. public void into(ImageView target, Callback callback) {  
  20.   long started = System.nanoTime();  
  21.   checkMain();  
  22.   
  23.   if (target == null) {  
  24.     throw new IllegalArgumentException("Target must not be null.");  
  25.   }  
  26.   
  27.   if (!data.hasImage()) {//清除取消存在請求  
  28.     picasso.cancelRequest(target);  
  29.     if (setPlaceholder) {  
  30.       setPlaceholder(target, getPlaceholderDrawable());  
  31.     }  
  32.     return;  
  33.   }  
  34.   
  35.   if (deferred) {  
  36.     if (data.hasSize()) {  
  37.       throw new IllegalStateException("Fit cannot be used with resize.");  
  38.     }  
  39.     int width = target.getWidth();  
  40.     int height = target.getHeight();  
  41.     if (width == 0 || height == 0) {  
  42.       if (setPlaceholder) {  
  43.         setPlaceholder(target, getPlaceholderDrawable());  
  44.       }  
  45.       picasso.defer(target, new DeferredRequestCreator(this, target, callback));  
  46.       return;  
  47.     }  
  48.     data.resize(width, height);  
  49.   }  
  50.   
  51.   Request request = createRequest(started);//時間戳為Key,建立Request  
  52.   String requestKey = createKey(request);  
  53.   
  54.   if (shouldReadFromMemoryCache(memoryPolicy)) {//記憶體快取檢查  
  55.     Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);  
  56.     if (bitmap != null) {  
  57.       picasso.cancelRequest(target);  
  58.       setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);  
  59.       if (picasso.loggingEnabled) {  
  60.         log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);  
  61.       }  
  62.       if (callback != null) {  
  63.         callback.onSuccess();  
  64.       }  
  65.       return;  
  66.     }  
  67.   }  
  68.   
  69.   if (setPlaceholder) {  
  70.     setPlaceholder(target, getPlaceholderDrawable());  
  71.   }  
  72.   //關鍵,建立Action,這裡用的ImageViewAction  
  73.   Action action =  
  74.       new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,  
  75.           errorDrawable, requestKey, tag, callback, noFade);  
  76.   //把請求封裝成Action,提交分發  
  77.   picasso.enqueueAndSubmit(action);  
  78. }  

[java] view plain copy
  1. void enqueueAndSubmit(Action action) {  
  2.    Object target = action.getTarget();  
  3.    if (target != null && targetToAction.get(target) != action) {  
  4.      // This will also check we are on the main thread.  
  5.      cancelExistingRequest(target);  
  6.      targetToAction.put(target, action);  
  7.    }  
  8.    submit(action);  
  9.  }  
  10. void submit(Action action) {//提交給Dispatcher分發  
  11.    dispatcher.dispatchSubmit(action);  
  12.  }  

[java] view plain copy
  1. void dispatchSubmit(Action action) {  
  2.   handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));  
  3. }  
  4. private static class DispatcherHandler extends Handler {  
  5.   private final Dispatcher dispatcher;  
  6.   
  7.   public DispatcherHandler(Looper looper, Dispatcher dispatcher) {  
  8.     super(looper);  
  9.     this.dispatcher = dispatcher;  
  10.   }  
  11.   
  12.   @Override public void handleMessage(final Message msg) {  
  13.     switch (msg.what) {  
  14.       case REQUEST_SUBMIT: {//提交分發下載請求  
  15.         Action action = (Action) msg.obj;  
  16.         dispatcher.performSubmit(action);  
  17.         break;  
  18.       }  
  19.       case REQUEST_CANCEL: {  
  20.         Action action = (Action) msg.obj;  
  21.         dispatcher.performCancel(action);  
  22.         break;  
  23.       }  
  24.       case TAG_PAUSE: {  
  25.         Object tag = msg.obj;  
  26.         dispatcher.performPauseTag(tag);  
  27.         break;  
  28.       }  
  29.       case TAG_RESUME: {  
  30.         Object tag = msg.obj;  
  31.         dispatcher.performResumeTag(tag);  
  32.         break;  
  33.       }  
  34.       case HUNTER_COMPLETE: {  
  35.         BitmapHunter hunter = (BitmapHunter) msg.obj;  
  36.         dispatcher.performComplete(hunter);  
  37.         break;  
  38.       }  
  39.       case HUNTER_RETRY: {  
  40.         BitmapHunter hunter = (BitmapHunter) msg.obj;  
  41.         dispatcher.performRetry(hunter);  
  42.         break;  
  43.       }  
  44.       case HUNTER_DECODE_FAILED: {  
  45.         BitmapHunter hunter = (BitmapHunter) msg.obj;  
  46.         dispatcher.performError(hunter, false);  
  47.         break;  
  48.       }  
  49.       case HUNTER_DELAY_NEXT_BATCH: {  
  50.         dispatcher.performBatchComplete();  
  51.         break;  
  52.       }  
  53.       case NETWORK_STATE_CHANGE: {  
  54.         NetworkInfo info = (NetworkInfo) msg.obj;  
  55.         dispatcher.performNetworkStateChange(info);  
  56.         break;  
  57.       }  
  58.       case AIRPLANE_MODE_CHANGE: {  
  59.         dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON);  
  60.         break;  
  61.       }  
  62.       default:  
  63.         Picasso.HANDLER.post(new Runnable() {  
  64.           @Override public void run() {  
  65.             throw new AssertionError("Unknown handler message received: " + msg.what);  
  66.           }  
  67.         });  
  68.     }  
  69.   }  
  70. }  

[java] view plain copy
  1.   void performSubmit(Action action) {  
  2.     performSubmit(action, true);  
  3.   }  
  4.   
  5.   void performSubmit(Action action, boolean dismissFailed) {  
  6.     if (pausedTags.contains(action.getTag())) {  
  7.       pausedActions.put(action.getTarget(), action);  
  8.       if (action.getPicasso().loggingEnabled) {  
  9.         log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),  
  10.             "because tag '" + action.getTag() + "' is paused");  
  11.       }  
  12.       return;  
  13.     }  
  14.    //BitmapHunter,核心,它是個執行緒,執行圖片下載編解碼  
  15.     BitmapHunter hunter = hunterMap.get(action.getKey());  
  16.     if (hunter != null) {  
  17.       hunter.attach(action);  
  18.       return;  
  19.     }  
  20.   
  21.     if (service.isShutdown()) {  
  22.       if (action.getPicasso().loggingEnabled) {  
  23.         log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");  
  24.       }  
  25.       return;  
  26.     }  
  27.   
  28.     hunter = forRequest(action.getPicasso(), this, cache, stats, action);  
  29.     hunter.future = service.submit(hunter);//這個service是ExecutorService執行緒池,提交執行下載執行緒  
  30.     hunterMap.put(action.getKey(), hunter);  
  31.     if (dismissFailed) {  
  32.       failedActions.remove(action.getTarget());  
  33.     }  
  34.   
  35.     if (action.getPicasso().loggingEnabled) {  
  36.       log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());  
  37.     }  
  38.   }  

大概看下BitmapHunter原始碼:

[java] view plain copy
  1. /* 
  2.  * Copyright (C) 2013 Square, Inc. 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16. package com.squareup.picasso;  
  17.   
  18. import android.graphics.Bitmap;  
  19. import android.graphics.BitmapFactory;  
  20. import android.graphics.Matrix;  
  21. import android.net.NetworkInfo;  
  22. import java.io.IOException;  
  23. import java.io.InputStream;  
  24. import java.io.PrintWriter;  
  25. import java.io.StringWriter;  
  26. import java.util.ArrayList;  
  27. import java.util.List;  
  28. import java.util.concurrent.Future;  
  29. import java.util.concurrent.atomic.AtomicInteger;  
  30.   
  31. import static com.squareup.picasso.MemoryPolicy.shouldReadFromMemoryCache;  
  32. import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;  
  33. import static com.squareup.picasso.Picasso.Priority;  
  34. import static com.squareup.picasso.Picasso.Priority.LOW;  
  35. import static com.squareup.picasso.Utils.OWNER_HUNTER;  
  36. import static com.squareup.picasso.Utils.VERB_DECODED;  
  37. import static com.squareup.picasso.Utils.VERB_EXECUTING;  
  38. import static com.squareup.picasso.Utils.VERB_JOINED;  
  39. import static com.squareup.picasso.Utils.VERB_REMOVED;  
  40. import static com.squareup.picasso.Utils.VERB_TRANSFORMED;  
  41. import static com.squareup.picasso.Utils.getLogIdsForHunter;  
  42. import static com.squareup.picasso.Utils.log;  
  43.   
  44. class BitmapHunter implements Runnable {  
  45.   /** 
  46.    * Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since 
  47.    * this will only ever happen in background threads we help avoid excessive memory thrashing as 
  48.    * well as potential OOMs. Shamelessly stolen from Volley. 
  49.    */  
  50.   private static final Object DECODE_LOCK = new Object();  
  51.   
  52.   private static final ThreadLocal<StringBuilder> NAME_BUILDER = new ThreadLocal<StringBuilder>() {  
  53.     @Override protected StringBuilder initialValue() {  
  54.       return new StringBuilder(Utils.THREAD_PREFIX);  
  55.     }  
  56.   };  
  57.   
  58.   private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger();  
  59.   
  60.   private static final RequestHandler ERRORING_HANDLER = new RequestHandler() {  
  61.     @Override public boolean canHandleRequest(Request data) {  
  62.       return true;  
  63.     }  
  64.   
  65.     @Override public Result load(Request request, int networkPolicy) throws IOException {  
  66.       throw new IllegalStateException("Unrecognized type of request: " + request);  
  67.     }  
  68.   };  
  69.   
  70.   final int sequence;  
  71.   final Picasso picasso;  
  72.   final Dispatcher dispatcher;  
  73.   final Cache cache;  
  74.   final Stats stats;  
  75.   final String key;  
  76.   final Request data;  
  77.   final int memoryPolicy;  
  78.   int networkPolicy;  
  79.   final RequestHandler requestHandler;  
  80.   
  81.   Action action;  
  82.   List<Action> actions;  
  83.   Bitmap result;  
  84.   Future<?> future;  
  85.   Picasso.LoadedFrom loadedFrom;  
  86.   Exception exception;  
  87.   int exifRotation; // Determined during decoding of original resource.  
  88.   int retryCount;  
  89.   Priority priority;  
  90.   
  91.   BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,  
  92.       RequestHandler requestHandler) {  
  93.     this.sequence = SEQUENCE_GENERATOR.incrementAndGet();  
  94.     this.picasso = picasso;  
  95.     this.dispatcher = dispatcher;  
  96.     this.cache = cache;  
  97.     this.stats = stats;  
  98.     this.action = action;  
  99.     this.key = action.getKey();  
  100.     this.data = action.getRequest();  
  101.     this.priority = action.getPriority();  
  102.     this.memoryPolicy = action.getMemoryPolicy();  
  103.     this.networkPolicy = action.getNetworkPolicy();  
  104.     this.requestHandler = requestHandler;  
  105.     this.retryCount = requestHandler.getRetryCount();  
  106.   }  
它是實現了Runable介面的執行緒,再繼續看Run方法。

[java] view plain copy
  1. @Override public void run() {  
  2.   try {  
  3.     updateThreadName(data);  
  4.   
  5.     if (picasso.loggingEnabled) {  
  6.       log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this));  
  7.     }  
  8.     //這個result是Bitmap,呼叫了hunt方法獲取  
  9.     result = hunt();  
  10.   
  11.     if (result == null) {//分發圖片載入失敗  
  12.       dispatcher.dispatchFailed(this);  
  13.     } else {//分發圖片載入完成  
  14.       dispatcher.dispatchComplete(this);  
  15.     }  
  16.   } catch (Downloader.ResponseException e) {  
  17.     if (!e.localCacheOnly || e.responseCode != 504) {  
  18.       exception = e;  
  19.     }  
  20.     dispatcher.dispatchFailed(this);  
  21.   } catch (NetworkRequestHandler.ContentLengthException e) {  
  22.     exception = e;  
  23.     dispatcher.dispatchRetry(this);  
  24.   } catch (IOException e) {  
  25.     exception = e;  
  26.     dispatcher.dispatchRetry(this);  
  27.   } catch (OutOfMemoryError e) {  
  28.     StringWriter writer = new StringWriter();  
  29.     stats.createSnapshot().dump(new PrintWriter(writer));  
  30.     exception = new RuntimeException(writer.toString(), e);  
  31.     dispatcher.dispatchFailed(this);  
  32.   } catch (Exception e) {  
  33.     exception = e;  
  34.     dispatcher.dispatchFailed(this);  
  35.   } finally {  
  36.     Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);  
  37.   }  
  38. }  
再繼續看hunt方法:

[java] view plain copy
  1. Bitmap hunt() throws IOException {  
  2.    Bitmap bitmap = null;  
  3.    //記憶體快取檢查,檢查了很多次記憶體快取了  
  4.    if (shouldReadFromMemoryCache(memoryPolicy)) {  
  5.      bitmap = cache.get(key);  
  6.      if (bitmap != null) {  
  7.        stats.dispatchCacheHit();  
  8.        loadedFrom = MEMORY;  
  9.        if (picasso.loggingEnabled) {  
  10.          log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");  
  11.        }  
  12.        return bitmap;  
  13.      }  
  14.    }  
  15.   
  16.    data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;  
  17.    RequestHandler.Result result = requestHandler.load(data, networkPolicy);  
  18.    if (result != null) {  
  19.      loadedFrom = result.getLoadedFrom();  
  20.      exifRotation = result.getExifOrientation();  
  21.   
  22.      bitmap = result.getBitmap();  
  23.   
  24.      // If there was no Bitmap then we need to decode it from the stream.  
  25.      if (bitmap == null) {  
  26.        InputStream is = result.getStream();  
  27.        try {//inputstream解碼  
  28.          bitmap = decodeStream(is, data);  
  29.        } finally {  
  30.          Utils.closeQuietly(is);  
  31.        }  
  32.      }  
  33.    }  
  34.   
  35.    if (bitmap != null) {  
  36.      if (picasso.loggingEnabled) {  
  37.        log(OWNER_HUNTER, VERB_DECODED, data.logId());  
  38.      }  
  39.      stats.dispatchBitmapDecoded(bitmap);//解碼命中率統計  
  40.      if (data.needsTransformation() || exifRotation != 0) {  
  41.        synchronized (DECODE_LOCK) {  
  42.          if (data.needsMatrixTransform() || exifRotation != 0) {  
  43.            bitmap = transformResult(data, bitmap, exifRotation);  
  44.            if (picasso.loggingEnabled) {  
  45.              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());  
  46.            }  
  47.          }  
  48.          if (data.hasCustomTransformations()) {  
  49.            bitmap = applyCustomTransformations(data.transformations, bitmap);  
  50.            if (picasso.loggingEnabled) {  
  51.              log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");  
  52.            }  
  53.          }  
  54.        }  
  55.        if (bitmap != null) {  
  56.          stats.dispatchBitmapTransformed(bitmap);  
  57.        }  
  58.      }  
  59.    }  
  60.   
  61.    return bitmap;  
  62.  }  
看decodeStream方法裡的邏輯:

[java] view plain copy
  1. /** 
  2.  * Decode a byte stream into a Bitmap. This method will take into account additional information 
  3.  * about the supplied request in order to do the decoding efficiently (such as through leveraging 
  4.  * {@code inSampleSize}). 
  5.  */  
  6. static Bitmap decodeStream(InputStream stream, Request request) throws IOException {  
  7.   MarkableInputStream markStream = new MarkableInputStream(stream);  
  8.   stream = markStream;  
  9.   
  10.   long mark = markStream.savePosition(65536); // TODO fix this crap.  
  11.   
  12.   final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);  
  13.   final boolean calculateSize = RequestHandler.requiresInSampleSize(options);  
  14.   
  15.   boolean isWebPFile = Utils.isWebPFile(stream);  
  16.   markStream.reset(mark);  
  17.   // When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.  
  18.   // Decode byte array instead  
  19.   if (isWebPFile) {  
  20.     byte[] bytes = Utils.toByteArray(stream);  
  21.     if (calculateSize) {  
  22.       BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);  
  23.       RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,  
  24.           request);  
  25.     }  
  26.     return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);  
  27.   } else {  
  28.     if (calculateSize) {  
  29.       BitmapFactory.decodeStream(stream, null, options);  
  30.       RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,  
  31.           request);  
  32.   
  33.       markStream.reset(mark);  
  34.     }  
  35.     Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);  
  36.     if (bitmap == null) {  
  37.       // Treat null as an IO exception, we will eventually retry.  
  38.       throw new IOException("Failed to decode stream.");  
  39.     }  
  40.     return bitmap;  
  41.   }  
  42. }  
就是對inputSteam轉為Bimap操作。

接下來,看dispatcher派發載入成功訊息的邏輯:

[java] view plain copy
  1. void dispatchComplete(BitmapHunter hunter) {  
  2.   handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));  
  3. }  
[java] view plain copy
  1. case HUNTER_COMPLETE: {//載入完成  
  2.          BitmapHunter hunter = (BitmapHunter) msg.obj;  
  3.          dispatcher.performComplete(hunter);  
  4.          break;  
  5.        }  
[java] view plain copy
  1. void performComplete(BitmapHunter hunter) {  
  2.   if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {  
  3.     cache.set(hunter.getKey(), hunter.getResult());  
  4.   }  
  5.   hunterMap.remove(hunter.getKey());  
  6.   batch(hunter);  
  7.   if (hunter.getPicasso().loggingEnabled) {  
  8.     log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion");  
  9.   }  
  10. }  
  11. private void batch(BitmapHunter hunter) {  
  12.   if (hunter.isCancelled()) {  
  13.     return;  
  14.   }  
  15.   batch.add(hunter);  
  16.   if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {  
  17.     handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);  
  18.   }  
  19. }  
[java] view plain copy
  1. case HUNTER_DELAY_NEXT_BATCH: {//繼續派發next_batch訊息  
  2.          dispatcher.performBatchComplete();  
  3.          break;  
  4.        }  
[java] view plain copy
  1. void performBatchComplete() {  
  2.    List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);  
  3.    batch.clear();  
  4.    mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));//注意這裡的mainThreadHandler是Picasso的Handler  
  5.    logBatch(copy);  
  6.  }  

注意這裡的mainThreadHander是Picasso裡的Handler,我們可以看下Dispatcher的例項化。

[java] view plain copy
  1. Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,  
  2.      Downloader downloader, Cache cache, Stats stats) {  
  3.    this.dispatcherThread = new DispatcherThread();  
  4.    this.dispatcherThread.start();  
  5.    Utils.flushStackLocalLeaks(dispatcherThread.getLooper());  
  6.    this.context = context;  
  7.    this.service = service;  
  8.    this.hunterMap = new LinkedHashMap<String, BitmapHunter>();  
  9.    this.failedActions = new WeakHashMap<Object, Action>();  
  10.    this.pausedActions = new WeakHashMap<Object, Action>();  
  11.    this.pausedTags = new HashSet<Object>();  
  12.    this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);  
  13.    this.downloader = downloader;  
  14.    this.mainThreadHandler = mainThreadHandler;//這個就是Picasso傳進來的mainThreadHandler  
  15.    this.cache = cache;  
  16.    this.stats = stats;  
  17.    this.batch = new ArrayList<BitmapHunter>(4);  
  18.    this.airplaneMode = Utils.isAirplaneModeOn(this.context);  
  19.    this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);  
  20.    this.receiver = new NetworkBroadcastReceiver(this);  
  21.    receiver.register();  
  22.  }  

那我們看下Picasso裡的HUNTER_BATCH_COMPLETE訊息處理:

[java] view plain copy
  1. static final String TAG = "Picasso";  
  2.  static final Handler HANDLER = new Handler(Looper.getMainLooper()) {  
  3.    @Override public void handleMessage(Message msg) {  
  4.      switch (msg.what) {  
  5.        case HUNTER_BATCH_COMPLETE: {  
  6.          @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;  
  7.          //noinspection ForLoopReplaceableByForEach  
  8.          for (int i = 0, n = batch.size(); i < n; i++) {  
  9.            BitmapHunter hunter = batch.get(i);  
  10.            hunter.picasso.complete(hunter);//回撥了complete方法  
  11.          }  
  12.          break;  
  13.        }  
  14.        case REQUEST_GCED: {  
  15.          Action action = (Action) msg.obj;  
  16.          if (action.getPicasso().loggingEnabled) {  
  17.            log(OWNER_MAIN, VERB_CANCELED, action.request.logId(), "target got garbage collected");  
  18.          }  
  19.          action.picasso.cancelExistingRequest(action.getTarget());  
  20.          break;  
  21.        }  
  22.        case REQUEST_BATCH_RESUME:  
  23.          @SuppressWarnings("unchecked") List<Action> batch = (List<Action>) msg.obj;  
  24.          //noinspection ForLoopReplaceableByForEach  
  25.          for (int i = 0, n = batch.size(); i < n; i++) {  
  26.            Action action = batch.get(i);  
  27.            action.picasso.resumeAction(action);  
  28.          }  
  29.          break;  
  30.        default:  
  31.          throw new AssertionError("Unknown handler message received: " + msg.what);  
  32.      }  
  33.    }  
  34.  };  

我們看下Picasso的complete方法:

[java] view plain copy
  1. void complete(BitmapHunter hunter) {  
  2.    Action single = hunter.getAction();//拿到了派發過來的Action  
  3.    List<Action> joined = hunter.getActions();  
  4.   
  5.    boolean hasMultiple = joined != null && !joined.isEmpty();  
  6.    boolean shouldDeliver = single != null || hasMultiple;  
  7.   
  8.    if (!shouldDeliver) {  
  9.      return;  
  10.    }  
  11.   
  12.    Uri uri = hunter.getData().uri;  
  13.    Exception exception = hunter.getException();  
  14.    Bitmap result = hunter.getResult();//拿到了Bitmap  
  15.    LoadedFrom from = hunter.getLoadedFrom();  
  16.   
  17.    if (single != null) {  
  18.      deliverAction(result, from, single);//執行deliverAction方法  
  19.    }  
  20.   
  21.    if (hasMultiple) {  
  22.      //noinspection ForLoopReplaceableByForEach  
  23.      for (int i = 0, n = joined.size(); i < n; i++) {  
  24.        Action join = joined.get(i);  
  25.        deliverAction(result, from, join);  
  26.      }  
  27.    }  
  28.   
  29.    if (listener != null && exception != null) {  
  30.      listener.onImageLoadFailed(this, uri, exception);  
  31.    }  
  32.  }  
我們看下deliverAction方法:

[java] view plain copy
  1. private void deliverAction(Bitmap result, LoadedFrom from, Action action) {  
  2.    if (action.isCancelled()) {  
  3.      return;  
  4.    }  
  5.    if (!action.willReplay()) {  
  6.      targetToAction.remove(action.getTarget());  
  7.    }  
  8.    if (result != null) {  
  9.      if (from == null) {  
  10.        throw new AssertionError("LoadedFrom cannot be null.");  
  11.      }  
  12.      action.complete(result, from);//回撥了action的complete方法  
  13.      if (loggingEnabled) {  
  14.        log(OWNER_MAIN, VERB_COMPLETED, action.request.logId(), "from " + from);  
  15.      }  
  16.    } else {  
  17.      action.error();  
  18.      if (loggingEnabled) {  
  19.        log(OWNER_MAIN, VERB_ERRORED, action.request.logId());  
  20.      }  
  21.    }  
  22.  }  
這裡注意下,我們之前用的是Action裡的ImageViewAction類,我們看下ImageViewAction原始碼和它的complete類裡的處理邏輯:

[java] view plain copy
  1. /* 
  2.  * Copyright (C) 2013 Square, Inc. 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16. package com.squareup.picasso;  
  17.   
  18. import android.content.Context;  
  19. import android.graphics.Bitmap;  
  20. import android.graphics.drawable.Drawable;  
  21. import android.widget.ImageView;  
  22.   
  23. class ImageViewAction extends Action<ImageView> {  
  24.   
  25.   Callback callback;  
  26.   
  27.   ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,  
  28.       int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,  
  29.       Callback callback, boolean noFade) {  
  30.     super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,  
  31.         tag, noFade);  
  32.     this.callback = callback;  
  33.   }  
  34.   //圖片載入完成回撥  
  35.   @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {  
  36.     if (result == null) {  
  37.       throw new AssertionError(  
  38.           String.format("Attempted to complete action with no result!\n%s"this));  
  39.     }  
  40.   
  41.     ImageView target = this.target.get();  
  42.     if (target == null) {  
  43.       return;  
  44.     }  
  45.   
  46.     Context context = picasso.context;  
  47.     boolean indicatorsEnabled = picasso.indicatorsEnabled;  
  48.     PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);//設定bitmap給PicassoDrawable  
  49.   
  50.     if (callback != null) {  
  51.       callback.onSuccess();  
  52.     }  
  53.   }  
  54.   
  55.   @Override public void error() {  
  56.     ImageView target = this.target.get();  
  57.     if (target == null) {  
  58.       return;  
  59.     }  
  60.     if (errorResId != 0) {  
  61.       target.setImageResource(errorResId);  
  62.     } else if (errorDrawable != null) {  
  63.       target.setImageDrawable(errorDrawable);  
  64.     }  
  65.   
  66.     if (callback != null) {  
  67.       callback.onError();  
  68.     }  
  69.   }  
  70.   
  71.   @Override void cancel() {  
  72.     super.cancel();  
  73.     if (callback != null) {  
  74.       callback = null;  
  75.     }  
  76.   }  
  77. }  
我們簡單看下PicassoDrawable原始碼和它的setBitmap方法:

[java] view plain copy
  1. /* 
  2.  * Copyright (C) 2013 Square, Inc. 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16. package com.squareup.picasso;  
  17.   
  18. import android.content.Context;  
  19. import android.graphics.Bitmap;  
  20. import android.graphics.Canvas;  
  21. import android.graphics.ColorFilter;  
  22. import android.graphics.Paint;  
  23. import android.graphics.Path;  
  24. import android.graphics.Point;  
  25. import android.graphics.Rect;  
  26. import android.graphics.drawable.AnimationDrawable;  
  27. import android.graphics.drawable.BitmapDrawable;  
  28. import android.graphics.drawable.Drawable;  
  29. import android.os.Build;  
  30. import android.os.SystemClock;  
  31. import android.widget.ImageView;  
  32.   
  33. import static android.graphics.Color.WHITE;  
  34. import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;  
  35.   
  36. final class PicassoDrawable extends BitmapDrawable {//繼承自BitmapDrawable  
  37.   // Only accessed from main thread.  
  38.   private static final Paint DEBUG_PAINT = new Paint();  
  39.   private static final float FADE_DURATION = 200f; //ms  
  40.   
  41.   /** 
  42.    * Create or update the drawable on the target {@link ImageView} to display the supplied bitmap 
  43.    * image. 
  44.    */  
  45.   static void setBitmap(ImageView target, Context context, Bitmap bitmap,  
  46.       Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {  
  47.     Drawable placeholder = target.getDrawable();  
  48.     if (placeholder instanceof AnimationDrawable) {  
  49.       ((AnimationDrawable) placeholder).stop();  
  50.     }  
  51.     PicassoDrawable drawable =  
  52.         new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);  
  53.     target.setImageDrawable(drawable);//呼叫ImageView的setImageDrawable方法將轉換後的drawable載入進去顯示  
  54.   }  
  55.   
  56.   /** 
  57.    * Create or update the drawable on the target {@link ImageView} to display the supplied 
  58.    * placeholder image. 
  59.    */  
  60.   static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {  
  61.     target.setImageDrawable(placeholderDrawable);  
  62.     if (target.getDrawable() instanceof AnimationDrawable) {  
  63.       ((AnimationDrawable) target.getDrawable()).start();  
  64.     }  
  65.   }  
  66.   
  67.   private final boolean debugging;  
  68.   private final float density;  
  69.   private final Picasso.LoadedFrom loadedFrom;  
  70.   
  71.   Drawable placeholder;  
  72.   
  73.   long startTimeMillis;  
  74.   boolean animating;  
  75.   int alpha = 0xFF;  
  76.   
  77.   PicassoDrawable(Context context, Bitmap bitmap, Drawable placeholder,  
  78.       Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {  
  79.     super(context.getResources(), bitmap);  
  80.   
  81.     this.debugging = debugging;  
  82.     this.density = context.getResources().getDisplayMetrics().density;  
  83.   
  84.     this.loadedFrom = loadedFrom;  
  85.   
  86.     boolean fade = loadedFrom != MEMORY && !noFade;  
  87.     if (fade) {  
  88.       this.placeholder = placeholder;  
  89.       animating = true;  
  90.       startTimeMillis = SystemClock.uptimeMillis();  
  91.     }  
  92.   }  
  93.   
  94.   @Override public void draw(Canvas canvas) {  
  95.     if (!animating) {  
  96.       super.draw(canvas);  
  97.     } else {  
  98.       float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;  
  99.       if (normalized >= 1f) {  
  100.         animating = false;  
  101.         placeholder = null;  
  102.         super.draw(canvas);  
  103.       } else {  
  104.         if (placeholder != null) {  
  105.           placeholder.draw(canvas);  
  106.         }  
  107.   
  108.         int partialAlpha = (int) (alpha * normalized);  
  109.         super.setAlpha(partialAlpha);  
  110.         super.draw(canvas);  
  111.         super.setAlpha(alpha);  
  112.         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {  
  113.           invalidateSelf();  
  114.         }  
  115.       }  
  116.     }  
  117.   
  118.     if (debugging) {  
  119.       drawDebugIndicator(canvas);  
  120.     }  
  121.   }  
  122.   
  123.   @Override public void setAlpha(int alpha) {  
  124.     this.alpha = alpha;  
  125.     if (placeholder != null) {  
  126.       placeholder.setAlpha(alpha);  
  127.     }  
  128.     super.setAlpha(alpha);  
  129.   }  
  130.   
  131.   @Override public void setColorFilter(ColorFilter cf) {  
  132.     if (placeholder != null) {  
  133.       placeholder.setColorFilter(cf);  
  134.     }  
  135.     super.setColorFilter(cf);  
  136.   }  
  137.   
  138.   @Override protected void onBoundsChange(Rect bounds) {  
  139.     if (placeholder != null) {  
  140.       placeholder.setBounds(bounds);  
  141.     }  
  142.     super.onBoundsChange(bounds);  
  143.   }  
  144.   
  145.   private void drawDebugIndicator(Canvas canvas) {  
  146.     DEBUG_PAINT.setColor(WHITE);  
  147.     Path path = getTrianglePath(new Point(00), (int) (16 * density));  
  148.     canvas.drawPath(path, DEBUG_PAINT);  
  149.   
  150.     DEBUG_PAINT.setColor(loadedFrom.debugColor);  
  151.     path = getTrianglePath(new Point(00), (int) (15 * density));  
  152.     canvas.drawPath(path, DEBUG_PAINT);  
  153.   }  
  154.   
  155.   private static Path getTrianglePath(Point p1, int width) {  
  156.     Point p2 = new Point(p1.x + width, p1.y);  
  157.     Point p3 = new Point(p1.x, p1.y + width);  
  158.   
  159.     Path path = new Path();  
  160.     path.moveTo(p1.x, p1.y);  
  161.     path.lineTo(p2.x, p2.y);  
  162.     path.lineTo(p3.x, p3.y);  
  163.   
  164.     return path;  
  165.   }  
  166. }  
這樣,整個Picasso就實現了我們的圖片請求載入顯示。

如果你覺得本文太長,可以單獨檢視Picasso原始碼原理分析一文:

http://blog.csdn.net/jay100500/article/details/71055229



參考文獻

[1]Picasso官方簡易Wiki.Picasso[OL].2013.http://square.github.io/picasso/

獨創性宣告

本人宣告所呈交的學術研究文章是本人自己和在他人指導下進行的研究工作及取得的研究成果。除了文中特別加以標註和致謝的地方外,文章中不包含他人已經發表或撰寫過的研究成果,也不包含為獲得其他教育機構的學位或證照而使用過的材料。與我一同工作的同志對本研究所做的任何貢獻均已在論文中作了明確的說明。


學術文章作者簽名:譚東              簽字日期: 2017年 4月 25日

學術文章版權使用授權書

本學術文章作者完全瞭解有關保留、使用學術文章的規定,有權保留並向國家有關部門或機構送交文章的影印件和磁碟,允許文章被查閱和借閱。本人授權後可以將學術文章的全部或部分內容編入有關資料庫進行檢索,可以採用影印、縮印或掃描等複製手段儲存、彙編學術文章。


相關文章