1. 程式人生 > >Android 常用開源框架源碼解析 系列 (十一)picasso 圖片框架

Android 常用開源框架源碼解析 系列 (十一)picasso 圖片框架

hand 需求 trim cor pan setname github ESS true

一、前言 Picasso 強大的圖片加載緩存框架 api加載方式和Glide 類似,均是通過鏈式調用的方式進行調用 1.1、作用 Picasso 管理整個圖片加載、轉換、緩存等策略 1.2、簡單調用: Picasso .with(this 傳入一個單例,上下文).load(“url”/file文件/資源路徑) .into() 1.2.1 、一些簡單的鏈式調用參數 .placeholder(R.drawable.xx) //網絡未加載完成的時候顯示的本地資源圖片 .error(R.drawable.xx) //網絡加載失敗顯示的本地資源圖片 .resize(480,800) //手動控制圖片寬高 ,對整個屏幕圖片進行設置像素設置 .centerCrop () //造成圖片拉伸扭曲的時候,將整個圖片充滿ImageView邊界,進行居中裁剪邊界 .rotate( 度數 0.0坐標開始轉 ) //旋轉動畫效果 .priority() //請求優先級,會影響請求執行順序,但不是百分之百保證的,只會往高優先級靠攏 .tag(“xxxx”) //允許為每一個請求設置一個tag ps:如果不設置tag,在list列表請求的時候,會加載所有的圖片請求,就會造成Ui卡頓; 而有了tag ,listView就可以進行監聽而後進行不同的操作事件 可以在滑動的setOnScrollListener()的監聽事件中 : 在onScrollStateChanged()方法中通過判斷是否處於IDLE狀態, 如果處於IDLE狀態就恢復 tag(picasso.resumeTag) 繼續加載; 如果是在滑動狀態,停止圖片加載的請求 picasso.pauseTag picasson的內存緩存 和 磁盤緩存 默認都會開啟的 ;但是不需要總是開啟,如果加載大圖片再緩存一份就很容易造成OOM .memoryPolicy(MemoryPolicy.No_CACHE) //picasso 的內存緩存模式 .networkPolicy(MemoryPolicy.No_CACHE) //人為禁掉 磁盤緩存 二、Picasso 源碼 2.1、 引入 implementation ‘com.squareup.picasso:picasso:2.71828‘
or Maven: <dependency> <groupId>com.squareup.picasso</groupId> <artifactId>picasso</artifactId> <version>2.71828</version> </dependency> 2.2 代碼混淆 If you are using ProGuard you might need to add OkHttp‘s rules: https://github.com/square/okhttp/#proguard 2.3 源碼分析 // 2.3.1 with()函數的進入-基礎配置工作,dispatch分發器配置,picasso對象配置 返回一個picasso對象的實例主旨功能 ,Picasso 入口函數 public void picasso() { Picasso.with(this) //使用單例模式構建保證在使用的時候整個module只有一個picasso存在 .load("url") .into(imageView); } with()://通過雙重鎖機制 保護線程的安全,通過Builder內部類的build()方法創建 Picasso對象 public static Picasso with
(Context context) { if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { singleton = new Builder(context).build(); } } } return singleton; } build(): public Picasso build() { Context context = this.context; //實際圖片加載的過程 if (downloader == null) { downloader = Utils.createDefaultDownloader(context); —————————————————— ps: 通過反射查找是否有OkHttpClient庫,如果有就通過OkHttp庫 進行圖片下載和加載方式 Class.forName("com.squareup.okhttp.OkHttpClient"); return OkHttpLoaderCreator.create(context); }else { //若沒有OkHttp庫就通過HttpURLConnection進行實現 return new UrlConnectionDownloader(context);
} —————————————————— //通過LruCache 最近使用內存算法;進行內存緩存 策略,picasso 中指定了手機的內存為15% —————————————————— ps:LruCache 實現了Lru算法復習;最近使用最少的緩存會被刪除;其實現會通過LinkedHashMap進行,每次調用get()、put()會將對象放到鏈表的尾端,每次put依然會將對象放到鏈表的尾部。當內存緩存達到最大值的時候,他就會將我們的鏈表頭部的對象移除,以達到Lru算法的目的。 —————————————————— if (cache == null) { cache = new LruCache(context); } //picasso 的線程池Executor if (service == null) { service = new PicassoExecutorService(); —————————————————— ps:PicassoExecutorService 繼承自 ThreadPoolExecutor private static final int DEFAULT_THREAD_COUNT = 3; public ThreadPoolExecutor(int corePoolSize, //核心線程數量,如果沒有任務的話,整個核心的corePoolSize也不會終止,除非認為設置需要核心線程 int maximumPoolSize, //線程池中允許的最大的線程數量,線程池數量越大,開銷越大 long keepAliveTime, //當我們線程池中的線程線程數目比核心線程多的時候,如果超過了keepAliveTime這個時間,那麽多余的線程就會被回收,也就是被移出線程池 TimeUnit unit, BlockingQueue<Runnable> workQueue, //阻塞隊列,是一個線程安全隊列 ThreadFactory threadFactory, //用來構造線程池的工廠 RejectedExecutionHandler handler) //任務阻塞的時候線程池的一種處理方式 {
  • 當有新任務的時候先觀察corePoolSize值
  • 然後觀察workQueue工作隊列是否已經滿了
  • 最好判斷是否有小於線程池的最大值
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); ... } 思考: 線程池中的線程是如何調度的呢?
  • 1、如果線程池中線程的數目少於corePoolSize; 這時候線程池會重新創建一個核心線程的,直到核心線程數量達到corePoolSize就不會再創建新核心線程了
  • 2、如果線程池中線程的數目大於或是等於corePoolSize,但是工作隊列workQueue沒有滿,那麽新的任務依然會放置到這個隊列當中,按照先進先出的原則來進行執行
  • 3、如果線程池中的線程數目大於等於corePoolSize,並且工作隊列workQueue滿了,但是總線程數目小於maximumPoolSize,那麽可以直接創建一個線程,處理並添加到任務當中
  • 4、如果工作隊列滿了,並且線程池中線程的數目到達了最大數目maximumPoolSize的時候,這時候就需要用到RejectedExecutionHandler這個對象進行處理,默認處理方式是丟棄任務並拋出異常
