Picasso使用詳解及源碼解析

原文地址:http://blog.csdn.net/u012702547/article/details/52273918


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

1.基本使用

Picasso加載一張網絡圖片,最簡單的一行代碼就搞定:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);  

如果你想對這張圖片進行剪裁,可以使用resize方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  2.                 .resize(200,200)  
  3.                 .into(iv);  

注意這裏的200表示200px,如果你想在resize時指定dp,可以使用如下方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  2.                 .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)  
  3.                 .into(iv);  

在dimen文件中定義寬高即可:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. <dimen name="iv_width">200dp</dimen>  
  2.     <dimen name="iv_height">200dp</dimen>  

其實我們看看resizeDimen的源碼就知道它是怎麼設置dp了:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. /** Resize the image to the specified dimension size. */  
  2. public RequestCreator resizeDimen(int targetWidthResId, int targetHeightResId) {  
  3.   Resources resources = picasso.context.getResources();  
  4.   int targetWidth = resources.getDimensionPixelSize(targetWidthResId);  
  5.   int targetHeight = resources.getDimensionPixelSize(targetHeightResId);  
  6.   return resize(targetWidth, targetHeight);  
  7. }  

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

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

使用fit:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  2.                 .fit()  
  3.                 .into(iv);  

使用centerCrop:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  2.         .resizeDimen(R.dimen.iv_width,R.dimen.iv_height)  
  3.         .centerCrop()  
  4.         .into(iv);  

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  2.                 //佔位圖,圖片加載出來之前顯示的默認圖片  
  3.                 .placeholder(R.mipmap.ic_launcher)  
  4.                 //錯誤圖,圖片加載出錯時顯示的圖片  
  5.                 .error(R.mipmap.ic_launcher)  
  6.                 .into(iv);  

很多時候,我們可能想顯示一個用戶圖像,但是這個用戶圖像是個圓形圖片,這個用Picasso該怎麼實現呢?首先定義一個Transformation,在transform方法中對圖片進行二次處理,包括剪裁重新處理等等,那我這裏想把原圖變爲一個圓形圖,就可以按下面的寫法來:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Transformation transformation = new Transformation() {  
  2.             @Override  
  3.             public Bitmap transform(Bitmap source) {  
  4.                 int width = source.getWidth();  
  5.                 int height = source.getHeight();  
  6.                 int size = Math.min(width, height);  
  7.                 Bitmap blankBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);  
  8.                 Canvas canvas = new Canvas(blankBitmap);  
  9.                 Paint paint = new Paint();  
  10.                 paint.setAntiAlias(true);  
  11.                 canvas.drawCircle(size / 2, size / 2, size / 2, paint);  
  12.                 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));  
  13.                 canvas.drawBitmap(source, 00, paint);  
  14.                 if (source != null && !source.isRecycled()) {  
  15.                     source.recycle();  
  16.                 }  
  17.                 return blankBitmap;  
  18.             }  
  19.   
  20.             @Override  
  21.             public String key() {  
  22.                 return "squareup";  
  23.             }  
  24.         };  

paint的setXfermode表示最終顯示的圖形取所繪製圖形的交集,我這裏先繪製了圓形,又繪製了一個矩形的Bitmap,圓形沒有Bitmap大,所以交集肯定是圓形,所以最終顯示結果就爲圓形,在加載圖片的時候可以通過transform屬性來使用自定義的這個transformation,如下:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso.with(this).load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  2.                .transform(transformation)  
  3.                .into(iv);  

最終顯示結果如下:

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

Picasso還可以通過開啓指示器,讓你看到這個圖片是從內存加載來的還是從SD卡加載來的還是從網絡加載來的,設置方式如下:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso picasso = Picasso.with(this);  
  2.        //開啓指示器  
  3.        picasso.setIndicatorsEnabled(true);  
  4.        picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  5.                .into(iv);  

開啓之後,圖片的加載效果如下:

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

現在大部分的圖片緩存框架都是支持三級緩存的,在Picasso中,我們也可以手動設置緩存策略,比如說當我們查看一張大圖的時候,可能由於圖片太大,不想將其緩存在內存中,那麼可以自定義緩存策略,如下:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso picasso = Picasso.with(this);  
  2. //開啓指示器  
  3. picasso.setIndicatorsEnabled(true);  
  4. picasso  
  5.         .load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  6.         //第一個參數是指圖片加載時放棄在內存緩存中查找  
  7.         //第二個參數是指圖片加載完不緩存在內存中  
  8.         .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)  
  9.         .into(iv);  

