Android 圖片載入框架 Picasso 基本使用和原始碼完全解析
說到Picasso,相信Android開發人員絕不陌生,它是Square公司開發的一款圖片載入神器。使用過它的coder絕對是愛不釋手:對它本身而言,輕量安全,有效載入圖片並防止OOM;對我們開發者來說,簡單方便,一行程式碼搞定圖片載入。因此它備受Android開發人員的鐘愛。
關於它的更多好處優點,相信不用我介紹你也非常的清楚,在這裡我們廢話不多說,直接的進入主題來講解它的使用和從原始碼的角度解析它的載入原理。
Picasso基本使用
一 工程引入
在我們的專案build.gradle中新增對Picasso框架的依賴:
compile 'com.squareup.picasso:picasso:2.5.2'
在這裡我使用的是2.5.2的最新版本。
二 新增許可權
因為載入圖片需要訪問網路,由此我們在Manifest中新增訪問網路的許可權:
<uses-permission android:name="android.permission.INTERNET"/>
三 建立載入圖片佈局檔案
在我們的MainActivity的佈局檔案中新增一個Button和一個ImageView:
四 MainActivity中點選載入圖片
在這裡我們點選Button讓它載入一張百度Logo的圖片並顯示在ImageView控制元件中。
五 載入圖片
點選Button載入圖片,來看結果:
ok,已載入出來了,但是我們到底做了什麼呢?
基本載入
其實我們所做的事情非常的簡單,那就是在Button的點選事件中新增一行程式碼:
Picasso.with(MainActivity.this).load(url).into(headerImage);
沒錯就是一行程式碼搞定圖片載入。至於它是怎麼實現的,我們會在後面的原始碼分析中詳細的解析它,現在來看看它的其他使用。
佔點陣圖
不光載入圖片是非常簡單的事情,而且還可以在載入過程中使用佔點陣圖,可以很友好的告訴使用者它正在載入,而不是載入中顯示空白的介面。
Picasso.with(MainActivity.this) .load(url) .placeholder(R.mipmap.ic_launcher) .into(headerImage);
使用placeholder為尚未載入到圖片的ImageView設定佔點陣圖,這是一種友好的顯示方式。
異常圖
不光可以設定佔點陣圖,而且還可以在圖片載入不出來,或是找不到要載入的圖片後,可以為ImageView設定異常圖:
Picasso.with(MainActivity.this) .load(url) .placeholder(R.mipmap.ic_launcher) .error(R.drawable.error) .into(headerImage);
使用error可以為載入異常的ImageView設定異常圖,修改我們的圖片URL,使它無法獲取到正確的圖片地址,然後來看結果:
轉換器
不僅如此,我們還可以對載入到的圖片進行重新調整,比如改變圖片的大小,顯示形狀等,可以使用transform方法,例如:
首先我們先自定義一個Transformation:
private class customTransformer implements Transformation{ @Override public Bitmap transform(Bitmap source) { //在這裡可以對Bitmap進行操作,比如改變大小,形狀等 return source; } @Override public String key() { return null; } }
然後在transform方法中進行處理:
Picasso.with(MainActivity.this) .load(url) .placeholder(R.mipmap.ic_launcher) .transform(new customTransformer()) .error(R.drawable.error) .into(headerImage);
這樣的話就可以對圖片進行一定意義上的控制和選擇,使它更加符合我們的需求。
當然Picasso中還有很多其他的應用,比如可以設定載入大小,使用resizeDimen方法,填充方式使用centerCrop,fit等等,大家如果有需要的話可以自己嘗試使用。這裡就不要一一的介紹了。
Picasso 原始碼解析
ok,上面介紹了Picasso的部分基礎使用,非常的簡單,一行程式碼搞定你的所需,那麼下面我們從原始碼的角度來解析下它到底是怎麼實現我們的圖片載入和使用的。
以下面最簡潔的載入為示例:
Picasso.with(MainActivity.this).load(url).into(headerImage);
with
首先Picasso會呼叫靜態with方法,那麼我們來看看with方法是怎麼實現的:
由上面的原始碼我們可以看到,在with方法中主要做了一件事,那就是返回一個Picasso例項,當然這個例項也不是那麼簡單的建立的,為了防止Picasso的多次建立,這裡使用了雙重加鎖的單例模式來建立的,主要目的是為了保證執行緒的安全性。但是它又不是直接的使用單例模式建立的,在建立例項的過程中使用了Builder模式,它可以使Picasso在建立時初始化很多物件,以便後期使用,那麼我們就來看看這個Builder是怎麼操作的:
在Builder的構造方法中就只是獲取到當前應用級別的上下文,也就說明了Picasso是針對應用級別的使用,不會是隨著Activity或是Fragment的生命週期而產生變化,只有噹噹前的應用退出或是銷燬時Picasso才會停止它的行為。
那麼接下來看下build方法中做了哪些操作呢:
這裡程式碼也是很簡單,主要是初始化了downloader,cache,service,dispatcher等幾個例項變數,而這幾個變數值也是已設定的,如原始碼:
public Builder downloader(Downloader downloader) { if (downloader == null) { throw new IllegalArgumentException("Downloader must not be null."); } if (this.downloader != null) { throw new IllegalStateException("Downloader already set."); } this.downloader = downloader; return this; } public Builder executor(ExecutorService executorService) { if (executorService == null) { throw new IllegalArgumentException("Executor service must not be null."); } if (this.service != null) { throw new IllegalStateException("Executor service already set."); } this.service = executorService; return this; } public Builder memoryCache(Cache memoryCache) { if (memoryCache == null) { throw new IllegalArgumentException("Memory cache must not be null."); } if (this.cache != null) { throw new IllegalStateException("Memory cache already set."); } this.cache = memoryCache; return this; } ...
這些設定就像我們平常使用AlertDialog一樣,貨到到Builder之後分別進行設定就行。
ok,我們現在來看看初始化中幾個非常重要的變數:
downloader 下載器
首先看下downloader,builder中首先判斷downloader是否為空值,當為空值時就為它初始化預設值,如:
if (downloader == null) { downloader = Utils.createDefaultDownloader(context); }
來看下Utils中是怎麼實現downloader 的初始化的:
createDefaultDownloader方法中首先使用Java反射機制來查詢專案中是否使用了OKHttp網路載入框架,如果使用了則會使用okhttp作為圖片的載入方式,如果沒有使用,則會使用內建的封裝載入器UrlConnectionDownloader。
注:由於okhttp3的包名已更換,所以在這裡都是使用內建的封裝下載器,這個是一個小bug等待完善。當修復之後Picasso+okhttp3則是最理想的載入方式。
service 執行緒池
同樣的使用,首先判斷是否為空,如果為空則初始化預設物件:
if (service == null) { service = new PicassoExecutorService(); }
我們在來看看PicassoExecutorService的原始碼:
PicassoExecutorService直接繼承與ThreadPoolExecutor執行緒池,在構造方法中初始化了主執行緒大小,最大執行緒等,如:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler;
當然,在Picasso執行緒池中主執行緒和最大執行緒數是可變的,根據使用者使用的網路型別來設定執行緒數量,後面會詳細說明它的應用。
dispatcher 事務分發器
dispatcher 在Picasso中扮演著十分重要的角色,可以說它是整個圖片載入過程中的中轉站,主執行緒和子執行緒來回的切換主要都是依賴它的存在。下面我們將來仔細的研究下它的設計,理解好它的存在對整個Picasso框架的理解也基本明朗。
首先,來看看它的登場亮相:
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
它的登場就是建立一個Dispatcher 物件例項,當然,它傳遞了在Builder中所初始化的物件例項,比如downloader下載器,用於圖片的下載,當然下載是不能再主執行緒進行的,所以這裡也傳遞了service執行緒池,而下載好的資源也是不能直接在子執行緒中進行更新UI的,所以同時也把主執行緒中的HANDLER傳遞進去,同時應用級別的上下文context,和快取cache,狀態變化stats等也傳遞進去進行相對應的業務操作。
經過上面的分析,我們可以很清楚的看出,就這麼簡單的一個建立例項已經很明確的表達出了Dispatcher存在的意義,而且我們也明確了它大概的職責。
那麼我們接著看看Dispatcher的構造方法中具體的做了哪些操作:
在Dispatcher的構造方法中我把它分為了5個部分,下面來詳細的解析下:
①:dispatcherThread,它是一個HandlerThread執行緒,如:
static class DispatcherThread extends HandlerThread { DispatcherThread() { super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND); } }
它的建立主要是開啟一個子執行緒供Dispatcher呼叫,目的就是為了在子執行緒中去執行耗時的圖片下載操作。
②:物件例項的接受,主要是接受在Picasso中初始化的物件例項,這個沒有什麼好說的。
③:建立用於儲存資料、物件的集合,也就是為了儲存物件或狀態用的。
④:DispatcherHandler,這是一個Handler,並且是作用在dispatcherThread執行緒中的Handler,它用於把在dispatcherThread子執行緒的操作轉到到Dispatcher中去,它的構造方法中接受了Dispatcher物件:
我們在來看看他的handleMessage方法是怎麼處理訊息的:
@Override public void handleMessage(final Message msg) { switch (msg.what) { case REQUEST_SUBMIT: { Action action = (Action) msg.obj; dispatcher.performSubmit(action); break; } case REQUEST_CANCEL: { Action action = (Action) msg.obj; dispatcher.performCancel(action); break; } case TAG_PAUSE: { Object tag = msg.obj; dispatcher.performPauseTag(tag); break; } case TAG_RESUME: { Object tag = msg.obj; dispatcher.performResumeTag(tag); break; } case HUNTER_COMPLETE: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performComplete(hunter); break; } case HUNTER_RETRY: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performRetry(hunter); break; } case HUNTER_DECODE_FAILED: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performError(hunter, false); break; } case HUNTER_DELAY_NEXT_BATCH: { dispatcher.performBatchComplete(); break; } case NETWORK_STATE_CHANGE: { NetworkInfo info = (NetworkInfo) msg.obj; dispatcher.performNetworkStateChange(info); break; } case AIRPLANE_MODE_CHANGE: { dispatcher.performAirplaneModeChange(msg.arg1 == AIRPLANE_MODE_ON); break; } default: Picasso.HANDLER.post(new Runnable() { @Override public void run() { throw new AssertionError("Unknown handler message received: " + msg.what); } }); } }
而從它的處理訊息的handleMessage中我們可以看出,所有的訊息並沒有被直接進行處理而是轉移到了dispatcher中,在dispatcher中進行相應的處理。
⑤:監聽網路變化操作,它是用於監聽使用者手機網路變化而存在的。我們主要來看看NetworkBroadcastReceiver這個類:
在構造引數中也接收到Dispatcher物件,並有註冊廣播和銷燬廣播的方法,當然它也沒有直接的處理,也是傳遞到Dispatcher中進行消化的。
我們在來看看當使用者的網路發生變化時,它會做哪些操作:
我們可以看到主要分為兩個,一個是航班模式,一個是正常的網路狀態變化,航班模式我們先不用理會,主要看下網路變化的操作:
void dispatchNetworkStateChange(NetworkInfo info) { handler.sendMessage(handler.obtainMessage(NETWORK_STATE_CHANGE, info)); }
這裡的handler就是DispatcherHandler,那麼我們看看它又是怎麼做的:
case NETWORK_STATE_CHANGE: { NetworkInfo info = (NetworkInfo) msg.obj; dispatcher.performNetworkStateChange(info); break; }
呼叫dispatcher的performNetworkStateChange方法來處理:
當網路變化時,它會傳遞給我們的PicassoExecutorService執行緒池,在adjustThreadCount方法中判斷使用者是使用的那型別網路,如wifi,4G等,然後為執行緒池設定相應的執行緒數。來看:
void adjustThreadCount(NetworkInfo info) { if (info == null || !info.isConnectedOrConnecting()) { setThreadCount(DEFAULT_THREAD_COUNT); return; } switch (info.getType()) { case ConnectivityManager.TYPE_WIFI: case ConnectivityManager.TYPE_WIMAX: case ConnectivityManager.TYPE_ETHERNET: setThreadCount(4); break; case ConnectivityManager.TYPE_MOBILE: switch (info.getSubtype()) { case TelephonyManager.NETWORK_TYPE_LTE: // 4G case TelephonyManager.NETWORK_TYPE_HSPAP: case TelephonyManager.NETWORK_TYPE_EHRPD: setThreadCount(3); break; case TelephonyManager.NETWORK_TYPE_UMTS: // 3G case TelephonyManager.NETWORK_TYPE_CDMA: case TelephonyManager.NETWORK_TYPE_EVDO_0: case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: setThreadCount(2); break; case TelephonyManager.NETWORK_TYPE_GPRS: // 2G case TelephonyManager.NETWORK_TYPE_EDGE: setThreadCount(1); break; default: setThreadCount(DEFAULT_THREAD_COUNT); } break; default: setThreadCount(DEFAULT_THREAD_COUNT); } } private void setThreadCount(int threadCount) { setCorePoolSize(threadCount); setMaximumPoolSize(threadCount); }
就如上面所說,根據使用者使用不同的網路型別分別設定執行緒的數量,比如當使用者使用的是wifi,執行緒數量將會設定為4個,4G的話設為3個等,這樣根據使用者的具體情況來設計執行緒數量是非常人性化的,也是值得我們效仿的。
ok,到此我們重要的Dispatcher物件的構造方法已完全的解析完成了,從上面的解析中我們很清楚的看到了Dispatcher作為中轉站存在的意義,幾乎所有的執行緒轉換操作都是由Dispatcher來操控的,當然可能還有小夥伴們並不清楚它是怎麼運作的,怎麼進入子執行緒的,那是因為我們的講解還沒有進行到進入子執行緒的步驟而已,下面將會進一步的講解。
總結下Dispatcher中所包含的重要物件例項:
①:PicassoExecutorService執行緒池
②:downloader 下載器
③:針對DispatcherThread執行緒的DispatcherHandler處理器
④:NetworkBroadcastReceiver網路監聽器
⑤:mainThreadHandler主執行緒的Handler
⑥:儲存資料的集合:hunterMap,pausedActions,batch等
理解好了上面這些物件存在的意義將對下面的理解有著巨大的好處,請沒有完全理解的在仔細的閱讀一遍(非常重要)。
ok,知道了Dispatcher包含哪些重要的物件例項之後,讓我們再回到Picasso的Builder中,在build方法中最終返回的是一個Picasso的物件例項:
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
在Picasso的構造方法中值得我們只要注意的是針對requestHandlers的應用:
這裡把Picasso能都應用的RequestHandler都新增到集合中,然後根據具體的需求運用相對應的Handler進行業務的處理。
ok,到此Picasso中with方法中所做的事情已經完完全全的展示在您的面前了,相信您對這一步應用理解的很透徹了。
那麼來總結下,Picasso現在擁有了那些重要的物件例項:
①:dispatcher,重要性不言而喻,上面已充分的展示了它的重要性,作為中轉站,Picasso是必須要擁有了的。最終目的是讓我們的主執行緒進入子執行緒中去進行耗時的下載操作。
②:requestHandlers,它的存在是為了選擇一種請求處理方式,比如說,下載網路圖片需要使用NetworkRequestHandler這個請求器。
③:HANDLER,這是主執行緒的Handler,用來處理返回結果的,比如說圖片下載成功後要更新UI就是通過它來完成的,這會在後面圖片下載完成後更新UI時詳細講解
④:一些配置物件例項等,如:快取cache,圖片載入預設配置defaultBitmapConfig,狀態儲存stats等等。
load
with方法中主要是做了一個基礎的配置工作,比如Picasso的配置,Dispatcher的配置,這些都是非常重要的前提工作,只有做好了這些配置我們使用起來才能顯得毫不費勁。
下面我們就來看看它的應用吧。在load方法中需要我們傳遞一個引數,這個引數可以是Url,可以是一個path路徑,也可以是一個檔案,一個資源佈局等:
①:url public RequestCreator load(Uri uri) { return new RequestCreator(this, uri, 0); } ②:path public RequestCreator load(String path) { if (path == null) { return new RequestCreator(this, null, 0); } if (path.trim().length() == 0) { throw new IllegalArgumentException("Path must not be empty."); } return load(Uri.parse(path)); } ③:file public RequestCreator load(File file) { if (file == null) { return new RequestCreator(this, null, 0); } return load(Uri.fromFile(file)); } ④:resourceId public RequestCreator load(int resourceId) { if (resourceId == 0) { throw new IllegalArgumentException("Resource ID must not be zero."); } return new RequestCreator(this, null, resourceId); }
不管傳遞的是一個什麼引數,它都是返回一個請求構造器RequestCreator,我們來看看它的構造方法做了哪些事情:
主要是獲取到Picasso物件,並Builder模式構建一個Request中的Builder物件:
Builder(Uri uri, int resourceId, Bitmap.Config bitmapConfig) { this.uri = uri; this.resourceId = resourceId; this.config = bitmapConfig; }
這個Builder物件主要接受了一些引數資訊,包括url,資源佈局,和預設的圖片配置。
由於load方法返回的是一個RequestCreator物件,所以我們可以使用方法鏈的形式進行呼叫其他的方法,比如給請求新增佔點陣圖,異常圖,轉換器等等,具體的可以看原始碼。
ok,總體來說這load方法中主要是建立了一個RequestCreator物件,並且RequestCreator中構造了一個Request.Builder物件。
那麼來看看RequestCreator擁有哪些重要的物件例項:
①:picasso物件
②:Request.Builder物件例項data,它裡面包括我們請求的URL地址,資原始檔以及圖片預設配置。
into
在load方法中主要建立了一個RequestCreator物件,並獲取到了要載入的url/path/file/resourceId資源地址路徑,那麼接下來要做的就是在into方法中來載入圖片了。先來看看into方法中做了哪些事情:
public void into(ImageView target, Callback callback) { long started = System.nanoTime(); checkMain(); if (target == null) { throw new IllegalArgumentException("Target must not be null."); } if (!data.hasImage()) { picasso.cancelRequest(target); if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } return; } if (deferred) { if (data.hasSize()) { throw new IllegalStateException("Fit cannot be used with resize."); } int width = target.getWidth(); int height = target.getHeight(); if (width == 0 || height == 0) { if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); } Request request = createRequest(started); String requestKey = createKey(request); if (shouldReadFromMemoryCache(memoryPolicy)) { Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey); if (bitmap != null) { picasso.cancelRequest(target); setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled); if (picasso.loggingEnabled) { log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY); } if (callback != null) { callback.onSuccess(); } return; } } if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); picasso.enqueueAndSubmit(action); }
如上原始碼可以知道into還是做了很多的事情,下面一一解析:
①:checkMain():首先檢查主否在主執行緒執行,如果不是在主執行緒就會丟擲一個應該在主執行緒執行的異常:throw new IllegalStateException(“Method call should happen from the main thread.”);這說明到這一步還是在主執行緒執行的。
②:data.hasImage():這裡的data是在之前初始化的Request.Builder物件,它裡面包含url地址,resourceId和預設配置,這裡是判斷uri或resourceId是否為空為0,如果是的話就取消imageview的請求:picasso.cancelRequest(target);
③:deferred:延遲,如果需要延遲的話就會得到執行,然後會去獲取data中圖片的大小,如果沒有的話,就得到target的寬高來重新設定要載入的圖片的尺寸:data.resize(width, height);
④:createRequest:在data.build()方法中建立Request物件,該物件將會包含uri或resourceId以及預設圖片config。然後在得到的Request物件後進行轉換,該轉換主要是與我們在Picasso構建時是否自定義了RequestTransformer有關。
⑤:createKey:它主要的是返回已String型別key,主要目的是建立ImageView和key的關聯,可以通過key來獲取到Imageview的狀態,比如說是否已快取
⑥:shouldReadFromMemoryCache:看方法名也能知道,它的作用是是否從記憶體快取中讀取資料,如果是的話,就從快取中讀取資料,假如獲取到資料則會取消當前ImageView的請求並直接給它設定Bitmap,而且如果有回撥的話將會回撥成功的方法。
⑦:ImageViewAction:構建一個ImageViewAction物件,值得注意的是在構建物件時,會把ImageView新增到RequestWeakReference進行儲存,以便於使用時查詢,RequestWeakReference是一個WeakReference型別的弱引用。同時ImageViewAction也是在完成圖片載入時真正更新UI的關鍵類,這在後面會進行詳細講解。
⑧:enqueueAndSubmit:呼叫picasso的enqueueAndSubmit方法進行提交任務。
在來看submit方法的操作:
void submit(Action action) { dispatcher.dispatchSubmit(action); }
在這裡再次使用到了dispatcher任務分發器,把我們的任務action提交到Dispatcher中進行處理。然後在來看下dispatchSubmit方法:
void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); }
由Dispatcher的構造方法我們可以知道此時的handler是DispatcherHandler,那麼我們看下它是怎麼處理我們的任務行為的:
case REQUEST_SUBMIT: { Action action = (Action) msg.obj; dispatcher.performSubmit(action); break;
在handleMessage中直接獲取到我們的任務行為Action,然後呼叫performSubmit方法:
void performSubmit(Action action, boolean dismissFailed) { if (pausedTags.contains(action.getTag())) { pausedActions.put(action.getTarget(), action); if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_PAUSED, action.request.logId(), "because tag '" + action.getTag() + "' is paused"); } return; } BitmapHunter hunter = hunterMap.get(action.getKey()); if (hunter != null) { hunter.attach(action); return; } if (service.isShutdown()) { if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_IGNORED, action.request.logId(), "because shut down"); } return; } hunter = forRequest(action.getPicasso(), this, cache, stats, action); hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter); if (dismissFailed) { failedActions.remove(action.getTarget()); } if (action.getPicasso().loggingEnabled) { log(OWNER_DISPATCHER, VERB_ENQUEUED, action.request.logId()); } }
performSubmit方法是真正意義上的任務提交的具體地方,我們來解讀下它的原始碼:
①:首先根據action的標誌來查詢是否已存在於暫停列表中,如果存在將會把action存放到pausedActions的集合列表中,以便等待喚醒請求。
②:然後通過action的key從hunterMap集合中查詢是否已存在該hunterMap的請求項,如果存在將會呼叫hunter的attach方法,進行合併請求,避免一個ImageView進行多次的重複請求:
把action存放到ArrayList當中,如果有相同的action根據ArrayList的不重複性將會儲存一個action,並且更新新的屬性值priority。
③:當執行緒池service沒有關閉的時候,通過forRequest方法獲取一個Runnable型別的BitmapHunter執行緒,來看下forRequest的原始碼:
分別從action和picasso獲取到Request請求和所有的requestHandlers請求處理器,然後遍歷所有的請求器獲取每一個請求處理器,呼叫canHandleRequest嘗試看是否該處理器能夠處理,來看下canHandleRequest方法:
public abstract boolean canHandleRequest(Request data);
它是一個抽象方法,需要到子類去查詢,而實現它的子類都是根據以下的約束條件來判斷是否可以處理該請求的:
String scheme = data.uri.getScheme();
也就是說通過url地址的Scheme約束條件進行判斷的,而以我們現在的action中的Request獲取到url,是以http/https開頭的,那麼來看下NetworkRequestHandler中的canHandleRequest原始碼:
private static final String SCHEME_HTTP = "http"; private static final String SCHEME_HTTPS = "https"; @Override public boolean canHandleRequest(Request data) { String scheme = data.uri.getScheme(); return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme)); }
可以看出正好的匹配,由此得出結論,將要處理我們請求資料的將是NetworkRequestHandler處理器。
匹配好了請求處理器,將會返回一個BitmapHunter的執行緒,獲取到執行緒之後會線上程池進行開啟執行緒,並把該執行緒存放到hunterMap執行緒集合中以便多次請求可以合併相同的執行緒:
hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter);
ok,現在可以到我們的執行緒池PicassoExecutorService中看看它到底是怎麼執行的,submit原始碼如下:
@Override public Future<?> submit(Runnable task) { PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task); execute(ftask); return ftask; }
用把我們的執行緒task封裝到PicassoFutureTask 中,PicassoFutureTask 是一個更便於我們控住處理的執行緒,然後呼叫execute開啟執行緒,之後會把我們的執行緒轉移到addWorker方法中,在addWorker中開啟執行緒start:
boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; }
上述原始碼是執行在ThreadPoolExecutor執行緒池中的,可以看下原始碼。
當執行緒開啟後,在哪執行呢?
我們知道我們的封裝的執行緒最初始的是BitmapHunter,那麼我們就到它裡面來看看是怎麼執行的:
在BitmapHunter的run方法中,先修改執行緒名稱,然後執行hunt方法,把執行結果存放到result中,那我們先看看hunt方法是怎麼執行的:
Bitmap hunt() throws IOException { Bitmap bitmap = null; if (shouldReadFromMemoryCache(memoryPolicy)) { bitmap = cache.get(key); if (bitmap != null) { stats.dispatchCacheHit(); loadedFrom = MEMORY; if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache"); } return bitmap; } } data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; RequestHandler.Result result = requestHandler.load(data, networkPolicy); if (result != null) { loadedFrom = result.getLoadedFrom(); exifRotation = result.getExifOrientation(); bitmap = result.getBitmap(); // If there was no Bitmap then we need to decode it from the stream. if (bitmap == null) { InputStream is = result.getStream(); try { bitmap = decodeStream(is, data); } finally { Utils.closeQuietly(is); } } } if (bitmap != null) { if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_DECODED, data.logId()); } stats.dispatchBitmapDecoded(bitmap); if (data.needsTransformation() || exifRotation != 0) { synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifRotation != 0) { bitmap = transformResult(data, bitmap, exifRotation); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId()); } } if (data.hasCustomTransformations()) { bitmap = applyCustomTransformations(data.transformations, bitmap); if (picasso.loggingEnabled) { log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations"); } } } if (bitmap != null) { stats.dispatchBitmapTransformed(bitmap); } } } return bitmap; }
解析下原始碼:
①:還是首先獲取到action行為對應key,通過key從快取cache中查詢bitmap是否存在,如果存在修改stats狀態並直接的把bitmap返回。
②:如果快取中不存在則是要去網路載入requestHandler.load();我們通過上面的分析知道能處理當前requesr的requestHandler是NetworkRequestHandler,那麼我們去NetworkRequestHandler的load方法中檢視:
這裡很清晰的可以看到是直接呼叫downloader的load方法,而我們的downloader在Picasso構建Builder的時候也很清晰的說明是UrlConnectionDownloader,那麼在去UrlConnectionDownloader的load方法看看:
protected HttpURLConnection openConnection(Uri path) throws IOException { HttpURLConnection connection = (HttpURLConnection) new URL(path.toString()).openConnection(); connection.setConnectTimeout(Utils.DEFAULT_CONNECT_TIMEOUT_MILLIS); connection.setReadTimeout(Utils.DEFAULT_READ_TIMEOUT_MILLIS); return connection; } @Override public Response load(Uri uri, int networkPolicy) throws IOException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { installCacheIfNeeded(context); } HttpURLConnection connection = openConnection(uri); connection.setUseCaches(true); if (networkPolicy != 0) { String headerValue; if (NetworkPolicy.isOfflineOnly(networkPolicy)) { headerValue = FORCE_CACHE; } else { StringBuilder builder = CACHE_HEADER_BUILDER.get(); builder.setLength(0); if (!NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) { builder.append("no-cache"); } if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) { if (builder.length() > 0) { builder.append(','); } builder.append("no-store"); } headerValue = builder.toString(); } connection.setRequestProperty("Cache-Control", headerValue); } int responseCode = connection.getResponseCode(); if (responseCode >= 300) { connection.disconnect(); throw new ResponseException(responseCode + " " + connection.getResponseMessage(), networkPolicy, responseCode); } long contentLength = connection.getHeaderFieldInt("Content-Length", -1); boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE)); return new Response(connection.getInputStream(), fromCache, contentLength); }
這裡的程式碼相信我們都很熟悉,就是構建一個HttpURLConnection 去進行網路請求,當然還有就是根據networkPolicy進行一些網路快取的策略。最後把結果存放到Response物件中。
然後NetworkRequestHandler的load方法又會從Response物件中獲取資料,並把它存放到Result物件中。然後返回給BitmapHunter中hunt方法的RequestHandler.Result result中,從result中獲取輸入流,解析輸入流轉化為Bitmap並返回。
到此我們已從網路中下載了資料,並轉化為bitmap了,然後在返回我們的BitmapHunter中的run方法中,在run方法中我們獲取到了bitmap,然後呼叫dispatcher進行事物分發,成功獲取則呼叫dispatchComplete,否則呼叫dispatchFailed。
下面我們看看dispatcher中的dispatchComplete方法:
void dispatchComplete(BitmapHunter hunter) { handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter)); }
同樣的道理,這裡的handler還是DispatcherHandler,那麼來看看它的處理:
case HUNTER_COMPLETE: { BitmapHunter hunter = (BitmapHunter) msg.obj; dispatcher.performComplete(hunter); break; }
轉到dispatcher的performComplete方法中:
這裡首先把我們的結果儲存在cache快取中,然後從hunterMap集合中移除BitmapHunter對應的key,原因是請求已完成。然後呼叫 batch(hunter);方法:
首先判斷BitmapHunter是否已取消,然後把BitmapHunter存放在一個List< BitmapHunter > batch集合中,最後通過DispatcherHandler傳送一個空的延遲訊息,目的是為了延遲下下一個網路載入以便處理當前的bitmap工作,來看下它是怎麼處理的:
case HUNTER_DELAY_NEXT_BATCH: { dispatcher.performBatchComplete(); break; }
進入performBatchComplete中檢視:
void performBatchComplete() { List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch); batch.clear(); mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy)); logBatch(copy); }
performBatchComplete中首先獲取存放BitmapHunter的集合,然後呼叫mainThreadHandler傳送訊息。
還記得mainThreadHandler是怎麼嗎?
在Picasso類中構建Builder的build方法中,建立一個Dispatcher物件,裡面傳進一個HANDLER的引數,請看:
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
這個HANDLER就是mainThreadHandler。
那麼它傳送的訊息是怎麼處理的呢?
獲取到BitmapHunter集合進行遍歷,然後直接呼叫Picasso中的complete方法:
void complete(BitmapHunter hunter) { Action single = hunter.getAction(); List<Action> joined = hunter.getActions(); boolean hasMultiple = joined != null && !joined.isEmpty(); boolean shouldDeliver = single != null || hasMultiple; if (!shouldDeliver) { return; } Uri uri = hunter.getData().uri; Exception exception = hunter.getException(); Bitmap result = hunter.getResult(); LoadedFrom from = hunter.getLoadedFrom(); if (single != null) { deliverAction(result, from, single); } if (hasMultiple) { //noinspection ForLoopReplaceableByForEach for (int i = 0, n = joined.size(); i < n; i++) { Action join = joined.get(i); deliverAction(result, from, join); } } if (listener != null && exception != null) { listener.onImageLoadFailed(this, uri, exception); } }
在complete方法中,直接從BitmapHunter中獲取原已封裝的Action,Uri,Bitmap等等資料資訊,然後呼叫deliverAction方法:
這裡進行一系列的判斷,當action沒有取消,並且Bitmap不為空時,將會呼叫action.complete(result, from);來完成操作。
那還記得我們在建立action時的操作嗎?
Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade);
那麼很清晰的就知道我們的Action 其實就是ImageViewAction呢
那麼我們來看下ImageViewAction的complete是怎麼操作的呢?
target就是我們在建立ImageViewAction時傳遞的ImageView,現在獲取到它,然後呼叫PicassoDrawable.setBitmap方法來完成設定圖片:
當我們看到target.setImageDrawable(drawable);時,是不是終於鬆了一口氣呢,終於看到了為ImageView設定圖片的資訊了。
好了,到了這一步整個Picasso載入圖片的原始碼執行流程就已完全的解析完畢了,相信您可以非常清楚的瞭解了整個框架的載入過程,同時我也相信可能很少人能做到我這麼詳細的完完全全的分析整個執行過程,而且整個過程中並沒有給大家留下什麼盲點,是真真正正的原始碼大解析。
各位如果還有哪裡不明白的,或是我這裡講的還不夠透徹,亦或是講錯了的地方請留言指正,讓我們共同進步,謝謝
相關文章
- 圖片載入框架Picasso - 原始碼分析框架原始碼
- 圖片載入框架Picasso原始碼分析框架原始碼
- Android圖片載入框架Picasso原始碼分析(基於Picasso 2.71828)Android框架原始碼
- Android圖片載入庫Picasso原始碼分析Android原始碼
- Picasso的使用和原始碼解析原始碼
- 圖片載入框架-Picasso最詳細的使用指南框架
- Picasso 載入圖片的流程分析
- Android 圖片載入框架Android框架
- Android Volley 原始碼解析(三),圖片載入的實現Android原始碼
- Android圖片載入框架Fresco使用詳解Android框架
- Android圖片載入的框架Fresco使用詳解Android框架
- android glide圖片載入框架AndroidIDE框架
- Picasso-原始碼解析(二)原始碼
- Picasso-原始碼解析(三)原始碼
- Picasso-原始碼解析(一)原始碼
- 圖片載入利器之Picasso(五)查漏補缺
- Android ImageLoader框架之圖片載入與載入策略Android框架
- 影片直播原始碼,載入gif圖片原始碼
- [原]Android官方圖片載入利器BitmapFun解析Android
- 詳談高大上的圖片載入框架Glide -原始碼篇框架IDE原始碼
- Android 框架練成 教你打造高效的圖片載入框架Android框架
- 小說APP原始碼的圖片載入方式,懶載入和預載入的實現APP原始碼
- Android 載入大圖片時報OOM的解決方案(原始碼)AndroidOOM原始碼
- 簡單說說我最常用的圖片載入庫 Picasso
- flutter圖片元件原始碼解析Flutter元件原始碼
- DialogFragment使用到原始碼完全解析Fragment原始碼
- android 載入大量圖片Android
- Android圖片載入-Glide4.0框架封裝AndroidIDE框架封裝
- Google推薦的圖片載入庫Glide於Picasso比較GoIDE
- 圖片預載入和懶載入
- 視訊直播app原始碼,Android RecyclerView 列表載入圖片寬高適配APP原始碼AndroidView
- Android 載入大圖片,不壓縮圖片Android
- Android Volley框架原始碼解析Android框架原始碼
- spark的基本運算元使用和原始碼解析Spark原始碼
- Android 高效安全載入圖片Android
- 《Android原始碼設計模式解析與實戰》——物件導向的六大原則的圖片載入器原始碼Android原始碼設計模式物件
- 要優雅!Android中這樣載入大圖片和長圖片Android
- Android平滑圖片載入和快取庫 Glide 使用詳解Android快取IDE