Picasso源码解析

作者: SparkInLee | 来源:发表于2016-08-09 20:53 被阅读473次

    一. 概述

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

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

    应用Picasso加载图片的主流程如下:

    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,也为这边的磁盘缓存画上句号。

    相关文章

      网友评论

      • 5c5113a4a475:楼主画的那个流程图,实在太清晰了,能明确的说出Picasso加载图片显示图片的流程:+1:
      • 5c5113a4a475:楼主分析Picasso很厉害:+1:

      本文标题:Picasso源码解析

      本文链接:https://www.haomeiwen.com/subject/mxmdsttx.html