當然,如果你想給圖片加載過程設置一個監聽器也是可以的,如下:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso picasso = Picasso.with(this);  
  2. //開啓指示器  
  3. picasso.setIndicatorsEnabled(true);  
  4. picasso  
  5.         .load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  6.         //第一個參數是指圖片加載時放棄在內存緩存中查找  
  7.         //第二個參數是指圖片加載完不緩存在內存中  
  8.         .memoryPolicy(MemoryPolicy.NO_CACHE, MemoryPolicy.NO_STORE)  
  9.         .into(iv, new Callback() {  
  10.             @Override  
  11.             public void onSuccess() {  
  12.                 Log.d("google_lenve_fb""onSuccess: 圖片加載成功!");  
  13.             }  
  14.   
  15.             @Override  
  16.             public void onError() {  
  17.                 Log.d("google_lenve_fb""onSuccess: 圖片加載失敗!");  
  18.             }  
  19.         });  

在ListView或者RecyclerView中加載圖片時,當列表處於滑動狀態的時候,我們可以停止圖片的加載,當列表停止滾動的時候,我們又可以繼續加載圖片,如下:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Object tag = new Object();  
  2. Picasso with = Picasso.with(this);  
  3. with.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg")  
  4.         .into(iv);  
  5. //暫停加載  
  6. with.pauseTag(tag);  
  7. //恢復加載  
  8. with.resumeTag(tag);  

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

2.自定義緩存位置

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  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. }  
大家看到,with方法返回了一個Picasso的單例,在創建Picasso的過程中,調用了new Builder(context).build()方法,說明Picasso實例創建的代碼在build方法中,那我們再來看看這個build方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public Picasso build() {  
  2.   Context context = this.context;  
  3.   
  4.   if (downloader == null) {  
  5.     downloader = Utils.createDefaultDownloader(context);  
  6.   }  
  7.   if (cache == null) {  
  8.     cache = new LruCache(context);  
  9.   }  
  10.   if (service == null) {  
  11.     service = new PicassoExecutorService();  
  12.   }  
  13.   if (transformer == null) {  
  14.     transformer = RequestTransformer.IDENTITY;  
  15.   }  
  16.   
  17.   Stats stats = new Stats(cache);  
  18.   
  19.   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  
  20.   
  21.   return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,  
  22.       defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  
  23. }  

我們先不急着看build中的其他方法,先來看看downloader這個判斷(如果我使用with方法downloader肯定爲null),如果downloader爲null,則系統會幫我們創建一個默認的downloader,那我們來看看這個默認的downloader是怎麼創建的:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. static Downloader createDefaultDownloader(Context context) {  
  2.   try {  
  3.     Class.forName("com.squareup.okhttp.OkHttpClient");  
  4.     return OkHttpLoaderCreator.create(context);  
  5.   } catch (ClassNotFoundException ignored) {  
  6.   }  
  7.   return new UrlConnectionDownloader(context);  
  8. }  

啊哈,這裏就很明白了,系統通過反射來檢查我們在項目中是否使用了OkHttp,如果使用了,就使用OkHttp來創建一個下載器,否則就使用HttpUrlConnection來創建一個下載器,可是大家注意Class.forName("com.squareup.okhttp.OkHttpClient");這個方法的參數,這是OkHttp3以前的寫法,現在我們都是使用OkHttp3了,OkHttp3的包名就不是這個樣子,而是okhttp3.OkHttpClient,所以即使你在項目中引用了OkHttp3,Picasso還是會把HttpUrlConnection當作下載器來下載圖片的,這個問題估計Picasso會在以後的版本中修正吧!OK,那如果我們想要使用自己的下載器又該怎麼做呢?其實很簡單,首先不使用with這個方法來初始化Picasso,而是使用Builder來初始化,在初始化的過程中傳入自己的下載器,自己的下載器我們可以模仿Picasso裏邊的這個下載器來寫,也可以自定義,我們來看一個Demo:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso picasso = new Picasso.Builder(this)  
  2.                 .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))  
  3.                 .build();  
  4.         Picasso.setSingletonInstance(picasso);  
  5.         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方法,我們來看看這個方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public static void setSingletonInstance(Picasso picasso) {  
  2.   synchronized (Picasso.class) {  
  3.     if (singleton != null) {  
  4.       throw new IllegalStateException("Singleton instance already exists.");  
  5.     }  
  6.     singleton = picasso;  
  7.   }  
  8. }  

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

3.自定義下載線程池

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public Picasso build() {  
  2.   Context context = this.context;  
  3.   
  4.   if (downloader == null) {  
  5.     downloader = Utils.createDefaultDownloader(context);  
  6.   }  
  7.   if (cache == null) {  
  8.     cache = new LruCache(context);  
  9.   }  
  10.   if (service == null) {  
  11.     service = new PicassoExecutorService();  
  12.   }  
  13.   if (transformer == null) {  
  14.     transformer = RequestTransformer.IDENTITY;  
  15.   }  
  16.   
  17.   Stats stats = new Stats(cache);  
  18.   
  19.   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  
  20.   
  21.   return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,  
  22.       defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  
  23. }  

