摘要:本文基于Picasso
的源码来分析图片加载及缓存的流程。
文章原创,允许转载,转载请注明出处。
一. 概述
Picasso
是Square
出品的一个非常精简的图片加载及缓存库,其主要特点包括:
- 易写易读的流式编程风格
- 中心事件分发器
- 拦截器式图片加载流
- 链式图片转换流
- 无效请求清除器
- 内存与磁盘双缓存策略
二. 创建及配置Picasso
1. 利用Picasso.Builder
配置Picasso
,主要配置项有:
// Application运行上下文
private final Context context;
// 图片网络下载器
private Downloader downloader;
// 图片加载线程管理服务
private ExecutorService service;
// 内存缓存
private Cache cache;
// 图片加载失败监听器
private Listener listener;
// 图片加载请求转换器
private RequestTransformer transformer;
// 图片加载请求处理器
private List<RequestHandler> requestHandlers;
// 图片格式配置
private Bitmap.Config defaultBitmapConfig;
// 图片加载来源显示开关,通常在Debug模式下使用
private boolean indicatorsEnabled;
// 日志开关
private boolean loggingEnabled;
context
必须配置,其他配置项可用默认参数如下所示:
1.1 默认Downloader
当应用依赖OkHttp
模块时,默认使用基于OkHttp
实现OkHttpDownloader
;反之则使用UrlConnectionDownloader
。Downloader
会设置磁盘缓存,默认缓存在应用Cache目录下,缓存设置为Cache的2%并且不小于5M且不大于50M;OkHttpDownloader
基于OkHttp
的缓存模块实现网络响应缓存,URLConnectionDownloader
利用HttpResponseCache
实现,而缓存策略由请求时传入的networkPolicy
控制。
1.2 默认内存缓存
内存缓存默认采用LruCache
实现,大小为应用总体内存的15%。
1.3 默认ExecutorService
默认ExecutorService
为PicassoExecutorService
:
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
该Service
的主要特点是:
- 可根据网络状态调整线程数量。在Wifi环境下设置为4线程,在4G环境下设置为3线程,在3G环境下设置为2线程,在2G环境下设置为1线程;
- 同时将提交的
Runnable
(Picasso
中为RequestHunter
)包装为PicassoFutureTask
,由于Service
本身设定为优先级队列,因此PicassoFutureTask
主要实现任务优先级判定规则:- 根据
RequestHunter
的priority
实现优先级队列; - 优先级一样的
RequestHunter
根据其递增序号实现先入先出队列
- 根据
1.4 默认RequestTransformer
不做任何转换。
1.5 默认Dispatcher
// 事件分发Handler
final DispatcherThread dispatcherThread;
final Handler handler;
// 由Picasso传入
final Context context;
final ExecutorService service;
final Downloader downloader;
final Cache cache;
// Action包装一个图片请求Request,可以理解为一个图片请求任务,实现了图片加载成功或失败的回调处理
// key为Action.key, value为图片请求Runnable,存储所有图片请求
final Map<String, BitmapHunter> hunterMap;
// key为Action.Target, value为Action,记录失败请求
final Map<Object, Action> failedActions;
// key为Action.Target, value为Action,记录暂停请求
final Map<Object, Action> pausedActions;
// key为Action.tag, 被暂停的tag,tag标识一组相关请求
final Set<Object> pausedTags;
// 主线程Handler,用于转发如UI刷新等需在主线程处理的事件
final Handler mainThreadHandler;
// 统计图片加载及缓存数据,生成内存快照
final Stats stats;
// 记录成功或失败的BitmapHunter,作为发送到主线程的事件的参数,用于回调complete或error
final List<BitmapHunter> batch;
Dispatcher
的核心在于利用DispatchThread
实现统一的事件分发器,统一处理图片请求、取消、响应、重试、完成或错误等事件。
1.6 默认链式图片请求处理器
RequestHandler
是图片处理器基类,主要包含以下接口:
// 是否能处理当前请求
public abstract boolean canHandleRequest(Request data);
// 根据当前请求以及网络策略加载相应图片,并将结果包装成Result
public abstract Result load(Request request, int networkPolicy) throws IOException;
// 重试次数
int getRetryCount() {
return 0;
}
// 当前环境下是否可以重试
boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {
return false;
}
// 是否支持重新加载
boolean supportsReplay() {
return false;
}
处理器根据请求完成图片加载之后会将得到的Bitmap
或者InputStream
包装成Result
返回,用于后续图片处理,Picasso
会添加7个默认处理器,如下所示:
List<RequestHandler> allRequestHandlers = new ArrayList<RequestHandler>();
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));
allRequestHandlers.add(new AssetRequestHandler(context));
allRequestHandlers.add(new FileRequestHandler(context));
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
以下简单分析每个RequestHandler
处理的请求类型以及处理逻辑:
ResourceRequestHandler
canHandleRequest:
// 匹配规则,SCHEME_ANDROID_RESOURCE=‘android.resource’
resourceId != 0 || SCHEME_ANDROID_RESOURCE.equals(data.uri.getScheme())
load:
根据Request
中的相关配置生成BitmapFactory.Options
,解析ResourceId
对应的资源图片,封装成Result(bitmap, DISK)
ContactsPhotoRequestHandler
canHandleRequest:
// 可处理Uri
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", ID_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*", ID_LOOKUP);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#/photo", ID_THUMBNAIL);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/#", ID_CONTACT);
matcher.addURI(ContactsContract.AUTHORITY, "display_photo/#", ID_DISPLAY_PHOTO);
}
// 匹配规则, SCHEME_CONTENT='content', AUTHORITY = "com.android.contacts"
(SCHEME_CONTENT.equals(uri.getScheme())
&& ContactsContract.Contacts.CONTENT_URI.getHost().equals(uri.getHost())
&& matcher.match(data.uri) != UriMatcher.NO_MATCH);
load:
根据合法Uri
利用ContentResolver
获取相应联系人的photo的数据流,封装成Result(inputstream, DISK)
MediaStoreRequestHandler
canHandleRequest:
// 匹配规则,SCHEME_CONTENT='content',AUTHORITY = "media";
(SCHEME_CONTENT.equals(uri.getScheme())
&& MediaStore.AUTHORITY.equals(uri.getAuthority()));
load:
根据合法Uri
利用Video.Thumbnails.getThumbnail
或Images.Thumbnails.getThumbnail
解析相应Image
或Video
获得图片数据流,封装成Result(inputstream, DISK)
ContentStreamRequestHandler
canHandleRequest:
// 匹配规则,SCHEME_CONTENT='content'
SCHEME_CONTENT.equals(data.uri.getScheme());
load:
利用ContentResolver.openInputStream
获取Uri
对应资源的数据流,封装成Result(inputstream, DISK)
AssetRequestHandler
canHandleRequest:
// 匹配规则,‘file://packagename/anroid_asset/...’
(SCHEME_FILE.equals(uri.getScheme())
&& !uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0)));
load:
加载asset
中由Uri
指定的资源的数据流,封装成Result(inputstream, DISK)
FileRequestHandler
canHandleRequest:
//匹配规则,SCHEME_FILE=‘file’
SCHEME_FILE.equals(data.uri.getScheme());
load:
读取相应的文件输入流,获取相应文件中可能存在的ExifInterface.Orientation
,封装成Result(null, in, DISK, orientation)
NetworkRequestHandler
canHandleRequest:
(SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
load:
获取磁盘缓存或网络上的图片,封装成Result(bitmap, loadedFrom)
或者Result(in, loadedFrom)
2. 未配置参数
// key为Action.target,Pause时记录相应的Action,Resume的时候记录的Action
final Map<Object, Action> targetToAction;
final Map<ImageView, DeferredRequestCreator> targetToDeferredRequestCreator;
// 由于Action中对target持有弱引用,因此当target被回收之后便会进入referenceQueue,
// 而相应的Action变为无效Action,通过cleanupThread中的loop循环实现对无效Action的清理。
final ReferenceQueue<Object> referenceQueue;
private final CleanupThread cleanupThread;
三、图片加载缓存过程详解
1. 调用Picasso.load
启动图片加载流程:
public RequestCreator load(Uri uri)
public RequestCreator load(String path)
public RequestCreator load(File file)
public RequestCreator load(int resourceId)
path
及file
均会转换成相应的uri
,从而实际Picasso
处理uri
及resourceId
两种形式的请求,这也是Picasso
的默认请求处理器中canHandleRequest
都是通过uri
来进行判断的原因。而两种类型的load
操作最终返回的都是新建的一个RequestCreator
,于是RequestCreator
也必须支持resourceId
以及uri
两种形式Request
的构建,并且这两种形式是互斥的,即非此即彼。
2. RequestCreator的工作
RequestCreator
就是一个Request
的生成器,其有一些基本配置项:
// 设置图片时是否做渐显效果
private boolean noFade;
// 是否延迟加载图片,可延迟至ImageView的宽高确定后再发起加载请求
private boolean deferred;
// 是否设置占位图
private boolean setPlaceholder = true;
// 占位图资源Id
private int placeholderResId;
// 加载失败图资源Id
private int errorResId;
// 内存加载缓存策略
private int memoryPolicy;
// 网络加载缓存策略
private int networkPolicy;
// 占位图drawable
private Drawable placeholderDrawable;
// 加载失败图drawable
private Drawable errorDrawable;
// 将请求关联到该tag,用于处理pause及resume
private Object tag;
除了这些配置项之外,还可以通过一个Request.Builder
来配置Request
:
// 图片加载uri
private Uri uri;
// 图片资源Id
private int resourceId;
// 设定的缓存key
private String stableKey;
// 图片宽高
private int targetWidth;
private int targetHeight;
// 图片缩放策略
private boolean centerCrop;
private boolean centerInside;
// 是否仅向下缩放
private boolean onlyScaleDown;
// 旋转参数
private float rotationDegrees;
private float rotationPivotX;
private float rotationPivotY;
private boolean hasRotationPivot;
// 图片转换器列表
private List<Transformation> transformations;
// 图片格式
private Bitmap.Config config;
// 任务优先级
private Priority priority;
配置完成之后便可以调用Request.Builder.build
生成Request
,而创建的Request
还必须设置以下属性:
// 请求唯一Id
int id;
// 请求发起时间
long started;
// 网络加载缓存策略
int networkPolicy;
这几个属性是在调用RequestCreator
发起请求的接口是设置的,其提供以下发起请求的接口:
// 在当前线程获取图片
public Bitmap get() throws IOException
// 由Dispatcher统一调度根据Action生成BitmapHunter,然后将BitmapHunter提交给PicassoExecutorService,
// 并将Action记录在Dispatcher中;PicassoExecutorService则根据当前所有任务的优先级逐个运行BitmapHunter
// 的hunt方法,获取到图片之后有dispatcher统一调度调用相应Action的完成或失败回调,后续会详细分析。
public void fetch(Callback callback)
public void into(Target target)
public void into(RemoteViews remoteViews, int viewId, int notificationId, Notification notification)
public void into(RemoteViews remoteViews, int viewId, int[] appWidgetIds)
public void into(ImageView target, Callback callback)
我们逐个看下里面具体有哪些猫腻:
get()
这是所有发起请求接口中唯一不提交到PicassoExecutorService
执行的接口,由于是直接在当前线程加载图片,因此该接口禁止在主线程中调用。首先生成Request
:
private Request createRequest(long started) {
int id = nextId.getAndIncrement();
Request request = data.build();
request.id = id;
request.started = started;
Request transformed = picasso.transformRequest(request);
if (transformed != request) {
transformed.id = id;
transformed.started = started;
}
return transformed;
}
以上源码已删除日志相关的部分,首先通过Request.Builder
创建出Request
,然后用Picasso
中配置的Request
转换器进行预处理后得到最终Request
,然后生成该Request
的请求Key
,并然后创建GetAction
,Action
标识一次请求行为,作为以下数据的载体:
final Picasso picasso;
// 请求发起target的弱引用,与Picasso中的cleanThread配合实现对无效Action的清除
final WeakReference<T> target;
// 以下数据含义见RequestCreator
final Request request;
final boolean noFade;
final int memoryPolicy;
final int networkPolicy;
final int errorResId;
final Drawable errorDrawable;
final String key;
final Object tag;
// 是否处于等待重新加载状态
boolean willReplay;
// 是否已取消
boolean cancelled;
同时Action
要求子类实现图片加载成功或失败的回调方法:
abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void error();
而继承自Action
的GetAction
在这两个方法中不做任何处理,后续会解释为什么不用做处理。
获得Action
之后,便使用该Action
创建BitmapHunter
,该类继承自Runnable
,因此可以提交给ExecutorService
执行,它包含以下属性:
// 递增生成的序列号,用于将优先级一样的请求实现为先入先出队列
final int sequence;
// 以下数据含义见RequestCreator
final Picasso picasso;
final Dispatcher dispatcher;
final Cache cache;
final Stats stats;
final String key;
final Request data;
final int memoryPolicy;
int networkPolicy;
// 遍历Picasso中预设的所有图片请求处理器得到,是根据resourceId或uri获取图片的核心类
final RequestHandler requestHandler;
// 初始化时设定的Action
Action action;
// 运行时添加到该Hunter的Action
List<Action> actions;
// 完成Action之后得到的最终图片
Bitmap result;
// 提交到PicassoExecutorService之后返回的PicassoFutureTask,用于取消该Hunter
Future<?> future;
// 图片加载来源(MEMORY、DISK、NETWORK),
// 当设定了Picasso.indicatorsEnabled时会在设定图片时用加载来源对应的颜色表示该图片
Picasso.LoadedFrom loadedFrom;
// 请求过程中抛出的异常
Exception exception;
// 当图片类型为EXIF时,解析出其中的旋转参数
int exifRotation;
// 可重试次数
int retryCount;
// 任务优先级,由action及actions中所有Action的最高优先级决定
Priority priority;
创建了可运行GetAction
的BitmapHunter
之后便调用其hunt
方法获取相应的图片,由于该方法直接返回hunt
得到的图片,因此无需再Action
得complete
或error
中做任何处理,而BitmapHunter
的具体执行过程后文进行分析。
fetch(Callback callback)
需通过Dispatcher
提交到PicassoExecutorService
执行,该接口创建FetchAction
,特点是不用设定Target
,因此可用于图片的预加载,当图片加载成功之后回调Callback
的成功或失败回调方法。创建得到Action
之后调用picasso.submit(action)
提交请求,由于into
相关接口的提交细节与fetch
一致,因此在后文统一分析。值得一提的是,fetch的时候会先检查内存缓存,只有当内存缓存中没有对应图片的时候才提交任务,后续into
相关接口亦采用了类似的策略。
into(Target target)
创建TargetAction
,并与传入的target
绑定,而Target
接口包含以下方法:
// 图片加载成功
void onBitmapLoaded(Bitmap bitmap, LoadedFrom from);
// 图片加载失败
void onBitmapFailed(Drawable errorDrawable);
// 图片开始加载
void onPrepareLoad(Drawable placeHolderDrawable);
因此在提交任务之前会回调onPrepareLoad
,提交任务完成之后会根据结果在Action
中回调方法中回调Target
的onBitmapLoaded
或onBitmapFailed
,由于该Action
指定了target
,因此采用Picasso.enqueueAndSubmit
来提交任务,与submit
的区别在于会以该target
为key
将对应Action
记录在Picasso
中,记录新Action
的时候取消之前设定的Action
。
into(RemoteViews remoteViews, int viewId, int notificationId, Notification notification)
创建NotificationAction
,并根据传入的remoteViews
及viewId
创建RemoteViewsTarget
,用于将加载好的图片设定给指定的View
。而notificationId
及notification
用于在设定了图片之后进行刷新:
NotificationManager manager = getService(picasso.context, NOTIFICATION_SERVICE);
manager.notify(notificationId, notification);
这样就实现对RemoteViews的更新。
into(RemoteViews remoteViews, int viewId, int[] appWidgetIds)
创建AppWidgetAction
,类似于NotificationAction
,通过调用:
AppWidgetManager manager = AppWidgetManager.getInstance(picasso.context);
manager.updateAppWidget(appWidgetIds, remoteViews);
实现对RemoteViews
的更新。
into(ImageView target, Callback callback)
以ImageView
为target
创建ImageViewAction
,在提交任务之前会用占位图更新ImageView
,在任务完成之后用加载得到的图片或加载错误图片更新ImageView
,值得一提的是,在使用占位图及加载成功图片更新的时候会将图片包装成PicassoDrawable
以实现渐显的效果。
如果你仅仅觉得这个Action
如此简单,那你就输了,它要祭起大杀器了——《延迟加载》,只有ImageView
作为Target
的Action
才可以设置延迟加载,当设定了RequestCreator.deferred
且ImageView
的宽或高等于0时,会将该RequestCreator
包装成DeferredRequestCreator
,然后记录到Picasso
中,DeferredRequestCreator
实现了ViewTreeObserver
.OnPreDrawListener
接口,在onPreDraw
中设定Request
的targetWidth
及targetHeight
并重新调用RequestCreator.into(ImageView target, Callback callback)
实现加载,这样可以有效的控制加载图片的大小,从而达到节省内存的目的。
通过以上方法完成请求的发起之后,RequestCreator
的工作就完成了,接下来就由Dispatcher
及PicassoExecutorService
接手来处理请求。
3. Dispatcher提交任务
通过调用Picasso.submit
或Picasso.enqueueAndSubmit
可以提交上文中创建的Action
,后者相对于前者的区别在于会以Action
.target
为key
记录该Action
,两者最终均调用dispatcher.dispatchSubmit
,由Dispatcher
来实现任务的统一提交,实际的处理逻辑则在performSubmit
方法中:
void performSubmit(Action action, boolean dismissFailed) {
if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
return;
}
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
if (service.isShutdown()) {
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());
}
}
以上源码已删除日志相关部分,主要做以下任务:
- 若该
Action
对应的tag
处于pause
态,则将该Action
添加到pause
记录中; - 若该
Action
的key
对应的BitmapHunter
已存在,则将该任务附加到该BitmapHunter
中等待执行,注意此处是用Action
.key
作为键存储BitmapHunter
的,这表明BitmapHunter
中所有Action
请求的是同一个Request
,即同一张图片,这也防止请求同一张图片的多个Action
多次加载图片; - 若
PicassoExecutorService
已终止则放弃提交; - 生成新的
BitmapHunter
并提交到PicassoExecutorService
执行,并记录该BitmapHunter
; - 有必要的情况下,移除失败列表中的
Action
;
4. BitmapHunter执行任务
提交到PicassoExecutorService
的BitmapHunter
被执行的时候会调用其run
方法(非实际源码):
try {
result = hunt();
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
} catch(Exception e){
dispatcher.dispatchRetry(this);
// 或者
dispatcher.dispatchFailed(this);
}
调用BitmapHunter.hunt
获取Request
中Uri
对应的资源图片,根据返回结果决定产生成功或失败事件,若抛出异常则根据异常类型决定是产生重试还是失败事件,具体细节可查看源码,这边主要分析hunt
方法中的处理逻辑:
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
if (shouldReadFromMemoryCache(memoryPolicy)) {
// 当memoryPolicy未设定为NO_CACHE时,尝试从内存缓存中加载
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
return bitmap;
}
}
// 内存缓存命中失败或不允许使用内存缓存则调用在创建BitmapHunter时选中的RequestHandler处理图片加载请求
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
// 当图片为EXIF格式时会包含该参数
exifRotation = result.getExifOrientation();
bitmap = result.getBitmap();
if (bitmap == null) {
InputStream is = result.getStream();
try {
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is);
}
}
}
if (bitmap != null) {
stats.dispatchBitmapDecoded(bitmap);
if (data.needsTransformation() || exifRotation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifRotation != 0) {
// 利用Matrix实现图片的旋转、缩放等转换
bitmap = transformResult(data, bitmap, exifRotation);
}
if (data.hasCustomTransformations()) {
// 依次应用图片加载Request中设定的图片转换器(Transformation.transform)
bitmap = applyCustomTransformations(data.transformations, bitmap);
}
}
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}
以上源码删除了日志部分,分析已注释在代码中。
其中RequestHandler.load
的逻辑在前文中已有分析,后续会针对网络加载及缓存进行特别的分析。
5. Dispatcher处理图片加载事件(complete、fail、retry)
performComplete(BitmapHunter hunter)
// 如果memoryPolicy没有设定为NO_STORE则将该图片缓存到内存中
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
// 从记录中移除该BitmapHunter
hunterMap.remove(hunter.getKey());
batch(hunter);
其中batch(hunter)
是将BitmapHunter
作为参数生成事件(HUNTER_BATCH_COMPLETE
)发送到主线程进行处理,这里有个小技巧就是这个事件是个延时事件,因此在图片加载任务比较密集的情况下可以减少事件的发送量。
performError(BitmapHunter hunter, boolean willReplay)
hunterMap.remove(hunter.getKey());
batch(hunter);
处理类似performComplete。
performRetry(BitmapHunter hunter)
感觉文章有点长,而这部分源码又有些长,所以就不放源码的,主要处理为:
- 如果满足重试条件则将
BitmapHunter
重新提交到PicassoExecutorService
进行执行; - 如果满足
replay
条件且没有提交重试,则将该BitmapHunter
中的所有Action
记录到failedActions
并设置Action
.willReplay
,等待网络再次连接的时候进行重试;当然在Action
被取消或重提交时会将该Action从failedActions
中移除。
6. 主线程处理HUNTER_BATCH_COMPLETE事件
List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
for (int i = 0, n = batch.size(); i < n; i++) {
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
Picasso.complete
方法将返回结果传递给Action
的成功或失败回调,因此Action中的成功或失败回调是在UI主线程中调用的,因此执行刷新UI等操作。当BitmapHunter.exception不为空的时候回回调Picasso.Listener.onImageLoadFailed
,个人觉得这个回调的实用性相对较低。
7. Picasso中cleanThread的工作
cleanThread
会循环检测是否有Action
对应的target
被回收,若有则发送REQUEST_GCED
事件回收该Action
,该事件调用Picasso.cancelExistingRequest(action.getTarget())
进行处理:
// 仅在主线程中运行该函数
checkMain();
Action action = targetToAction.remove(target);
if (action != null) {
// 执行Action取消操作
action.cancel();
dispatcher.dispatchCancel(action);
}
if (target instanceof ImageView) {
// 如果当前Action被延迟加载则取消对应的延迟任务
ImageView targetImageView = (ImageView) target;
DeferredRequestCreator deferredRequestCreator =
targetToDeferredRequestCreator.remove(targetImageView);
if (deferredRequestCreator != null) {
deferredRequestCreator.cancel();
}
}
其中Action
的取消工作由Dispatcher
进行统一处理:
void performCancel(Action action) {
String key = action.getKey();
BitmapHunter hunter = hunterMap.get(key);
if (hunter != null) {
// 将Action从对应的BitmapHunter清除
hunter.detach(action);
if (hunter.cancel()) {
// 若该BitmapHunter无其他Action,则从任务记录中删除
hunterMap.remove(key);
}
}
if (pausedTags.contains(action.getTag())) {
// 如果该Action对应tag当前是pause态,则从pausedActions中清除该Action
pausedActions.remove(action.getTarget());
}
// 从replay的failedActions中清除该Action
failedActions.remove(action.getTarget());
}
这样Picasso
就可以有效管理target
与Action
之间的关系,防止无效加载浪费系统资源。其实Target
也可以通过Picasso
中的接口直接取消对对应的Action
,其流程与cleanThread
的清除流程一致。
8. pauseTag
及resumeTag
既然说到这,索性先把Picasso中Action的管理体系说一下,Action个中有三种管理关系:
target
与Action
:target
是Action
的承载体,如ImageView
、RemoteViews
或者继承自Target
接口的实例,一个target
仅对应一个Action
,当基于某个target
添加另一个Action
是会清除掉之前对应的Action
;tag
与Action
:tag
可以是普通字符串,也可以是Android
中的组件(如Activity
、Fragment
等),一个tag
对对应一组Action
,可以通过tag
取消、暂停或重新加载一组Action
;key
与Action
:key
是根据Request
创建的,用于表示请求的图片,并作为图片缓存的key
,因此也唯一指向一个BitmapHunter
,而BitmapHunter
中可以包含一组请求这张图片的Action
。
理清楚这层关系之后,再来说pauseTag
及resumeTag
:
pauseTag
就是讲tag
对应的一组Action
移除到pausedActions
;resumeTag
就是将pausedActions
中所有Action
重新提交执行(这里会先从缓存中Action
对应的图片,如果命中则直接完成Action
)。
至此,Picasso
加载及缓存图片的整体流程就分析完毕了。
四、其他想说的
1. Utils.flushStackLocalLeaks(Looper)
/**
* Prior to Android 5, HandlerThread always keeps a stack local reference to the last message
* that was sent to it. This method makes sure that stack local reference never stays there
* for too long by sending new messages to it every second.
*/
static void flushStackLocalLeaks(Looper looper) {
Handler handler = new Handler(looper) {
@Override public void handleMessage(Message msg) {
sendMessageDelayed(obtainMessage(), THREAD_LEAK_CLEANING_MS);
}
};
handler.sendMessageDelayed(handler.obtainMessage(), THREAD_LEAK_CLEANING_MS);
}
意思就是5.0之前HandlerThread
有个Bug,会保持最后一条消息的引用,用这种周期性空事件防止引用到重要资源导致内存泄露,至于怎么验证这个Bug,可以通过dump内存看,后续有时间再具体研究。
2. OkHttpDownloader
这是一个基于okhttp
的downloader
,这不是我要说的重点,重点是:
Square真乃业界良心,旗下开源的okhttp、retrofit、picasso、okio、otto、leakcanary、javapoet等等知名常用库,简直让感动得哭。
后续文章会分析okhttp
,也为这边的磁盘缓存画上句号。