摘要:本文基于Picasso的源码来分析图片加载及缓存的流程。


文章原创,允许转载,转载请注明出处。


一. 概述

PicassoSquare出品的一个非常精简的图片加载及缓存库,其主要特点包括:

  • 易写易读的流式编程风格
  • 中心事件分发器
  • 拦截器式图片加载流
  • 链式图片转换流
  • 无效请求清除器
  • 内存与磁盘双缓存策略

应用Picasso加载图片的主流程如下(查看原图):

二. 创建及配置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;反之则使用UrlConnectionDownloaderDownloader会设置磁盘缓存,默认缓存在应用Cache目录下,缓存设置为Cache的2%并且不小于5M且不大于50M;OkHttpDownloader基于OkHttp的缓存模块实现网络响应缓存,URLConnectionDownloader利用HttpResponseCache实现,而缓存策略由请求时传入的networkPolicy控制。

1.2 默认内存缓存

内存缓存默认采用LruCache实现,大小为应用总体内存的15%。

1.3 默认ExecutorService

默认ExecutorServicePicassoExecutorService

super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());

Service的主要特点是:

  1. 可根据网络状态调整线程数量。在Wifi环境下设置为4线程,在4G环境下设置为3线程,在3G环境下设置为2线程,在2G环境下设置为1线程;
  2. 同时将提交的RunnablePicasso中为RequestHunter)包装为PicassoFutureTask,由于Service本身设定为优先级队列,因此PicassoFutureTask主要实现任务优先级判定规则:
    • 根据RequestHunterpriority实现优先级队列;
    • 优先级一样的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.getThumbnailImages.Thumbnails.getThumbnail解析相应ImageVideo获得图片数据流,封装成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)

pathfile均会转换成相应的uri,从而实际Picasso处理uriresourceId两种形式的请求,这也是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,并然后创建GetActionAction标识一次请求行为,作为以下数据的载体:

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();

而继承自ActionGetAction在这两个方法中不做任何处理,后续会解释为什么不用做处理。 获得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;

创建了可运行GetActionBitmapHunter之后便调用其hunt方法获取相应的图片,由于该方法直接返回hunt得到的图片,因此无需再Actioncompleteerror中做任何处理,而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中回调方法中回调TargetonBitmapLoadedonBitmapFailed,由于该Action指定了target,因此采用Picasso.enqueueAndSubmit来提交任务,与submit的区别在于会以该targetkey将对应Action记录在Picasso中,记录新Action的时候取消之前设定的Action

into(RemoteViews remoteViews, int viewId, int notificationId, Notification notification)

创建NotificationAction,并根据传入的remoteViewsviewId创建RemoteViewsTarget,用于将加载好的图片设定给指定的View。而notificationIdnotification用于在设定了图片之后进行刷新:

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)

ImageViewtarget创建ImageViewAction,在提交任务之前会用占位图更新ImageView,在任务完成之后用加载得到的图片或加载错误图片更新ImageView,值得一提的是,在使用占位图及加载成功图片更新的时候会将图片包装成PicassoDrawable以实现渐显的效果。 如果你仅仅觉得这个Action如此简单,那你就输了,它要祭起大杀器了——《延迟加载》,只有ImageView作为TargetAction才可以设置延迟加载,当设定了RequestCreator.deferredImageView的宽或高等于0时,会将该RequestCreator包装成DeferredRequestCreator,然后记录到Picasso中,DeferredRequestCreator实现了ViewTreeObserver.OnPreDrawListener接口,在onPreDraw中设定RequesttargetWidthtargetHeight并重新调用RequestCreator.into(ImageView target, Callback callback)实现加载,这样可以有效的控制加载图片的大小,从而达到节省内存的目的。

通过以上方法完成请求的发起之后,RequestCreator的工作就完成了,接下来就由DispatcherPicassoExecutorService接手来处理请求。

3. Dispatcher提交任务

通过调用Picasso.submitPicasso.enqueueAndSubmit可以提交上文中创建的Action,后者相对于前者的区别在于会以Action.targetkey记录该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());
    }
  }

以上源码已删除日志相关部分,主要做以下任务:

  1. 若该Action对应的tag处于pause态,则将该Action添加到pause记录中;
  2. 若该Actionkey对应的BitmapHunter已存在,则将该任务附加到该BitmapHunter中等待执行,注意此处是用Action.key作为键存储BitmapHunter的,这表明BitmapHunter中所有Action请求的是同一个Request,即同一张图片,这也防止请求同一张图片的多个Action多次加载图片;
  3. PicassoExecutorService已终止则放弃提交;
  4. 生成新的BitmapHunter并提交到PicassoExecutorService执行,并记录该BitmapHunter
  5. 有必要的情况下,移除失败列表中的Action

4. BitmapHunter执行任务

提交到PicassoExecutorServiceBitmapHunter被执行的时候会调用其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获取RequestUri对应的资源图片,根据返回结果决定产生成功或失败事件,若抛出异常则根据异常类型决定是产生重试还是失败事件,具体细节可查看源码,这边主要分析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)

感觉文章有点长,而这部分源码又有些长,所以就不放源码的,主要处理为:

  1. 如果满足重试条件则将BitmapHunter重新提交到PicassoExecutorService进行执行;
  2. 如果满足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就可以有效管理targetAction之间的关系,防止无效加载浪费系统资源。其实Target也可以通过Picasso中的接口直接取消对对应的Action,其流程与cleanThread的清除流程一致。

8. pauseTagresumeTag

既然说到这,索性先把Picasso中Action的管理体系说一下,Action个中有三种管理关系:

  1. targetActiontargetAction的承载体,如ImageViewRemoteViews或者继承自Target接口的实例,一个target仅对应一个Action,当基于某个target添加另一个Action是会清除掉之前对应的Action
  2. tagActiontag可以是普通字符串,也可以是Android中的组件(如ActivityFragment等),一个tag对对应一组Action,可以通过tag取消、暂停或重新加载一组Action
  3. keyActionkey是根据Request创建的,用于表示请求的图片,并作为图片缓存的key,因此也唯一指向一个BitmapHunter,而BitmapHunter中可以包含一组请求这张图片的Action

理清楚这层关系之后,再来说pauseTagresumeTag
pauseTag就是讲tag对应的一组Action移除到pausedActionsresumeTag就是将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

这是一个基于okhttpdownloader,这不是我要说的重点,重点是:

Square真乃业界良心,旗下开源的okhttp、retrofit、picasso、okio、otto、leakcanary、javapoet等等知名常用库,简直让感动得哭。

后续文章会分析okhttp,也为这边的磁盘缓存画上句号。