在build方法中還有一個判斷,如果service爲null,則新創建一個PicassoExecutorService,我們來看看這個PicassoExecutorService:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. class PicassoExecutorService extends ThreadPoolExecutor {  
  2.   private static final int DEFAULT_THREAD_COUNT = 3;  
  3.   
  4.   PicassoExecutorService() {  
  5.     super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,  
  6.         new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());  
  7.   }  
  8.   
  9.   ....  
  10.   ....  
  11. }   

大家看到,這個PicassoExecutorService繼承自ThreadPoolExecutor這個線程池,線程池中的核心線程數爲3,線程池的最大線程數也爲3,說明線程池中沒有非核心線程,線程隊列使用了PriorityBlockingQueue,說明所有加載進來的任務都將實現Comparator接口。OK,這是系統默認幫我們創建的線程池,如果你想修改,可以在創建Picasso實例的時候傳入自己的線程池:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. int CPU_COUNT = Runtime.getRuntime().availableProcessors();  
  2. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CPU_COUNT + 1, CPU_COUNT * 2 + 1,  
  3.         1, TimeUnit.MINUTES, new PriorityBlockingQueue<Runnable>());  
  4. Picasso picasso = new Picasso.Builder(this)  
  5.         .executor(threadPoolExecutor)  
  6.         .downloader(new OkHttp3Downloader(this.getExternalCacheDir()))  
  7.         .build();  
  8. Picasso.setSingletonInstance(picasso);  
  9. picasso.load("http://n.sinaimg.cn/translate/20160819/9BpA-fxvcsrn8627957.jpg").into(iv);  

對線程池的創建如果還不瞭解的話,請參考Android開發之線程池使用總結