—————————————————— } //配置請求轉換器,picasso 不進行轉換操作所以直接返回原來請求 if (transformer == null) { transformer = RequestTransformer.IDENTITY; } //新建一個保留緩存狀態對象 Stats stats = new Stats(cache); //主線程到子線程的切換通過 重要的Dispatcher 完成 Dispatcherdispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); //通過單例完成Picasso 對象,該Picasso 對象是加載圖片的入口同時 return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled); } 2.3.2 、Dispatcher 完成線程切換 通過該類,在內部使用handler機制 ,完成主線程和子線程之間所有的切換。所以非常重要 Dispatcher()的構造方法: Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler, Downloader downloader, Cache cache, Stats stats) { //1、在構造方法中創建一個子線程,並開啟一個子線程 ,執行耗時操作 this.dispatcherThread = new DispatcherThread(); —————————————————— ps:static class DispatcherThread extends HandlerThread,而HandlerThread又繼承自Thread本質上是一個線程類; 這個handlerThread會開啟一個新的線程,這個線程內部會有一個looper對象,通過looper對象會循環遍歷消息隊列 在其核心的 run()方法中 : //通過looper完成looper的創建,以及通過looper的循環方法來構造一個循環的線程。??HandlerThread需要start()方法開啟線程 @Override public void run() { mTid = Process.myTid(); Looper.prepare(); //完成Looper對象的創建 synchronized (this) { //利用同步鎖鎖住當前對象,通過myLooper()函數獲取當前looper並賦值 mLooper = Looper.myLooper(); notifyAll();//喚醒等待線程—wait()成對出現 } Process.setThreadPriority(mPriority); onLooperPrepared(); //空實現方法 Looper.loop();//開啟線程消息循環 mTid = -1; } 在getLooper()函數內 ,如果線程存活或是looper對象是空,則進入等待wait()狀態,表示進入阻塞階段,直到looper對象被成功創建同時調用了notifyAll()函數後就會喚醒先前的等待線程。 —————————————————— … //2、初始化兩個很重要的handler,一個是主線程中的handler ,一個是子線程的handler this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this); —————————————————— ps:在其構造方法內傳入一個looper 和一個dispatcher 分發器,這個handler的作用是處理DispatcherThread 這個子線程當中的handler public DispatcherHandler(Looper looper, Dispatcher dispatcher) { super(looper); this.dispatcher = dispatcher; } —————————————————— this.downloader = downloader; this.mainThreadHandler = mainThreadHandler;//主線程handler —————————————————— ps:在前面的Dispatcherdispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);表達式中傳入的第三個參數就是代表主線程中的handler;該Handler是一個static靜態的handler對象,並且傳入一個主線程的looper對象,為了防止內存泄漏的產生;在這裏如果定義成非靜態內部類的話,會持有外部累Picasso的引用,導致picasso對象無法在該被回收的時候進行回收。 —————————————————— … //3、創建一個廣播接收者用於接收外部網絡環境的變化,內部通過切換子線程的數量完成理論操作 this.receiver = new NetworkBroadcastReceiver(this);//廣播接收者,用於監聽網絡變化等操作 內部核心方法:onReceive(),監聽intent 並做出相應的操作 @Override public void onReceive(Context context, Intent intent) { if (intent == null) { return; } final String action = intent.getAction(); //針對飛行模式進行判斷 並處理 if (ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { if (!intent.hasExtra(EXTRA_AIRPLANE_STATE)) { return; // No airplane state, ignore it. Should we query Utils.isAirplaneModeOn? } dispatcher.dispatchAirplaneModeChange(intent.getBooleanExtra(EXTRA_AIRPLANE_STATE, false)); //正常情況下,網絡變化的處理邏輯 } else if (CONNECTIVITY_ACTION.equals(action)) { ConnectivityManager connectivityManager = getService(context, CONNECTIVITY_SERVICE); dispatcher.dispatchNetworkStateChange(connectivityManager.getActiveNetworkInfo()); } } dispatcher.dispatchNetworkStateChange(): //可以發現使用的是handler通過sendMessage()方法進行消息的傳遞,交給HandleMessage()進行處理 void dispatchNetworkStateChange(NetworkInfo info) { handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info)); } 在該方法中進行網絡變化的業務邏輯: dispatcher.performNetworkStateChange(info); void performNetworkStateChange(NetworkInfo info) { //線程池判斷,在adjustThreadCount()中根據不同的網絡類型設置線程的數量,根據用戶具體的場景和使用需要設置線程的數量,值得自己的代碼進行復刻 if (service instanceof PicassoExecutorService) { ((PicassoExecutorService) service).adjustThreadCount(info); ... } 2.3.3 、NetworkRequestHandler處理 //用來存放requestHandler ,根據具體的需求選擇相應的handler List<RequestHandler> allRequestHandlers = new ArrayList<RequestHandler>(builtInHandlers + extraCount); allRequestHandlers.add(new ContentStreamRequestHandler(context)); allRequestHandlers.add(new AssetRequestHandler(context));//用於處理asset 資源圖片,如果加載的圖片在Aset資源目錄下就 allRequestHandlers.add(new FileRequestHandler(context));//用於處理文件當中的圖片 allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));//如果圖片需要通過網絡下載就會通過這個handler進行處理 NetworkRequestHandler() 裏的核心方法:如何加載圖片請求 @Override public Result load(Request request, int networkPolicy) throws IOException { //開啟一個downloader下載器 完成下載 Response response = downloader.load(request.uri, request.networkPolicy); if (response == null) { return null; } Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK; Bitmap bitmap = response.getBitmap(); if (bitmap != null) { return new Result(bitmap, loadedFrom); } InputStream is = response.getInputStream(); if (is == null) { return null; } // Sometimes response content length is zero when requests are being replayed. Haven‘t found // root cause to this but retrying the request seems safe to do so. if (loadedFrom == DISK && response.getContentLength() == 0) { Utils.closeQuietly(is); throw new ContentLengthException("Received response with 0 content-length header."); } //如果是從網絡下載圖片,同時獲取到的資源長度大於0 ,表示可以調用成功的回調 if (loadedFrom == NETWORK && response.getContentLength() > 0) { stats.dispatchDownloadFinished(response.getContentLength()); } return new Result(is, loadedFrom); } //可以發現該方法同樣是通過handle 發送消息來控制的,但是要註意的是這裏的這個handler不是前面的dispatchHandler中的handler了,這裏的這個handler是保存在state 類中的StatsHandler,傳入的是statsThread.getLooper()的handler void dispatchDownloadFinished(long size) { handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size)); } 在case 中 通過 performDownloadFinished()方法 stats.performDownloadFinished((Long) msg.obj); void performDownloadFinished(Long size) { downloadCount++; //當前下載數量加1 totalDownloadSize += size;//將總的下載文件的大小,加上本次下載文件大小的值 averageDownloadSize = getAverage(downloadCount, totalDownloadSize);//計算平均下載的大小 } 2.3.4 .load(“") 參數類型: 1、Url 地址 2、path文件路徑 3、資源ID //在load()方法中創建了RequestCreator對象,在RequestCreator對象內部又創建了一個RequestBuilder對象,通過Builder對象可以進行鏈式調用配置更多參數 public RequestCreator load(String path) { if (path == null) { //創建圖片加載請求 return new RequestCreator(this, null, 0); ps:RequestCreator 對picasso進行賦值,並創建一個Request的Builder對象,該對象接收3個參數,url、資源布局Id和一個默認的圖片配置 RequestCreator(Picasso picasso, Uri uri, int resourceId) { if (picasso.shutdown) { throw new IllegalStateException( "Picasso instance already shut down. Cannot submit new requests."); } this.picasso = picasso; this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig); } } if (path.trim().length() == 0) { throw new IllegalArgumentException("Path must not be empty."); } return load(Uri.parse(path)); } 2.3.5 .into(“") public void into(ImageView target, Callback callback) { long started = System.nanoTime(); //1、用於檢驗當前線程是否實在主線程,非主線程拋出異常 checkMain(); if (target == null) { //ImageView對象不能為空 throw new IllegalArgumentException("Target must not be null."); } //2、創建Request的Builder 的內部類 data,hasImage()函數用於判斷 url 或是資源Id是否為空 if (!data.hasImage()) { picasso.cancelRequest(target); //3、為空則取消加載圖片請求 ———————————————————————————— ps: private void cancelExistingRequest(Object target) { checkMain(); //Action是Request的封裝類,存儲了Picasso、request、memoryPolicy、networkPolicy等對象,封裝好後交給BitmapHunter這個Runnabel進行開啟線程下載 1、targetToAction 是一個map對象,key是imageView而value就是請求包裝類action,取消請求就是把有關的target的信息刪除掉; Action action = targetToAction.remove(target); if (action != null) { action.cancel(); 2、使用了dispatch線程中的handler通過 sendMessage傳遞取消請求的請求,通過dispatcher的performCancel()進行取消: dispatcher.dispatchCancel————>performCancel(): //BitmapHunter 是一個Runnable工具 可以開啟線程,下載並處理Bitmap以及小量的圖片處理工作 BitmapHunterhunter = hunterMap.get(key); if (hunter != null) { hunter.detach(action); if (hunter.cancel()) { //取消BitmapHunter操作 hunterMap.remove(key); ... } dispatcher.dispatchCancel(action); } if (target instanceof ImageView) { 3、將target轉化成Imageview ImageView targetImageView = (ImageView) target; DeferredRequestCreator deferredRequestCreator = targetToDeferredRequestCreator.remove(targetImageView); //創建一個圖片加載請求的時候,還不能獲取到當前ImageView的寬和高,這時候就需要創建一個deferredRequestCreator類用來對ImageView的target去進行監聽,直到獲取到了ImageView的寬和高後就可以重新執行請求創建了。所以說刪除請求也需要刪除掉deferredRequestCreator綁定的監聽事件 if (deferredRequestCreator != null) {//Requestcreator 創造器的包裝類 deferredRequestCreator.cancel(); } } } ———————————————————————— if (setPlaceholder) { 4、通過setPlaceHolder()函數設置占位符 setPlaceholder(target, getPlaceholderDrawable()); } return; } 5、圖片加載是否延遲進行判斷 if (deferred) { if (data.hasSize()) { throw new IllegalStateException("Fit cannot be used with resize."); } 根據target寬高進行從新的測量 int width = target.getWidth(); int height = target.getHeight(); ... data.resize(width, height); } 6、創建圖片加載請求 Request request = createRequest(started); ps:內部通過Request request = data.build();創建Request對象並將這個Request對象進行轉換 Request transformed = picasso.transformRequest(request); 只有在創建Picasso的時候有關transform的時候就需要進行轉換的操作 //將ImageView和創建好的createKey值進行關聯,也就是將ImageView和Request關聯到一起為後續需求作準備 String requestKey = createKey(request); 7、判斷是否從內存緩存中讀取數據 if (shouldReadFromMemoryCache(memoryPolicy)) { 8、獲取緩存當中的Bitmap Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey); if (bitmap != null) { 9、如果已經從緩存中讀取就沒必要在從網絡中價值圖片了所以一定要取消掉這個請求否則會重復加載 picasso.cancelRequest(target); setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled); if (picasso.loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY); } if (callback != null) { callback.onSuccess(); } return; } } //Action的構造方法中,將ImageView 添加到了Action.RequestWeakReference這個弱引用中,也就是說可以在內存不足的時候會回收掉這個對象 Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); 10、進行任務的提交 picasso.enqueueAndSubmit(action); void enqueueAndSubmit(Action action) { //通過請求包裝類獲取到target 屬性 Object target = action.getTarget(); if (target != null && targetToAction.get(target) != action) { //判斷target和請求包裝Action類是否配套,如果不配套就取消,會將新的action 添加到 argetToAction這個Map當中 // This will also check we are on the main thread. cancelExistingRequest(target); targetToAction.put(target, action); } submit(action); } void submit(Action action) { //內部通過dispatcher 分發器進行調度 dispatcher.dispatchSubmit(action); //內部依然是通過handler進行消息的傳遞 REQUEST_SUBMIT } performSubmit(): void performSubmit(Action action, boolean dismissFailed) { 1、對根據Action的標誌查詢是否已經存在於pausedTags這個set列表當中 if (pausedTags.contains(action.getTag())) { pausedActions.put(action.getTarget(), action); if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(), "because tag ‘" + action.getTag() + "‘ is paused"); } return; } 2、創建BitmapHunter對象,並調用attach()函數完成工作 BitmapHunter hunter = hunterMap.get(action.getKey()); if (hunter != null) { hunter.attach(action); //actions是在一個arrayList當中,保證了action 不會重復 return; } 3、判斷線程池是否關閉了 ,如果關閉了就打印異常日誌 if (service.isShutdown()) { if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down"); } return; } 4、沒有關閉線程池的時候,通過forRequest()函數獲取到BitmapHunter對象, hunter = forRequest(action.getPicasso(), this, cache, stats, action); ———————————————————————————————————— ps:通過action、picasso 對象獲取到Request對象和requestHandler對象 Request request = action.getRequest(); List<RequestHandler> requestHandlers = picasso.getRequestHandlers(); 通過for循環遍歷所有的handler請求器,獲取到不同業務處理的請求業務處理器 static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) { for (int i = 0, count = requestHandlers.size(); i < count; i++) { RequestHandler requestHandler = requestHandlers.get(i); if (requestHandler.canHandleRequest(request)) { //最終將BitmapHunter 子線程返回給orRequest()方法 return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler); } } ———————————————————————————————————— future 對象用來保存結果 hunter.future = service.submit(hunter); ps:將圖片請求放到線程池中進行 @Override public Future<?> submit(Runnable task) { 將線程task封裝成PicassoFutureTask便於控制處理的線程類,並通過execute()函數開啟線程 PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task); execute(ftask); return ftask; } hunterMap.put(action.getKey(), hunter); if (dismissFailed) { failedActions.remove(action.getTarget()); } if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId()); } 思考:線程開啟後如何執行圖片加載?又是在何地方進行進行操作的呢? private static final class PicassoFutureTask extends FutureTask<BitmapHunter> implements Comparable<PicassoFutureTask> { private final BitmapHunter hunter; //在PicassoFutrueTask中持有了BitmapHunter的引用 單個線程如何操作 ——BitmapHunter BitmapHunter 核心類:run @Override public void run() { try { updateThreadName(data); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_EXECUTING, getLogIdsForHunter(this)); } result = hunt();//整個線程的核心方法 if (result == null) { dispatcher.dispatchFailed(this); } else { dispatcher.dispatchComplete(this); } } catch (Downloader.ResponseException e) { ... } finally { Thread.currentThread().setName(Utils.THREAD_IDLE_NAME); } } hunt(): Bitmap hunt() throws IOException { Bitmap bitmap = null; 1、是否從內存緩存中讀取緩存進行判斷 if (shouldReadFromMemoryCache(memoryPolicy)) { 2、根據Action的key值從緩存中查找bitmap是否存在 bitmap = cache.get(key); if (bitmap != null) { 3、bitmap不為空,修改stats的狀態 stats.dispatchCacheHit(); loadedFrom = MEMORY; if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache"); } return bitmap; } } 4、如果緩存不存在就從網絡中加載圖片 data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; RequestHandler.Result result =requestHandler.load(data, networkPolicy);//requestHandler是屬於networkRequestHandler ps:RequestHandler的 NetworkRequestHandler實現為例: @Override public Result load(Request request, int networkPolicy) throws IOException { Response response = downloader.load(request.uri, request.networkPolicy); //這裏的load 有兩個實現類OkHttpDownloader和UrlConnectionDownloader,也就是前者通過反射機制查找是否加載了OkHttp網絡框架,如果沒有使用OkHttp庫則直接使用google自己的HttpUrlConnection進行網絡加載 OkHttpDownloader 和 UrlConnectionDownloader 對於 load ()方法的區別 a、OkHttpDownloader是如何下載圖片的 @Override public Response load(Uri uri, int networkPolicy) throws IOException { 1、新建CacheControl 對象 用於對緩存進行處理,設置 CacheControl cacheControl = null; … 2、新建builder對象用來存放硬盤內存緩存的設置 CacheControl.Builder builder = new CacheControl.Builder(); 3、判斷是否從內存緩存中讀取數據 if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) { builder.noCache(); } 4、如果設置不從硬盤緩存中讀取數據 if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) { builder.noStore(); } 5、將build方法的返回值給cacheControl,由cacheControl變量設置OkHttp庫的設置 cacheControl = builder.build(); 6、新建Request對象並為其url賦值 Request.Builder builder = new Request.Builder().url(uri.toString()); if (cacheControl != null) { 設置Okhttp 緩存策略的頭部 builder.cacheControl(cacheControl); } 7、調用OkHttp同步請求 獲取response對象 com.squareup.okhttp.Response response = client.newCall(builder.build()).execute(); int responseCode = response.code(); 8、針對響應碼進行判斷 如果大於等於300就關閉響應體 if (responseCode >= 300) { response.body().close(); throw new ResponseException(responseCode + " " + response.message(), networkPolicy, responseCode); } 9、如果小於300就返回response boolean fromCache = response.cacheResponse() != null; ResponseBody responseBody = response.body(); return new Response(responseBody.byteStream(), fromCache, responseBody.contentLength()); } b、UrlConnectionDownloader是如何下載圖片的 @Override public Response load(Uri uri, int networkPolicy) throws IOException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { installCacheIfNeeded(context); } 1、新建HttpURLConnection 對象 HttpURLConnection connection = openConnection(uri); connection.setUseCaches(true); //開啟緩存功能 2、判斷是否僅在離線情況下使用緩存 if (networkPolicy != 0) { String headerValue; if (NetworkPolicy.isOfflineOnly(networkPolicy)) { headerValue = FORCE_CACHE; } else { 3、判斷是否從內存緩存中讀取 if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) { 不從緩存中讀取 直接添加no-cache 標簽 builder.append("no-cache"); } 4、判斷是否從硬盤緩存中讀取數據 if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) { if (builder.length() > 0) { builder.append(‘,‘); } 不從硬盤緩存中讀取數據添加 no-store標簽 builder.append("no-store"); } headerValue = builder.toString(); 5、將獲取到的所有緩存信息添加到Cache-Control 頭部信息中 connection.setRequestProperty("Cache-Control", headerValue); 6、跟OkHttp一樣獲取responseCode響應碼,大於300關閉連接,小於300返回response int responseCode = connection.getResponseCode(); if (responseCode >= 300) { connection.disconnect(); throw new ResponseException(responseCode + " " + connection.getResponseMessage(), networkPolicy, responseCode); } long contentLength = connection.getHeaderFieldInt("Content-Length", -1); boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE)); return new Response(connection.getInputStream(), fromCache, contentLength); } dispatcher.dispatchComplete(): void performComplete(BitmapHunter hunter) { 1、首先對緩存策略進行判斷,如果開啟內測緩存就 if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) { 將結果保存到cache緩存當中 cache.set(hunter.getKey(), hunter.getResult()); } 2、從map中移除BitmapHunter對應的key值,因為這會兒請求已經完成可以刪除對應的BitmapHunter中的數據否則會重復請求 hunterMap.remove(hunter.getKey()); batch(hunter); —————————————————— ps:batch() private void batch(BitmapHunter hunter) { //1、判斷bitmapHunter 是否已經取消 if (hunter.isCancelled()) { return; } //2、沒有取消就將這個hunter存放在batch 這個list 集合當中 batch.add(hunter); if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) { //3、發送一個空消息 給handler進行處理 handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY); } } —————————————————— void performBatchComplete() { //1、首先獲取存放BitmapHunter的集合 List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch); //2、清理整個batch集合 batch.clear(); //3、給主線程發送消息 處理事件 mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy)); logBatch(copy); } —————————————————— ps:在handleMessage()中遍歷BitmapHunter 並使用picasso.complete()函數進行操作 for (int i = 0, n = batch.size(); i < n; i++) { BitmapHunter hunter = batch.get(i); hunter.picasso.complete(hunter); } … //根據BitmapHunter 獲取Uri信息、異常信息、bitmap信息等 Uri uri = hunter.getData().uri; Exception exception = hunter.getException(); Bitmap result = hunter.getResult(); LoadedFrom from = hunter.getLoadedFrom(); if (single != null) { //分發圖片請求的包裝類Action deliverAction(result, from, single); //在deliverAction()方法中 ,當Action沒有被取消,以及bitmap不為空的時候,就會調用 action.complete(result, from)完成一些操作; 在complete的實現類ImageViewAction的complete()中,會將target轉換成ImageView,在後面調用 PicassoDrawable的setBitmap()方法中使用setImageDrawable(drawable)方法設置圖片,最終調用ImageView進行設置圖片的操作 } —————————————————— ... if (hunter.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_BATCHED, getLogIdsForHunter(hunter), "for completion"); } }

Android 常用開源框架源碼解析 系列 (十一)picasso 圖片框架