前言
使用的是picasso最新版本
github地址:https://github.com/square/picasso
版本:2.71828
簡單例子
程式碼
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
塞入很多不同的引數。
由上圖其實我們可以發現,我們常用的一些鏈式方法,如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
來實現的。
總結
後面還會繼續深入。