4.自定義下載進度條

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


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

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public OkHttp3Downloader(OkHttpClient client) {  
  2.     this.client = client;  
  3.     this.cache = client.cache();  
  4. }  

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

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public interface ProgressListener {  
  2.     //定義接口,取值範圍爲0~100  
  3.     public void update(@IntRange(from = 0, to = 100int progress);  
  4. }  
然後定義一個OkHttpClient對象,在定義的過程中給OkHttpClient添加攔截器:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. OkHttpClient client = new OkHttpClient.Builder()  
  2.                 .addNetworkInterceptor(new Interceptor() {  
  3.                     @Override  
  4.                     public Response intercept(Chain chain) throws IOException {  
  5.                         Response response = chain.proceed(chain.request());  
  6.                         return response.newBuilder()  
  7.                                 .body(new MyProgressbarResponseBody(new ProgressListener() {  
  8.                                     @Override  
  9.                                     public void update(@IntRange(from = 0, to = 100final int progress) {  
  10.                                         //更新進度條  
  11.                                         runOnUiThread(new Runnable() {  
  12.                                             @Override  
  13.                                             public void run() {  
  14.                                                 Log.d("google_lenve_fb""run: " + progress);  
  15.                                                 myPb.setSweepAngle(progress * 360f / 100);  
  16.                                             }  
  17.                                         });  
  18.                                     }  
  19.                                 }, response.body()))  
  20.                                 .build();  
  21.                     }  
  22.                 })  
  23.                 //設置緩存位置,Picasso下載的圖片將緩存在這裏  
  24.                 .cache(new Cache(this.getExternalCacheDir(), 10 * 1024 * 1024))  
  25.                 .build();  

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public class MyProgressbarResponseBody extends ResponseBody {  
  2.     private ResponseBody responseBody;  
  3.     private ProgressListener progressListener;  
  4.     private BufferedSource bufferedSource;  
  5.   
  6.     public MyProgressbarResponseBody(ProgressListener progressListener, ResponseBody responseBody) {  
  7.         this.progressListener = progressListener;  
  8.         this.responseBody = responseBody;  
  9.     }  
  10.   
  11.     @Override  
  12.     public MediaType contentType() {  
  13.         return responseBody.contentType();  
  14.     }  
  15.   
  16.     @Override  
  17.     public long contentLength() {  
  18.         return responseBody.contentLength();  
  19.     }  
  20.   
  21.     @Override  
  22.     public BufferedSource source() {  
  23.         if (bufferedSource == null) {  
  24.             bufferedSource = Okio.buffer(source(responseBody.source()));  
  25.         }  
  26.         return bufferedSource;  
  27.     }  
  28.     private Source source(Source source) {  
  29.   
  30.         return new ForwardingSource(source) {  
  31.             long totalBytesRead = 0L;  
  32.   
  33.             @Override  
  34.             public long read(Buffer sink, long byteCount) throws IOException {  
  35.                 long bytesRead = super.read(sink, byteCount);  
  36.                 totalBytesRead += bytesRead != -1 ? bytesRead : 0;  
  37.                 if (progressListener != null) {  
  38.                     progressListener.update(  
  39.                             ((int) ((100 * totalBytesRead) / responseBody.contentLength())));  
  40.                 }  
  41.                 return bytesRead;  
  42.             }  
  43.         };  
  44.     }  
  45. }  

MyProgressbarResponseBody繼承自ResponseBody,並重寫它裏邊的三個方法,分別返回數據類型,數據大小等信息,在source方法中我們來統計當前下載百分比,並且回調監聽器中的接口。最後再來看一眼自定義的ProgressBar,對這個如果還不瞭解,請參考Android自定義View之ProgressBar出場記 

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public class MyProgressBar extends View {  
  2.     /** 
  3.      * View默認的寬 
  4.      */  
  5.     private static final int DEFAULTWIDTH = 200;  
  6.     /** 
  7.      * View默認的高度 
  8.      */  
  9.     private static final int DEFAULTHEIGHT = 200;  
  10.     private Paint sweepPaint;  
  11.     private int padding = 20;  
  12.     /** 
  13.      * 內層實體圓的顏色 
  14.      */  
  15.     private int sweepColor = getResources().getColor(R.color.pbColor);  
  16.     /** 
  17.      * 開始繪製的角度 
  18.      */  
  19.     private int startAngle = -90;  
  20.     /** 
  21.      * 已經繪製的角度 
  22.      */  
  23.     private float sweepAngle = 0;  
  24.   
  25.     public MyProgressBar(Context context) {  
  26.         this(context, null);  
  27.     }  
  28.   
  29.     public MyProgressBar(Context context, AttributeSet attrs) {  
  30.         this(context, attrs, 0);  
  31.     }  
  32.   
  33.     public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {  
  34.         super(context, attrs, defStyleAttr);  
  35.         setAlpha(0.8f);  
  36.         sweepPaint = new Paint();  
  37.         sweepPaint.setColor(sweepColor);  
  38.         sweepPaint.setAntiAlias(true);  
  39.     }  
  40.   
  41.     @Override  
  42.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  43.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  44.         //獲取寬的測量模式  
  45.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  46.         //獲取寬的測量值  
  47.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  48.         //獲取高的測量模式  
  49.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  50.         //獲取高的測量值  
  51.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  52.         switch (widthMode) {  
  53.             case MeasureSpec.EXACTLY:  
  54.                 break;  
  55.             case MeasureSpec.AT_MOST:  
  56.             case MeasureSpec.UNSPECIFIED:  
  57.                 //如果寬爲wrap_content,則給定一個默認值  
  58.                 widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTWIDTH, getResources().getDisplayMetrics());  
  59.                 break;  
  60.         }  
  61.         switch (heightMode) {  
  62.             case MeasureSpec.EXACTLY:  
  63.                 break;  
  64.             case MeasureSpec.AT_MOST:  
  65.             case MeasureSpec.UNSPECIFIED:  
  66.                 heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULTHEIGHT, getResources().getDisplayMetrics());  
  67.                 break;  
  68.         }  
  69.         widthSize = heightSize = Math.min(widthSize, heightSize);  
  70.         //設置測量結果  
  71.         setMeasuredDimension(widthSize, heightSize);  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onDraw(Canvas canvas) {  
  76.         if (sweepAngle != 360 && sweepAngle != 0) {  
  77.             RectF oval = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);  
  78.             Log.d("google_lenve_fb""onDraw: " + sweepAngle);  
  79.             canvas.drawArc(oval, startAngle, sweepAngle, true, sweepPaint);  
  80.         }  
  81.     }  
  82.   
  83.     public void setSweepAngle(float sweepAngle) {  
  84.         this.sweepAngle = sweepAngle;  
  85.         if (Build.VERSION.SDK_INT > 15) {  
  86.             postInvalidateOnAnimation();  
  87.         } else {  
  88.             ViewCompat.postInvalidateOnAnimation(this);  
  89.         }  
  90.     }  
  91. }  

最後,加載一張網絡圖片幾個,注意下載器的創建方式:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso picasso = new Picasso  
  2.            .Builder(this)  
  3.            .downloader(new OkHttp3Downloader(client))  
  4.            .build();  

5.Picasso源碼剖析

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public Picasso build() {  
  2.   Context context = this.context;  
  3.   
  4.   if (downloader == null) {  
  5.     downloader = Utils.createDefaultDownloader(context);  
  6.   }  
  7.   if (cache == null) {  
  8.     cache = new LruCache(context);  
  9.   }  
  10.   if (service == null) {  
  11.     service = new PicassoExecutorService();  
  12.   }  
  13.   if (transformer == null) {  
  14.     transformer = RequestTransformer.IDENTITY;  
  15.   }  
  16.   
  17.   Stats stats = new Stats(cache);  
  18.   
  19.   Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);  
  20.   
  21.   return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,  
  22.       defaultBitmapConfig, indicatorsEnabled, loggingEnabled);  
  23. }  

