Picasso-原始碼解析(一)
前言
使用的是picasso最新版本
github地址:https://github.com/square/picasso
版本: 2.71828
簡單例子

image.png
程式碼
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Picasso.get().setIndicatorsEnabled(true) Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest) Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest2) }
程式碼很簡單,令大家比較驚訝的應該是左上角的藍三角,其實原圖是沒有的,由於我加入了 Picasso.get().setIndicatorsEnabled(true)
,打開了指示標誌。
這裡先直接說明下代表的意思,後面我們再慢慢深入。
Picasso.java
public enum LoadedFrom { //記憶體載入,綠色 MEMORY(Color.GREEN), //磁碟載入,藍色 DISK(Color.BLUE), //網路載入,紅色 NETWORK(Color.RED); final int debugColor; LoadedFrom(int debugColor) { this.debugColor = debugColor; } }
一般來說,絕大多數的圖片框架都是三級快取,Picasso也不例外。 Glide
, Fresco
我還未深入瞭解,但是 Picasso
這個標識還是很有用的。很容易讓我們能夠明白是哪種載入方式。
先簡單的說明下這是如何去實現的。
PicassoDrawable.java
@Override public void draw(Canvas canvas) { if (!animating) { super.draw(canvas); } else { float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION; if (normalized >= 1f) { animating = false; placeholder = null; super.draw(canvas); } else { if (placeholder != null) { placeholder.draw(canvas); } // setAlpha will call invalidateSelf and drive the animation. int partialAlpha = (int) (alpha * normalized); super.setAlpha(partialAlpha); super.draw(canvas); super.setAlpha(alpha); } } //前面都是繪製原圖的 if (debugging) { //這裡判斷下,繪製下標識 drawDebugIndicator(canvas); } } private void drawDebugIndicator(Canvas canvas) { DEBUG_PAINT.setColor(WHITE); Path path = getTrianglePath(0, 0, (int) (16 * density)); canvas.drawPath(path, DEBUG_PAINT); //根據載入方式 DEBUG_PAINT.setColor(loadedFrom.debugColor); path = getTrianglePath(0, 0, (int) (15 * density)); canvas.drawPath(path, DEBUG_PAINT); }
原始碼解析
前面只是簡單的介紹了一下Picasso的一個小功能,下面還是通過上面那個簡單的載入圖片程式碼,一步步跟入原始碼,來介紹下是如何實現圖片載入的,如何做到三級快取的。
Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(ivTest)
-
get
Picasso.java
public static Picasso get() { if (singleton == null) { synchronized (Picasso.class) { if (singleton == null) { if (PicassoProvider.context == null) { throw new IllegalStateException("context == null"); } singleton = new Builder(PicassoProvider.context).build(); } } } return singleton; } public Picasso build() { Context context = this.context; if (downloader == null) { downloader = new OkHttp3Downloader(context); } if (cache == null) { cache = new LruCache(context); } if (service == null) { service = new PicassoExecutorService(); } if (transformer == null) { transformer = RequestTransformer.IDENTITY; } Stats stats = new Stats(cache); Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats); return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, loggingEnabled); }
非常簡單的一個單例模式,和建造者模式。單例模式就不過多說了,這裡主要介紹下建造者模式,一般來說對於引數比較多的構造方法,使用建造者模式,就可以直接使用鏈式的方式,來配置物件。
這裡直接使用 Picasso.get
其實是獲取了預設的一個 Picasso
物件,然後幫你預設的配置了 LruCache
, PicassoExecutorService
, RequestTransformer
, OkHttp3Downloader
, Stats
, Dispatcher
。
很顯然,一般來說,肯定是會提供一個自定義的方式,不然就太low了。
public static void setSingletonInstance(@NonNull Picasso picasso) { if (picasso == null) { throw new IllegalArgumentException("Picasso must not be null."); } synchronized (Picasso.class) { if (singleton != null) { throw new IllegalStateException("Singleton instance already exists."); } singleton = picasso; } }
你可以使用 Picasso.Builder
先自己構建一個 Picasso
物件,然後再呼叫這個方法,接下來就可以使用 Picasso.get()
來獲取自己的配置的單例了。
-
load
load方法有很多過載,這裡還是以String為例子。
public RequestCreator load(@Nullable 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)); } public RequestCreator load(@Nullable Uri uri) { return new RequestCreator(this, uri, 0); }
很顯然,load方法只是為了獲取一個 RequestCreator
物件。
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); }
而 RequestCreator
裡面最重要的,其實就是 data
也就是一個 Request.Builder
,從這裡其實我們很明顯的可以看出, RequestCreator
,顧名思義,就是為了建立一個 Request
,最終的 Request
肯定是由 data.build
生成的。但是目前只是new了一個 Request.Builder
物件,並沒有呼叫。這是因為後面我們還需要往 Request.Builder
塞入很多不同的引數。

