Picasso是一款图片加载库出自Square,以小巧功能齐全出名,本文将从源码解析Picasso的整个加载流程。
时序图为笔者根据整个调用流程所画,有误私聊笔者进行修改
整个流程
- 调用Picasso创建一个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设置请求参数,url、资源id、transform、fix等
- 调用RequestCreator的into,设置了fix的情况下,如果控件已经获取到尺寸就创建Action,否则就创建一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();
//控件已经获取到宽高,重新into
this.creator.unfit().resize(width, height).into(target, callback);
return true;
}
void cancel() {
...省略
}
}
- 调用Picasso.enqueueAndSubmit(Action)->Dispatcher.performSubmit(Action)->ExecutorService.submit(BitmapHunter)将请求提交给线程池去处理
void performSubmit(Action action, boolean dismissFailed) {
//已暂停的,就结束了
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;
}
//策略模式,选取可以请求该种类型
//根据传入的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执行run方法获取图片资源,run方法调用hunt获取bitmap,并执行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)) {
//根据请求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;
}
}
//没有缓存或者不允许读取缓存,继续往下执行
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 {
//将流文件转为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());
}
//状态更新
stats.dispatchBitmapDecoded(bitmap);
//是否需要transformation,必须调用RequestCreator的fix()方法,默认的Transformation才会被调用。会根据获取到控件的size调整图
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifOrientation != 0) {
//默认的transformation,会根据控件大小调整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;
}
从上面的源码可知缓存逻辑:内存-->本地缓存(由okhttp提供)-->网络,Picasso一共提供了7图片请求处理器,这里着重介绍哈网络处理器
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 {
//创建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);
}
//资源来源,网络/磁盘
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);
}
...省略
}
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执行同步方法去加载网络上的图片,是否使用本地缓存的图片,这里有okhttp去实现
//如果需要更换网络访问框架,需要实现1.网络下载图片; 2. 缓存图片到本地,下次请求时,返回缓存图片
@NonNull @Override public Response load(@NonNull Request request) throws IOException {
return client.newCall(request).execute();
}
...省略
}
- Dispatcher.dispatchComplete最终调用了performComplete方法
void performComplete(BitmapHunter hunter) {
//是否缓存到内次
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);
}
}
最终调用到了调用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.");
}
//调用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);
//回调
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加载流程就已经解析完了,网络和本地缓存都依赖OKhttp,我们整个项目图片加载不多,项目中使用的OKhttp,所以我选用了Picasso来减少代码量。
Picasso需要优化点
- 内存缓存,可以看到是通过requestKey+Bitmap存入LruCache,但是requestkey上带有尺寸旋转角度等参数,也就是说同一张图片,因为尺寸等参数不同就会造成储存了多张bitmap,bitmap(吃内存大户啊)
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后发现比当前大,就Transformation。比请求的小,就再去请求一次原图(这里有磁盘缓存),Transformation后保存原图
- 及时调用Picasso.shutdown(),此方法会停止Picasso和清理缓存,只有在确定不需要使用Picasso时调用
Picasso如何防止传入的View内存泄露
- 弱引用View
- 引用队列保留action,发现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) {
...省略
//创建引用队列
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对象,并发送消息给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内的持有对象被回收后,加入到引用队列ReferenceQueue
- CleanupThread在Picasso创建时被启动,不停的从ReferenceQueue对象中移除RequestWeakReference对象
- 在移除对象时得到RequestWeakReference中action对象,发送消息取消Action
- Action和RequestWeakReference是相互引用关系,其它地方都已经释放,所以都可以被gc了