build方法中前面幾個if判斷我們剛纔已經說過了,這幾個判斷中的變量我們在創建Picasso實例的時候都可以自定義,也可以使用系統默認創建的,我們再來總結一下:

downloader   創建一個下載器

cache 創建圖片的緩存器,默認使用LruCache,這個我們一般不做修改,最多重新配置一下LruCache

service 創建圖片下載的線程池

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

接下來就是創建一個Stats實例,這個stats主要是用來統計緩存,下載數量等數據,一言以蔽之,就是保存圖片的一些狀態信息。再之後,則是創建一個Dispatcher,創建Dispatcher的時候還傳入了一個HANDLER,這個Handler我們在後文再說,dispatcher顧名思義就是分發,事實上dispatcher主要用來任務調度,這個一會再說,最後new一個Picasso實例返回:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,  
  2.     RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,  
  3.     Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {  
  4.   this.context = context;  
  5.   this.dispatcher = dispatcher;  
  6.   this.cache = cache;  
  7.   this.listener = listener;  
  8.   this.requestTransformer = requestTransformer;  
  9.   this.defaultBitmapConfig = defaultBitmapConfig;  
  10.   
  11.   int builtInHandlers = 7// Adjust this as internal handlers are added or removed.  
  12.   int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);  
  13.   List<RequestHandler> allRequestHandlers =  
  14.       new ArrayList<RequestHandler>(builtInHandlers + extraCount);  
  15.   
  16.   // ResourceRequestHandler needs to be the first in the list to avoid  
  17.   // forcing other RequestHandlers to perform null checks on request.uri  
  18.   // to cover the (request.resourceId != 0) case.  
  19.   allRequestHandlers.add(new ResourceRequestHandler(context));  
  20.   if (extraRequestHandlers != null) {  
  21.     allRequestHandlers.addAll(extraRequestHandlers);  
  22.   }  
  23.   allRequestHandlers.add(new ContactsPhotoRequestHandler(context));  
  24.   allRequestHandlers.add(new MediaStoreRequestHandler(context));  
  25.   allRequestHandlers.add(new ContentStreamRequestHandler(context));  
  26.   allRequestHandlers.add(new AssetRequestHandler(context));  
  27.   allRequestHandlers.add(new FileRequestHandler(context));  
  28.   allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));  
  29.   requestHandlers = Collections.unmodifiableList(allRequestHandlers);  
  30.   
  31.   this.stats = stats;  
  32.   this.targetToAction = new WeakHashMap<Object, Action>();  
  33.   this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();  
  34.   this.indicatorsEnabled = indicatorsEnabled;  
  35.   this.loggingEnabled = loggingEnabled;  
  36.   this.referenceQueue = new ReferenceQueue<Object>();  
  37.   this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);  
  38.   this.cleanupThread.start();  
  39. }  

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

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. RequestCreator(Picasso picasso, Uri uri, int resourceId) {  
  2.   if (picasso.shutdown) {  
  3.     throw new IllegalStateException(  
  4.         "Picasso instance already shut down. Cannot submit new requests.");  
  5.   }  
  6.   this.picasso = picasso;  
  7.   this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);  
  8. }  

shutdown屬性是判斷Picasso實例是否已經停止運行,如果已經shutdown則拋異常,否則將我們即將要加載的圖片信息保存在data中,data是一個Request.Builder對象,裏邊保存了我們所有的圖片加載的配置信息,比如你調用了centerCrop方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public RequestCreator centerCrop() {  
  2.   data.centerCrop();  
  3.   return this;  
  4. }  

大家看到這些方法不過都是修改data裏邊的變量,當所有的配置信息都完成之後,接下載就到into方法了,那麼小夥伴們大概也猜到了,真正的圖片加載過程是在into方法中完成的,如下:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public void into(ImageView target, Callback callback) {  
  2.   long started = System.nanoTime();  
  3.   checkMain();  
  4.   
  5.   if (target == null) {  
  6.     throw new IllegalArgumentException("Target must not be null.");  
  7.   }  
  8.   
  9.   if (!data.hasImage()) {  
  10.     picasso.cancelRequest(target);  
  11.     if (setPlaceholder) {  
  12.       setPlaceholder(target, getPlaceholderDrawable());  
  13.     }  
  14.     return;  
  15.   }  
  16.   
  17.   if (deferred) {  
  18.     if (data.hasSize()) {  
  19.       throw new IllegalStateException("Fit cannot be used with resize.");  
  20.     }  
  21.     int width = target.getWidth();  
  22.     int height = target.getHeight();  
  23.     if (width == 0 || height == 0) {  
  24.       if (setPlaceholder) {  
  25.         setPlaceholder(target, getPlaceholderDrawable());  
  26.       }  
  27.       picasso.defer(target, new DeferredRequestCreator(this, target, callback));  
  28.       return;  
  29.     }  
  30.     data.resize(width, height);  
  31.   }  
  32.   
  33.   Request request = createRequest(started);  
  34.   String requestKey = createKey(request);  
  35.   
  36.   if (shouldReadFromMemoryCache(memoryPolicy)) {  
  37.     Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);  
  38.     if (bitmap != null) {  
  39.       picasso.cancelRequest(target);  
  40.       setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);  
  41.       if (picasso.loggingEnabled) {  
  42.         log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);  
  43.       }  
  44.       if (callback != null) {  
  45.         callback.onSuccess();  
  46.       }  
  47.       return;  
  48.     }  
  49.   }  
  50.   
  51.   if (setPlaceholder) {  
  52.     setPlaceholder(target, getPlaceholderDrawable());  
  53.   }  
  54.   
  55.   Action action =  
  56.       new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,  
  57.           errorDrawable, requestKey, tag, callback, noFade);  
  58.   
  59.   picasso.enqueueAndSubmit(action);  
  60. }  