image.png
由上圖其實我們可以發現,我們常用的一些鏈式方法,如 centerCrop
等,其實就是呼叫了 Request.Builder
物件的方法,只是為了構建一個 Request
.
-
into
這裡才是真正發起請求的地方。
public void into(ImageView target) { into(target, null); } public void into(ImageView target, Callback callback) { long started = System.nanoTime(); //判斷下是否為主執行緒 checkMain(); if (target == null) { throw new IllegalArgumentException("Target must not be null."); } //如果uri為空或者resId為0,則直接取消請求,設定為placeholder圖片 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); } //簡單理解,就是呼叫了data.build(),生成一個Request Request request = createRequest(started); //這裡通過request生成一個String,用來後面key-value儲存圖片在LruCache中 String requestKey = createKey(request); if (shouldReadFromMemoryCache(memoryPolicy)) { //如果前面請求過了,會快取到記憶體,這邊再請求,還是會生成了相同的key,直接從cache中獲取到了Bitmap 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; } } //沒有從記憶體中獲取到快取,先設定placeholder圖片 if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } //建立一個action Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); //提交一個action picasso.enqueueAndSubmit(action); }
這裡其實非常簡單的分析了下。
這裡面有2步單獨拿出來說。
- deferred的作用
public RequestCreator fit() { deferred = true; return this; } /** Internal use only. Used by {@link DeferredRequestCreator}. */ RequestCreator unfit() { deferred = false; return this; } .... if (deferred) { if (data.hasSize()) { throw new IllegalStateException("Fit cannot be used with resize."); } int width = target.getWidth(); int height = target.getHeight(); //如果說imageview本身已經可以獲取到寬高了,都不是0,那麼就直接resize一下圖片,如果說有一個是0,說明這個Imageview可能還沒有佈局完成,還沒有自己的寬高,那麼就在原來的`RequestCreator`外面再包了一層`DeferredRequestCreator ` if (width == 0 || height == 0) { if (setPlaceholder) { setPlaceholder(target, getPlaceholderDrawable()); } picasso.defer(target, new DeferredRequestCreator(this, target, callback)); return; } data.resize(width, height); }
用過了 fit
方法的人應該知道,呼叫後可以適配 ImageView
的尺寸,這裡就是實現方式
下面我們來看看 DeferredRequestCreator
是如何實現的
DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) { this.creator = creator; this.target = new WeakReference<>(target); this.callback = callback; //實現很簡單,就是給ImageView設定下監聽 target.addOnAttachStateChangeListener(this); if (target.getWindowToken() != null) { onViewAttachedToWindow(target); } } @Override public void onViewAttachedToWindow(View view) { view.getViewTreeObserver().addOnPreDrawListener(this); } //這裡才是最關鍵的部分 @Override public boolean onPreDraw() { ImageView target = this.target.get(); if (target == null) { return true; } ViewTreeObserver vto = target.getViewTreeObserver(); if (!vto.isAlive()) { return true; } int width = target.getWidth(); int height = target.getHeight(); if (width <= 0 || height <= 0) { return true; } target.removeOnAttachStateChangeListener(this); vto.removeOnPreDrawListener(this); this.target.clear(); //獲取到了ImageView的寬高後,呼叫resize重新設定了下寬高。 this.creator.unfit().resize(width, height).into(target, callback); return true; }
- 真正去載入圖片的地方
public void into(ImageView target, Callback callback) { ... Action action = new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade); picasso.enqueueAndSubmit(action); } /**下面是每一步的方法***/ void enqueueAndSubmit(Action action) { Object target = action.getTarget(); if (target != null && targetToAction.get(target) != action) { // This will also check we are on the main thread. cancelExistingRequest(target); targetToAction.put(target, action); } submit(action); } void submit(Action action) { dispatcher.dispatchSubmit(action); } void dispatchSubmit(Action action) { handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action)); } @Override public void handleMessage(final Message msg) { switch (msg.what) { case REQUEST_SUBMIT: { Action action = (Action) msg.obj; dispatcher.performSubmit(action); break; } ... void performSubmit(Action action) { performSubmit(action, true); }
前面其實講到了這裡,我們再往後繼續。從 enqueueAndSubmit
一步步往下,雖然呼叫了很多方法,但是最終,其實就是呼叫 Dispatcher
中的 performSubmit
方法。下面我們來具體分析下這個方法。
void performSubmit(Action action, boolean dismissFailed) { ... BitmapHunter hunter = hunterMap.get(action.getKey()); if (hunter != null) { hunter.attach(action); return; } ... //一開始hunterMap肯定不包含action的key,所以會建立一個BitmapHunter hunter = forRequest(action.getPicasso(), this, cache, stats, action); //其實我們會發現BitmapHunter是一個Runnable,service是ExecutorService,可以理解為一個執行緒池,這裡就直接執行一個Runnable hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter); ... } //通過傳入的引數生成一個BitmapHunter static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) { Request request = action.getRequest(); List<RequestHandler> requestHandlers = picasso.getRequestHandlers(); for (int i = 0, count = requestHandlers.size(); i < count; i++) { RequestHandler requestHandler = requestHandlers.get(i); //最重要的地方在這裡,遍歷所有的requestHandler,看哪個requestHandler能夠處理request,後面再詳細介紹 if (requestHandler.canHandleRequest(request)) { return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler); } } return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER); }
為了讓後面我們可以更好的理解,我們先回過頭來,看一下requestHandlers是什麼東西,為什麼要先找出能夠處理當前 Request
的 RequestHandler
.
一直往前找發現是在Picasso的構造方法裡面初始化的
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener, RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats, Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) { ... List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount); //resource圖片處理,比如R.drawable這種 allRequestHandlers.add(new ResourceRequestHandler(context)); if (extraRequestHandlers != null) { //自定義處理 allRequestHandlers.addAll(extraRequestHandlers); } //聯絡人圖片處理 allRequestHandlers.add(new ContactsPhotoRequestHandler(context)); //媒體資源處理 allRequestHandlers.add(new MediaStoreRequestHandler(context)); //流資源處理 allRequestHandlers.add(new ContentStreamRequestHandler(context)); //asset資源處理 allRequestHandlers.add(new AssetRequestHandler(context)); //檔案資源處理 allRequestHandlers.add(new FileRequestHandler(context)); //網路資源處理 allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats)); ... }
都是通過load方法之後的引數來判斷的。我們這裡以 NetworkRequestHandler
為例
@Override public boolean canHandleRequest(Request data) { String scheme = data.uri.getScheme(); return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme)); }
如果說uri是 http
或者 https
就可以由 NetworkRequestHandler
來處理。
那麼我們繼續回到剛才那個地方
void performSubmit(Action action, boolean dismissFailed) { ... hunter = forRequest(action.getPicasso(), this, cache, stats, action); //其實我們會發現BitmapHunter是一個Runnable,service是ExecutorService,可以理解為一個執行緒池,這裡就直接執行一個Runnable hunter.future = service.submit(hunter); hunterMap.put(action.getKey(), hunter); ... }
獲取到可以處理 RequestHandler
之後建立了一個 BitmapHunter
,然後呼叫 service.submit
最終其實是呼叫 Runnable
的 run
方法,我們繼續跟入。
@Override public void run() { try { ... 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; } } networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy; //前面其實我們已經分析過,requestHandler其實是NetworkRequestHandler,等下單獨提出load方法來講 RequestHandler.Result result = requestHandler.load(data, networkPolicy); if (result != null) { loadedFrom = result.getLoadedFrom(); //獲取下exif格式資訊,一般情況用不到,這裡不深入 exifOrientation = result.getExifOrientation(); //獲取到真正的bitmap bitmap = result.getBitmap(); if (bitmap == null) { Source source = result.getSource(); try { bitmap = decodeStream(source, data); } finally { try { source.close(); } catch (IOException ignored) { } } } } //下面一大串其實是對原來的圖片進行一些變換,這裡先不深入 if (bitmap != null) { ... if (data.needsTransformation() || exifOrientation != 0) { synchronized (DECODE_LOCK) { if (data.needsMatrixTransform() || exifOrientation != 0) { bitmap = transformResult(data, bitmap, exifOrientation); 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;
下面我們還是具體再看看 NetworkRequestHandler
@Override public Result load(Request request, int networkPolicy) throws IOException { okhttp3.Request downloaderRequest = createRequest(request, networkPolicy); Response response = downloader.load(downloaderRequest); ResponseBody body = response.body(); if (!response.isSuccessful()) { body.close(); throw new ResponseException(response.code(), request.networkPolicy); } //從這裡可以看出磁碟快取 Picasso.LoadedFrom loadedFrom = response.cacheResponse() == null ? NETWORK : DISK; if (loadedFrom == DISK && body.contentLength() == 0) { body.close(); throw new ContentLengthException("Received response with 0 content-length header."); } if (loadedFrom == NETWORK && body.contentLength() > 0) { stats.dispatchDownloadFinished(body.contentLength()); } return new Result(body.source(), loadedFrom); }
其實這裡最關鍵的部分就是 response.cacheResponse()
這一句程式碼。因為之前我一直以為 Picasso
使用的是 DiskLruCache
來進行磁碟快取。但是一直找不到實現的地方。一直找到這裡才恍然大悟, Picasso
的磁碟快取是利用 http
協議中的 cache-control
去實現的。
然後使用的其實是 Okhttp3
實現了 http協議
,其中磁碟快取確實也是用 DiskLruCache
來實現的。
總結
後面還會繼續深入。