Picasso是一款圖片加載庫出自Square葛闷,以小巧功能齊全出名,本文將從源碼解析Picasso的整個加載流程双藕。
時序圖為筆者根據(jù)整個調(diào)用流程所畫淑趾,有誤私聊筆者進行修改
整個流程
- 調(diào)用Picasso創(chuàng)建一個RequestCreator,并返回
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);
}
- RequestCreator設置請求參數(shù)忧陪,url扣泊、資源id驳概、transform、fix等
- 調(diào)用RequestCreator的into旷赖,設置了fix的情況下顺又,如果控件已經(jīng)獲取到尺寸就創(chuàng)建Action,否則就創(chuàng)建一DeferredRequestCreator(延遲請求)等孵,獲取到后重新into
class DeferredRequestCreator implements OnPreDrawListener, OnAttachStateChangeListener {
private final RequestCreator creator;
@VisibleForTesting final WeakReference<ImageView> target;
@VisibleForTesting Callback callback;
DeferredRequestCreator(RequestCreator creator, ImageView target, Callback callback) {
this.creator = creator;
this.target = new WeakReference<>(target);
this.callback = callback;
target.addOnAttachStateChangeListener(this);
// Only add the pre-draw listener if the view is already attached.
// See: https://github.com/square/picasso/issues/1321
if (target.getWindowToken() != null) {
onViewAttachedToWindow(target);
}
}
@Override public void onViewAttachedToWindow(View view) {
view.getViewTreeObserver().addOnPreDrawListener(this);
}
@Override public void onViewDetachedFromWindow(View view) {
view.getViewTreeObserver().removeOnPreDrawListener(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();
//控件已經(jīng)獲取到寬高稚照,重新into
this.creator.unfit().resize(width, height).into(target, callback);
return true;
}
void cancel() {
...省略
}
}
- 調(diào)用Picasso.enqueueAndSubmit(Action)->Dispatcher.performSubmit(Action)->ExecutorService.submit(BitmapHunter)將請求提交給線程池去處理
void performSubmit(Action action, boolean dismissFailed) {
//已暫停的,就結(jié)束了
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
if (action.getPicasso().loggingEnabled) {
Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_PAUSED, action.request.logId(),
"because tag '" + action.getTag() + "' is paused");
}
return;
}
//通過請求key獲取BitmapHunter核心類俯萌,這一步的主要作用是果录,一個圖片有兩個控價都需要使用她,
//避免重復請求咐熙。這里的attach方法就是將action弱恒,添加當前這個BitmapHunter的action列表中
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
if (service.isShutdown()) {
if (action.getPicasso().loggingEnabled) {
Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_IGNORED, action.request.logId(), "because shut down");
}
return;
}
//策略模式,選取可以請求該種類型
//根據(jù)傳入的action中的Request資源地址來判定使用那種請求處理器
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) {
Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_ENQUEUED, action.request.logId());
}
}
- BitmapHunter執(zhí)行run方法獲取圖片資源返弹,run方法調(diào)用hunt獲取bitmap,并執(zhí)行Transformation
@Override public void run() {
try {
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
}
...省略
}
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//讀取策略爪飘,是否可以讀取緩存
if (MemoryPolicy.shouldReadFromMemoryCache(memoryPolicy)) {
//根據(jù)請求key獲取緩存bitmap
bitmap = cache.get(key);
if (bitmap != null) {
//記錄緩存熱度
stats.dispatchCacheHit();
loadedFrom = Picasso.LoadedFrom.MEMORY;
if (picasso.loggingEnabled) {
Utils.log(Utils.OWNER_HUNTER, Utils.VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
}
}
//沒有緩存或者不允許讀取緩存义起,繼續(xù)往下執(zhí)行
networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
//請求處理器load圖片資源
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
Source source = result.getSource();
try {
//將流文件轉(zhuǎn)為bitmap
bitmap = decodeStream(source, data);
} finally {
try {
//noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
source.close();
} catch (IOException ignored) {
}
}
}
}
if (bitmap != null) {
if (picasso.loggingEnabled) {
Utils.log(Utils.OWNER_HUNTER, Utils.VERB_DECODED, data.logId());
}
//狀態(tài)更新
stats.dispatchBitmapDecoded(bitmap);
//是否需要transformation,必須調(diào)用RequestCreator的fix()方法师崎,默認的Transformation才會被調(diào)用默终。會根據(jù)獲取到控件的size調(diào)整圖
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
//默認的transformation,會根據(jù)控件大小調(diào)整bitmap
bitmap = transformResult(data, bitmap, exifOrientation);
if (picasso.loggingEnabled) {
Utils.log(Utils.OWNER_HUNTER, Utils.VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) {
//自定義的transformation,圓角圖片啥的變換
bitmap = applyCustomTransformations(data.transformations, bitmap);
if (picasso.loggingEnabled) {
Utils.log(Utils.OWNER_HUNTER, Utils.VERB_TRANSFORMED, data.logId(), "from custom transformations");
}
}
}
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}
從上面的源碼可知緩存邏輯:內(nèi)存-->本地緩存(由okhttp提供)-->網(wǎng)絡犁罩,Picasso一共提供了7圖片請求處理器齐蔽,這里著重介紹哈網(wǎng)絡處理器
NetworkRequestHandler.java
class NetworkRequestHandler extends RequestHandler {
private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";
private final Downloader downloader;
private final Stats stats;
NetworkRequestHandler(Downloader downloader, Stats stats) {
this.downloader = downloader;
this.stats = stats;
}
//這個處理器,可以處理那種類型的資源床估,scheme是http和https的
@Override public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}
//加載并返回result
@Override public Result load(Request request, int networkPolicy) throws IOException {
//創(chuàng)建okhttp request
Request downloaderRequest = createRequest(request, networkPolicy);
//OkHttp3Downloader的load方法去下載資源
Response response = downloader.load(downloaderRequest);
ResponseBody body = response.body();
if (!response.isSuccessful()) {
body.close();
throw new ResponseException(response.code(), request.networkPolicy);
}
//資源來源含滴,網(wǎng)絡/磁盤
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());
}
//返回結(jié)果
return new Result(body.source(), loadedFrom);
}
...省略
}
OkHttp3Downloader下載器源碼
public final class OkHttp3Downloader implements Downloader {
@VisibleForTesting final Call.Factory client;
private final Cache cache;
private boolean sharedClient = true;
//初始化獲設置緩存目錄默認:/data/data/包名/cache/picasso-cache下
public OkHttp3Downloader(final Context context) {
this(Utils.createDefaultCacheDir(context));
}
//初始化獲設置緩存
public OkHttp3Downloader(final File cacheDir) {
this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
}
//初始化獲設置緩存
public OkHttp3Downloader(final Context context, final long maxSize) {
this(Utils.createDefaultCacheDir(context), maxSize);
}
/**
* Create new downloader that uses OkHttp. This will install an image cache into the specified
* directory.
*
* @param cacheDir The directory in which the cache should be stored
* @param maxSize The size limit for the cache.
*/
public OkHttp3Downloader(final File cacheDir, final long maxSize) {
this(new OkHttpClient.Builder().cache(new Cache(cacheDir, maxSize)).build());
sharedClient = false;
}
//load執(zhí)行同步方法去加載網(wǎng)絡上的圖片,是否使用本地緩存的圖片顷窒,這里有okhttp去實現(xiàn)
//如果需要更換網(wǎng)絡訪問框架蛙吏,需要實現(xiàn)1.網(wǎng)絡下載圖片源哩; 2. 緩存圖片到本地鞋吉,下次請求時,返回緩存圖片
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
return client.newCall(request).execute();
}
...省略
}
- Dispatcher.dispatchComplete最終調(diào)用了performComplete方法
void performComplete(BitmapHunter hunter) {
//是否緩存到內(nèi)次
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
//移除進行時的bitmap請求
hunterMap.remove(hunter.getKey());
batch(hunter);
if (hunter.getPicasso().loggingEnabled) {
Utils.log(Utils.OWNER_DISPATCHER, Utils.VERB_BATCHED, Utils.getLogIdsForHunter(hunter), "for completion");
}
}
private void batch(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
if (hunter.result != null) {
//bitmap的預畫
hunter.result.prepareToDraw();
}
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
最終調(diào)用到了調(diào)用Picasso類的deliverAction方法
private void deliverAction(Bitmap result, LoadedFrom from, Action action, Exception e) {
if (action.isCancelled()) {//取消
return;
}
if (!action.willReplay()) {//將重試
targetToAction.remove(action.getTarget());
}
if (result != null) {
if (from == null) {
throw new AssertionError("LoadedFrom cannot be null.");
}
//調(diào)用action的complete
action.complete(result, from);
if (loggingEnabled) {
Utils.log(Utils.OWNER_MAIN, Utils.VERB_COMPLETED, action.request.logId(), "from " + from);
}
} else {
action.error(e);
if (loggingEnabled) {
Utils.log(Utils.OWNER_MAIN, Utils.VERB_ERRORED, action.request.logId(), e.getMessage());
}
}
}
Picasso提供了4種Actiong励烦,這里貼哈ImageViewAction
class ImageViewAction extends Action<ImageView> {
Callback callback;
ImageViewAction(Picasso picasso, ImageView imageView, Request data, int memoryPolicy,
int networkPolicy, int errorResId, Drawable errorDrawable, String key, Object tag,
Callback callback, boolean noFade) {
super(picasso, imageView, data, memoryPolicy, networkPolicy, errorResId, errorDrawable, key,
tag, noFade);
this.callback = callback;
}
//加載完成時
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
}
ImageView target = this.target.get();
if (target == null) {
return;
}
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
//設置bitmap到target谓着,也就是我們的目標view
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
//回調(diào)
if (callback != null) {
callback.onSuccess();
}
}
//加載失敗時
@Override public void error(Exception e) {
ImageView target = this.target.get();
if (target == null) {
return;
}
Drawable placeholder = target.getDrawable();
if (placeholder instanceof Animatable) {
((Animatable) placeholder).stop();
}
if (errorResId != 0) {
target.setImageResource(errorResId);
} else if (errorDrawable != null) {
target.setImageDrawable(errorDrawable);
}
if (callback != null) {
callback.onError(e);
}
}
//取消
@Override void cancel() {
super.cancel();
if (callback != null) {
callback = null;
}
}
}
Picasso加載流程就已經(jīng)解析完了,網(wǎng)絡和本地緩存都依賴OKhttp坛掠,我們整個項目圖片加載不多赊锚,項目中使用的OKhttp治筒,所以我選用了Picasso來減少代碼量。
Picasso需要優(yōu)化點
- 內(nèi)存緩存舷蒲,可以看到是通過requestKey+Bitmap存入LruCache耸袜,但是requestkey上帶有尺寸旋轉(zhuǎn)角度等參數(shù),也就是說同一張圖片牲平,因為尺寸等參數(shù)不同就會造成儲存了多張bitmap堤框,bitmap(吃內(nèi)存大戶啊)
Utils下的requestKey源碼
static String createKey(Request data, StringBuilder builder) {
if (data.stableKey != null) {
builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
builder.append(data.stableKey);
} else if (data.uri != null) {
String path = data.uri.toString();
builder.ensureCapacity(path.length() + KEY_PADDING);
builder.append(path);
} else {
builder.ensureCapacity(KEY_PADDING);
builder.append(data.resourceId);
}
builder.append(KEY_SEPARATOR);
if (data.rotationDegrees != 0) {
builder.append("rotation:").append(data.rotationDegrees);
if (data.hasRotationPivot) {
builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
}
builder.append(KEY_SEPARATOR);
}
if (data.hasSize()) {
builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
builder.append(KEY_SEPARATOR);
}
if (data.centerCrop) {
builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
} else if (data.centerInside) {
builder.append("centerInside").append(KEY_SEPARATOR);
}
if (data.transformations != null) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, count = data.transformations.size(); i < count; i++) {
builder.append(data.transformations.get(i).key());
builder.append(KEY_SEPARATOR);
}
}
return builder.toString();
}
解決方案:
按照地址生成key纵柿,保存bitmap最大的一張蜈抓,取出bitmap后發(fā)現(xiàn)比當前大,就Transformation昂儒。比請求的小沟使,就再去請求一次原圖(這里有磁盤緩存),Transformation后保存原圖
- 及時調(diào)用Picasso.shutdown()渊跋,此方法會停止Picasso和清理緩存腊嗡,只有在確定不需要使用Picasso時調(diào)用
Picasso如何防止傳入的View內(nèi)存泄露
- 弱引用View
- 引用隊列保留action,發(fā)現(xiàn)View被gc后取消請求
- Picasso實例化時啟動清理線程拾酝,初始化應用對象
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...省略
//創(chuàng)建引用隊列
this.referenceQueue = new ReferenceQueue<>();
this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
//啟動清理線程
this.cleanupThread.start();
}
- Action實例化時用WeakReference包裹View
Action(Picasso picasso, T target, Request request, int memoryPolicy, int networkPolicy,
int errorResId, Drawable errorDrawable, String key, Object tag, boolean noFade) {
//使用WeakReference持有View叽唱,并傳入引用隊列,當View被GC時當前RequestWeakReference對象會被放入referenceQueue中
this.target =
target == null ? null : new RequestWeakReference<>(this, target, picasso.referenceQueue);
...省略
}
- RequestWeakReference中有一個請求的Action
static class RequestWeakReference<M> extends WeakReference<M> {
//請求action
final Action action;
RequestWeakReference(Action action, M referent, ReferenceQueue<? super M> q) {
super(referent, q);
this.action = action;
}
}
-清理線程微宝,不停的從引用隊列中取出RequestWeakReference對象棺亭,回收RequestWeakReference對象
private static class CleanupThread extends Thread {
private final ReferenceQueue<Object> referenceQueue;
private final Handler handler;
CleanupThread(ReferenceQueue<Object> referenceQueue, Handler handler) {
//應用隊列
this.referenceQueue = referenceQueue;
this.handler = handler;
setDaemon(true);
setName(Utils.THREAD_PREFIX + "refQueue");
}
@Override public void run() {
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
while (true) {
try {
//回收RequestWeakReference對象,并發(fā)送消息給Handler取消Action
//remove超時時間1s蟋软,如果1s任然為null镶摘,則直接返回null
Action.RequestWeakReference<?> remove =
(Action.RequestWeakReference<?>) referenceQueue.remove(Utils.THREAD_LEAK_CLEANING_MS);
Message message = handler.obtainMessage();
if (remove != null) {
message.what = REQUEST_GCED;
message.obj = remove.action;
handler.sendMessage(message);
} else {
message.recycle();
}
} catch (InterruptedException e) {
break;
} catch (final Exception e) {
handler.post(new Runnable() {
@Override public void run() {
throw new RuntimeException(e);
}
});
break;
}
}
}
void shutdown() {
interrupt();
}
}
- Action取消請求
case REQUEST_GCED: {
Action action = (Action) msg.obj;
if (action.getPicasso().loggingEnabled) {
Utils.log(Utils.OWNER_MAIN, Utils.VERB_CANCELED, action.request.logId(), "target got garbage collected");
}
action.picasso.cancelExistingRequest(action.getTarget());
break;
}
整個清理流程:
- View被RequestWeakReference對象持有, RequestWeakReference被Action持有岳守,Action也被RequestWeakReference持有
- RequestWeakReference內(nèi)的持有對象被回收后凄敢,加入到引用隊列ReferenceQueue
- CleanupThread在Picasso創(chuàng)建時被啟動,不停的從ReferenceQueue對象中移除RequestWeakReference對象
- 在移除對象時得到RequestWeakReference中action對象湿痢,發(fā)送消息取消Action
- Action和RequestWeakReference是相互引用關系涝缝,其它地方都已經(jīng)釋放,所以都可以被gc了