into方法有點長,但是邏輯還是很清晰,我們來看一下,

首先checkMain方法檢查程序是否運行在主線程,接下來target不能爲空,這個不用多說,簡單。data.hasImage表示是否設置了要加載的圖片資源,如果設置了,則返回true,否則返回false。返回false時進入到if判斷中,這個時候首先取消加載,然後如果設置了佔位圖片,就將其顯示出來。接下來進入到if(deferred)的判斷中,deferred這個變量是在哪裏進行初始化的呢?我們來看看這裏:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public RequestCreator fit() {  
  2.   deferred = true;  
  3.   return this;  
  4. }  
  5.   
  6. /** Internal use only. Used by {@link DeferredRequestCreator}. */  
  7. RequestCreator unfit() {  
  8.   deferred = false;  
  9.   return this;  
  10. }  

是在我們調用了fit方法的時候,也就是說,如果我們希望我們的圖片在加載的過程中能夠自由縮放以填滿整個ImageView的話,那麼就會進入到這個分支中,進來之後首先是判斷data.hasSize,我們知道這個是判斷圖片是否有寬高,我們來看看hasSize方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. boolean hasSize() {  
  2.   return targetWidth != 0 || targetHeight != 0;  
  3. }  

那麼targetWidth和targetHeight又是在什麼地方調用的呢?我們不由得想到了resize方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public Builder resize(int targetWidth, int targetHeight) {  
  2.   if (targetWidth < 0) {  
  3.     throw new IllegalArgumentException("Width must be positive number or 0.");  
  4.   }  
  5.   if (targetHeight < 0) {  
  6.     throw new IllegalArgumentException("Height must be positive number or 0.");  
  7.   }  
  8.   if (targetHeight == 0 && targetWidth == 0) {  
  9.     throw new IllegalArgumentException("At least one dimension has to be positive number.");  
  10.   }  
  11.   this.targetWidth = targetWidth;  
  12.   this.targetHeight = targetHeight;  
  13.   return this;  
  14. }  

沒錯,是這裏,那我們在這裏可以得出結論了,如果在加載一張圖片的是否使用了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方法中。接下來我們就來看看這個請求入隊的方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  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. }  

首先獲取action裏邊的target,其實就是我們的ImageView,如果這個ImageView不爲空,並且該ImageView已經有了一個Action,則取消已經存在的請求,然後重新給該target設置Action,完了之後就是submit了,我們來看看這個submit:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. void submit(Action action) {  
  2.     dispatcher.dispatchSubmit(action);  
  3.   }  

咦,dispatcher,大家還記不記得我們是在哪裏初始化的dispatcher呢?沒錯,build方法中,這裏調用了dispatcher的dispatchSubmit方法,點擊去再看:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. void dispatchSubmit(Action action) {  
  2.     handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));  
  3.   }  

哦,原來是使用了Handler,發送了一條消息,那我們來找找handler初始化的地方,在Dispatcher類中,Handler通過如下方式初始化:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);  
竟然不是new一個Handler,DispatcherHandler是什麼鬼?來看看:

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

DispatcherHandler繼承自Handler重寫了它裏邊的方法,順藤摸瓜,找到屬於我們的case,點進去,最終來到了這個方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. void performSubmit(Action action, boolean dismissFailed) {  
  2.   if (pausedTags.contains(action.getTag())) {  
  3.     pausedActions.put(action.getTarget(), action);  
  4.     if (action.getPicasso().loggingEnabled) {  
  5.       log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(),  
  6.           "because tag '" + action.getTag() + "' is paused");  
  7.     }  
  8.     return;  
  9.   }  
  10.   
  11.   BitmapHunter hunter = hunterMap.get(action.getKey());  
  12.   if (hunter != null) {  
  13.     hunter.attach(action);  
  14.     return;  
  15.   }  
  16.   
  17.   if (service.isShutdown()) {  
  18.     if (action.getPicasso().loggingEnabled) {  
  19.       log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down");  
  20.     }  
  21.     return;  
  22.   }  
  23.   
  24.   hunter = forRequest(action.getPicasso(), this, cache, stats, action);  
  25.   hunter.future = service.submit(hunter);  
  26.   hunterMap.put(action.getKey(), hunter);  
  27.   if (dismissFailed) {  
  28.     failedActions.remove(action.getTarget());  
  29.   }  
  30.   
  31.   if (action.getPicasso().loggingEnabled) {  
  32.     log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId());  
  33.   }  
  34. }  
一進來,首先判斷該請求是否該暫停,接下來關鍵的是24行,調用forRequest方法給hunter賦值,我們來看看這個forRequest方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,  
  2.     Action action) {  
  3.   Request request = action.getRequest();  
  4.   List<RequestHandler> requestHandlers = picasso.getRequestHandlers();  
  5.   
  6.   // Index-based loop to avoid allocating an iterator.  
  7.   //noinspection ForLoopReplaceableByForEach  
  8.   for (int i = 0, count = requestHandlers.size(); i < count; i++) {  
  9.     RequestHandler requestHandler = requestHandlers.get(i);  
  10.     if (requestHandler.canHandleRequest(request)) {  
  11.       return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);  
  12.     }  
  13.   }  
  14.   
  15.   return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);  
  16. }  

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

第25行創建一個BitmapHunter,並在線程池中執行請求,線程池中傳入的對象是hunter,那毫無疑問,hunter肯定是實現了Runnable接口的,那接下來就去看看這個BitmapHunter的run方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  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.   
  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. }  
一進來,先更新線程名稱,然後是第9行調用了hunt方法,獲取到一個result,這個result是一個Bitmap,如果獲取到了Bitmap則調用dispatcher.dispatchComplete方法,否則調用dispatcher.dispatchFailed方法,這兩個實際上都是調用了Handler的sendMessage方法,來發送不同的消息做不同處理,我們這裏就來看看hunt()方法,看看這個Bitmap到底是怎麼獲取的:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  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 {  
  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. }  

首先是判斷是否可以從內存中獲取這張圖片,如果可以,將圖片加載出來並返回,並更新stats中相關變量,否則就會來到第17行,從一個RequestHandler中讀取,那麼RequestHandler是我們在new一個Picasso的時候傳入了多個RequestHandler,這裏到底是使用哪一個RequestHandler呢?這就和我們上文說的匹配RequestHandler有關了,毫無疑問,我們下載網絡圖片,當然是匹配NetworkRequestHandler,那我們看看NetworkRequestHandler裏邊的load方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. @Override public Result load(Request request, int networkPolicy) throws IOException {  
  2.   Response response = downloader.load(request.uri, request.networkPolicy);  
  3.   if (response == null) {  
  4.     return null;  
  5.   }  
  6.   
  7.   Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;  
  8.   
  9.   Bitmap bitmap = response.getBitmap();  
  10.   if (bitmap != null) {  
  11.     return new Result(bitmap, loadedFrom);  
  12.   }  
  13.   
  14.   InputStream is = response.getInputStream();  
  15.   if (is == null) {  
  16.     return null;  
  17.   }  
  18.   // Sometimes response content length is zero when requests are being replayed. Haven't found  
  19.   // root cause to this but retrying the request seems safe to do so.  
  20.   if (loadedFrom == DISK && response.getContentLength() == 0) {  
  21.     Utils.closeQuietly(is);  
  22.     throw new ContentLengthException("Received response with 0 content-length header.");  
  23.   }  
  24.   if (loadedFrom == NETWORK && response.getContentLength() > 0) {  
  25.     stats.dispatchDownloadFinished(response.getContentLength());  
  26.   }  
  27.   return new Result(is, loadedFrom);  
  28. }  

這個方法裏首先調用了downloader裏邊的load方法,獲取一個Response對象,然後再拿到這個response對象裏邊的Bitmap返回,downloader就是我們在上文說的那個downloader,我們就看那個源碼吧,反正和Piasso自帶的差不多,看看它裏邊的load方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. public Response load(Uri uri, int networkPolicy) throws IOException {  
  3.     CacheControl cacheControl = null;  
  4.     if (networkPolicy != 0) {  
  5.         if (NetworkPolicy.isOfflineOnly(networkPolicy)) {  
  6.             cacheControl = CacheControl.FORCE_CACHE;  
  7.         } else {  
  8.             CacheControl.Builder builder = new CacheControl.Builder();  
  9.             if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {  
  10.                 builder.noCache();  
  11.             }  
  12.             if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {  
  13.                 builder.noStore();  
  14.             }  
  15.             cacheControl = builder.build();  
  16.         }  
  17.     }  
  18.   
  19.     Request.Builder builder = new Request.Builder().url(uri.toString());  
  20.     if (cacheControl != null) {  
  21.         builder.cacheControl(cacheControl);  
  22.     }  
  23.   
  24.     okhttp3.Response response = client.newCall(builder.build()).execute();  
  25.     int responseCode = response.code();  
  26.     if (responseCode >= 300) {  
  27.         response.body().close();  
  28.         throw new ResponseException(responseCode + " " + response.message(), networkPolicy,  
  29.                 responseCode);  
  30.     }  
  31.   
  32.     boolean fromCache = response.cacheResponse() != null;  
  33.   
  34.     ResponseBody responseBody = response.body();  
  35.     return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength());  
  36. }  
哈哈,在這裏我們總算看到了網絡訪問的代碼了,就是大家熟悉的OkHttp網絡請求了,下載到數據之後,再重新new一個Response對象返回。just so so。。。。

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. void dispatchComplete(BitmapHunter hunter) {  
  2.     handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));  
  3.   }  

又是Handler,再找:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. case HUNTER_COMPLETE: {  
  2.           BitmapHunter hunter = (BitmapHunter) msg.obj;  
  3.           dispatcher.performComplete(hunter);  
  4.           break;  
  5.         }  

這裏又調用了dispatcher.performComplete方法,點擊去看看:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  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. }  

首先判斷了是否該將Bitmap寫入到內存緩存中,需要的話就寫入,然後是batch方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. private void batch(BitmapHunter hunter) {  
  2.     if (hunter.isCancelled()) {  
  3.       return;  
  4.     }  
  5.     batch.add(hunter);  
  6.     if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {  
  7.       handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);  
  8.     }  
  9.   }  

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

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. case HUNTER_DELAY_NEXT_BATCH: {  
  2.           dispatcher.performBatchComplete();  
  3.           break;  
  4.         }  

繼續點:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. void performBatchComplete() {  
  2.   List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);  
  3.   batch.clear();  
  4.   mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));  
  5.   logBatch(copy);  
  6. }  

在這裏將batch存入到一個新的List集合中,然後mainThreadHandler又發送一條消息,這個mainThreadHandler是什麼鬼?不知道大家是否還記得在build方法中我們創建Dispatch實例的時候傳入了一個Handler,就是那個在主線程中創建的Handler,在Picasso那個類裏邊,我們找到了HUNTER_BATCH_COMPLETE這個case:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. case HUNTER_BATCH_COMPLETE: {  
  2.           @SuppressWarnings("unchecked") List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;  
  3.           //noinspection ForLoopReplaceableByForEach  
  4.           for (int i = 0, n = batch.size(); i < n; i++) {  
  5.             BitmapHunter hunter = batch.get(i);  
  6.             hunter.picasso.complete(hunter);  
  7.           }  
  8.           break;  
  9.         }  

這個case中我們來一條一條的處理batch中的消息,交給picasso的complete方法去處理:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. void complete(BitmapHunter hunter) {  
  2.    Action single = hunter.getAction();  
  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();  
  15.    LoadedFrom from = hunter.getLoadedFrom();  
  16.   
  17.    if (single != null) {  
  18.      deliverAction(result, from, single);  
  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.  }  

在這裏,14行我們拿到Bitmap,17行去派發Action,如果有合併的Action則在25行進行派發,我們來看看這個派發操作:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  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);  
  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. }  
第8行,如果Bitmap不爲空,則會執行第12行,調用action的complete方法,Action是我們在into方法中創建的,當時new了一個ImageViewAction,所以我們去找ImageViewAction的complete方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. @Override public void complete(Bitmap result, Picasso.LoadedFrom from) {  
  2.   if (result == null) {  
  3.     throw new AssertionError(  
  4.         String.format("Attempted to complete action with no result!\n%s"this));  
  5.   }  
  6.   
  7.   ImageView target = this.target.get();  
  8.   if (target == null) {  
  9.     return;  
  10.   }  
  11.   
  12.   Context context = picasso.context;  
  13.   boolean indicatorsEnabled = picasso.indicatorsEnabled;  
  14.   PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);  
  15.   
  16.   if (callback != null) {  
  17.     callback.onSuccess();  
  18.   }  
  19. }  

獲取到所有信息之後,然後調用PicassoDrawable的setBitmap方法:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. static void setBitmap(ImageView target, Context context, Bitmap bitmap,  
  2.     Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {  
  3.   Drawable placeholder = target.getDrawable();  
  4.   if (placeholder instanceof AnimationDrawable) {  
  5.     ((AnimationDrawable) placeholder).stop();  
  6.   }  
  7.   PicassoDrawable drawable =  
  8.       new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);  
  9.   target.setImageDrawable(drawable);  
  10. }  

終於看到了給target設置圖片的代碼了,這裏的代碼都很簡單,不多說。

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


以上。


參考資料 

1.http://blog.csdn.net/zxm317122667/article/details/51814095

2.http://www.jianshu.com/p/c2b029f69f52


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