picasso详解及其源码简析

1、前言

图片库做为开发中一个重要的基础组件,其重要性不言而喻。目前来讲,比较流行的图片库分别是 Android-Universal-Image-LoaderPicassoGlideFresco。它们的通常特色是:android

  • 使用上比较简单,基本上均可以用一句链式代码就直接完成了图片的下载到展现。
  • 多级缓存,至少有 2 级缓存,磁盘缓存以及内存缓存。加上高效的图片处理,极大的减小了OOM 的发生。
  • 多种数据源,网络,磁盘,resource,assets 等。
  • 自动化程度高,图片下载、缓存管理,图片解码、图片处理甚至是 ListView/RecyclerView 的 Adapter 中图片自动取消、展现等,都不用咱们操心了。

这篇文章要分析的是 square/picasso。没错,又是 square,谁中你那么优秀呢?不去管它的旧版本如何,这里直接分析其最新版本 2.71828 吧。git

2、picasso 简介

1.关于 picasso

picasso,伟大的画家毕加索,这里想必也是在表达致敬之意。square 在 github 项目首页中只用了一句话解释这个库。github

一个适用于Android的强大的图像下载和缓存库编程

image.png

除了 github 项目首页的介绍外,picasso 还另外提供了一个 github page。在这里,它进一步介绍了其具有的几个功能:设计模式

  • 处理 ImageView 在适配器中的回收以及取消下载。
  • 使用最少的内存来处理复杂的图像转换。
  • 自动管理磁盘和内存缓存。

同时该页面也给出了一些使用例子,如在 Adapter 中使用,图像变换以及自定义变换、设置 placeholders 等。感兴趣的同窗能够自行前往看一看,不过要注意 Picasso 的实例再也不从 Picasso.with(context) 开始了,也再也不是该页面所展现的 Picasso.get(),而封装了一个 PicassoProvider,而后经过它的静态方法 get() 来获取。缓存

2.集成

gradle 中集成,so easy,每一个整 android 的小伙伴都懂的。bash

implementation 'com.squareup.picasso:picasso:2.71828'网络

3、源码分析

前面简要介绍了下 picasso,下面便开始进入正题源码分析。不变的原则,从最简单的主路径开始分析。分析以前先来看一看其框架图,是否是似曾相识?并发

Picasso.jpg

1.demo

picasso 工程里有提供 sample,做为典型的例子,这里选择了从 SampleListDetailAdapter 拿出一段用于 ListView 的 Adapter 中加载列表图片的代码来做为用于主路径分析的 demo。app

PicassoProvider.get()
        .load(url)
        .placeholder(R.drawable.placeholder)
        .error(R.drawable.error)
        .resizeDimen(R.dimen.list_detail_image_size, R.dimen.list_detail_image_size)
        .centerInside()
        .tag(context)
        .into(holder.image);
复制代码

链式调用的一大特色就在于无论多复杂的逻辑,看起来好像都是顺序执行的同样。嗯呵,你可能想到了 RxJava。下面就来逐个逐个分析上面的每个方法的调用。

2.主路径分析

2.1 构造 Picasso 实例,初始化运行环境

  • PicassoProvider及其 get() 方法
public final class PicassoProvider {
  @SuppressLint("StaticFieldLeak")
  private static volatile Picasso instance;

  @Initializer
  public static Picasso get() {
    // 单例实现
    if (instance == null) {
      synchronized (PicassoProvider.class) {
        if (instance == null) {
          // 获取 context
          Context autoContext = PicassoContentProvider.context;
          if (autoContext == null) {
            throw new NullPointerException("context == null");
          }
          // 经过 Picasso.Builder 来构建 Picasso 实例
          instance = new Picasso.Builder(autoContext).build();
        }
      }
    }
    return instance;
  }

  private PicassoProvider() {
    throw new AssertionError("No instances.");
  }
}
复制代码

PicassoProvider 经过单例设计模式的方式,在 get() 方法中为咱们提供了 Picasso 的实例。也就是说,在应用中,一个进程内只有一个 Picasso 实例。

构建 Picasso 实例是须要 Context 的,这里是从 PicassoContentProvider 处获取。所以,进一步看看 PicassoContentProvider 又是如何提供 Context。

  • PicassoContentProvider 及其 context 的初始化
public final class PicassoContentProvider extends ContentProvider {
  @SuppressLint("StaticFieldLeak")
  @Nullable static Context context;

  @Override public boolean onCreate() {
    context = getContext();
    return true;
  }
  ......
}
复制代码

PicassoContentProvider 继承了 ContentProvider,这是一个很是微妙的实现。其利用了 ContentProvider 会在 Application 初始化的时候同时被初始化的原理,从而初始化 context。不得不服!!!这就避免了开发者还须要对其进行全局初始化的步骤,典型是在应用的 Application#onCreate() 进行初始化。

  • Picasso.Builder 及其 build() 方法

先来看 Picasso 的构造方法,其主要目的是获取 ApplicationCotnext,从而避免引发内存泄漏。

public Builder(@NonNull Context context) {
      checkNotNull(context, "context == null");
      this.context = context.getApplicationContext();
    }
复制代码

再来看 build() 方法。

public Picasso build() {
      Context context = this.context;
      // 初始化磁盘 Cache,也即初始化 OkHttp 的 Cache
      okhttp3.Cache unsharedCache = null;
      if (callFactory == null) {
        File cacheDir = createDefaultCacheDir(context);
        long maxSize = calculateDiskCacheSize(cacheDir);
        unsharedCache = new okhttp3.Cache(cacheDir, maxSize);
        callFactory = new OkHttpClient.Builder()
            .cache(unsharedCache)
            .build();
      }
      // 初始化内存 Cache
      if (cache == null) {
        cache = new PlatformLruCache(Utils.calculateMemoryCacheSize(context));
      }
      // 初始化线程池
      if (service == null) {
        service = new PicassoExecutorService(new PicassoThreadFactory());
      }
      // 初始化内存 Cache 统计器
      Stats stats = new Stats(cache);
      // 初始化分发器
      Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, cache, stats);
      // new 出 Picasso 实例
      return new Picasso(context, dispatcher, callFactory, unsharedCache, cache, listener,
          requestTransformers, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled,
          loggingEnabled);
    }
复制代码

磁盘 Cache: 对了,磁盘在手机上通常指的就是闪存 Flash 或者外置的 SD Card 了。Picasso 没有单独实现一个磁盘缓存,而是利用了 OkHttp 的缓存管理,使得缓存的有效期与 Http 协议同步。这样作的好处是保证了图片的更新的及时性,而不像其余磁盘缓存经过硬设一个缓存过时时间好比 7 天来判断其是否过时。固然,它可能也会增长多一些网络请求。另外,默认建立的磁盘 Cache 的路径为 App 的内部 data 目录下 cache/picasso-cache,而大小通常为磁盘空间总大小的 2%,可是也不能超过 50 M。

内存 Cache: PlatformLruCache,其内部经过 LruCache 来实现的内存 Cahce。而其大小被限制在应用可得到的最大 heap 内存的 15%。

/** A memory cache which uses a least-recently used eviction policy. */
final class PlatformLruCache {
  final LruCache<String, BitmapAndSize> cache;
  ......
}
复制代码

线程池: PicassoExecutorService,其继承自ThreadPoolExecutor,在其构造函数中设置了其核心线程数以及最大线程数为 3 个,这就限制了其最多只能有 3 个线程能同时工做。

class PicassoExecutorService extends ThreadPoolExecutor {
  private static final int DEFAULT_THREAD_COUNT = 3;

  PicassoExecutorService(ThreadFactory threadFactory) {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), threadFactory);
  }
  ......
}
复制代码

Stats: 内存 Cache 统计器,统计 Cache 的各个指标,好比命中,未命中等。

分发器Dispatcher: 似曾相识的概念,在 OkHttp 中有见到过。这里也是同样,主要管理任务的分发与调度。包括提交,取消,重试以及网络变化的监听策略。

Picasso及其构造函数: Picasso 的构造会使得其持有 Dispatcher,Cache,PlatformLruCache,RequestTransformer 队列,RequestHandler队列等,这些引用都会保存在 Picasso 内部。除此以外,其自身也会初始化一些 RequestHandlers 用于处理已知的各类资源类型。当咱们传递不一样的资源类型的请求时,其便会选择相应的 RequestHandler 来进行处理。

另外,这里还注意到,Picasso 实现了 LifecycleObserver 接口。这意味着咱们能够将其注册到 LifecycleOwner 的 LifecycleRegistry 中,从而实现生命周期的联动。

public class Picasso implements LifecycleObserver {
  ......
  Picasso(Context context, Dispatcher dispatcher, Call.Factory callFactory,
      @Nullable okhttp3.Cache closeableCache, PlatformLruCache cache, @Nullable Listener listener,
      List<RequestTransformer> requestTransformers, List<RequestHandler> extraRequestHandlers,
      Stats stats, @Nullable Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled,
      boolean loggingEnabled) {
    this.context = context;
    this.dispatcher = dispatcher;
    this.callFactory = callFactory;
    this.closeableCache = closeableCache;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformers = Collections.unmodifiableList(new ArrayList<>(requestTransformers));
    this.defaultBitmapConfig = defaultBitmapConfig;

    // Adjust this and Builder(Picasso) as internal handlers are added or removed.
    int builtInHandlers = 8;
    int extraCount = extraRequestHandlers.size();
    List<RequestHandler> allRequestHandlers = new ArrayList<>(builtInHandlers + extraCount);

    ......
    allRequestHandlers.add(ResourceDrawableRequestHandler.create(context));
    allRequestHandlers.add(new ResourceRequestHandler(context));
    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(callFactory, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    this.stats = stats;
    this.targetToAction = new LinkedHashMap<>();
    this.targetToDeferredRequestCreator = new LinkedHashMap<>();
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
  }
  ......
}
复制代码

到这里,Picasso 的运行环境已经基本初始化好了,接下来就是等待用户的请求发送了。

2.2 参数设置,从 RequestCreator 到 Request

前面经过 PicassoProvider.get() 构建了 Picasso 实例,同时也初始化了 Picasso 的运行环境,接下来就是一系列参数的设置。经过 Picasso 的 load() 方法即可获得 Request 的建立器 RequestCreator,咱们将须要的参数都送进 RequestCreator 以便能最终构建出咱们所须要的那个 Request。

  • Picasso 的 load() 方法,构造 RequestCreator 实例

load() 重载了 4 个不一样的方法,分别对应了加载不一样的数据源,4 个方法以下。

public RequestCreator load(@Nullable Uri uri) {
    return new RequestCreator(this, uri, 0);
}
public RequestCreator load(@Nullable String path) {
    .......
    return load(Uri.parse(path));
}
public RequestCreator load(@Nullable File file) {
   ......
    return load(Uri.fromFile(file));
}
public RequestCreator load(@DrawableRes int resourceId) {
   ......
    return new RequestCreator(this, null, resourceId);
}
复制代码

能够看出前 3 个其实都是同一个,形参都将转成 Uri。而从上面的代码也能够看出,最终都是 new 出一个 RequestCreator 实例。而且这里 Uri 与 resourceId 是互斥的,必定只是其中的一个。再来看看 RequestCraetor 的构造方法。

RequestCreator(Picasso picasso, @Nullable Uri uri, int resourceId) {
    ......
    this.picasso = picasso;
    this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
复制代码

这里最主要就是构造了 Request.Builder 实例,并存储在 data 属性中。其主要是用于在设置好参数后,进一步构造出 Request 实例。

  • RequestCreator.placeholder() 以及 error() 方法
public RequestCreator placeholder(@DrawableRes int placeholderResId) {
   ......
    this.placeholderResId = placeholderResId;
    return this;
}
public RequestCreator error(@DrawableRes int errorResId) {
    ......
    this.errorResId = errorResId;
    return this;
}
复制代码

就是简单的记录下 placeholderResId 以及 errorResId。

  • RequestCreator.resizeDimen() 和 centerInside() 方法

resizeDeimen() 其实主是将参数 dimen 所表达的数值转成实际大小,再调用 resize() 方法,因此这里直接看 resize() 方法便可。

public RequestCreator resize(int targetWidth, int targetHeight) {
    data.resize(targetWidth, targetHeight);
    return this;
 }
  public RequestCreator centerCrop() {
    data.centerCrop(Gravity.CENTER);
    return this;
 }
复制代码

这里的 data 是 Request.Builder 实例,因此这里是将参数送进了 RequestBuilder 中去了。和上面的 2 个方法比较一下,为何参数会被存储在不一样的地方?

  • ReqeustCreator.tag()方法
public RequestCreator tag(@NonNull Object tag) {
    data.tag(tag);
    return this;
  }
复制代码

tag 用来标记一个 Request ,能够是任意一个对象,如 String,Context,Activity,Fragment等。咱们能够经过 tag 来 pause,resume 以及 cancel 具有相同 tag 的 Request。但要注意的是 tag 与 Request 的生命周期是同样长的,必定程度上可能会存在内存泄漏的风险。

关于 RequestCreator 的参数还有不少能够设置的,这里就不一一详细进行讲解了。仅经过类图将其列举出来以下。

RequestCreator

2.3 提交请求 into(ImageView)

  • into() 相关说明,关于 target,关于 fetch()

其实 into() 也是有不少重载的,好比into(BitmapTarget),into(RemoteViews, int viewId, int notificationId,Notification notification) 等。也就是说它还能够将图片显示到 Notification 中以及 BitmapTarget 接口。关于 BitmapTarget 这个接口是什么意思呢?这里看一看其在注释中举的一个例子就能够明白了。

public class ProfileView extends FrameLayout implements BitmapTarget {
    Override 
    public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from) {
        setBackgroundDrawable(new BitmapDrawable(bitmap));
      }
    Override 
    public void onBitmapFailed(Exception e, Drawable errorDrawable) {
     setBackgroundDrawable(errorDrawable);
      }
    Override 
    public void onPrepareLoad(Drawable placeHolderDrawable) {
       setBackgroundDrawable(placeHolderDrawable);
     }
  }
复制代码

也就是说咱们只实现这个接口,而后在 onBitmapLoaded() 的回调中本身选择应该如何显示该图片。

而与 into() 相同级别的还有另外一个方法 fetch(),这个方法主要是只下载图片而不展现到 target 上去。这也是一个极为有用的方法,典型的场景就是预加载。

关于 into() 以外的东西就了解这么多,下面将重要放到 into(ImageView) 上来。into(ImageView) 其只是进一步调用了 into(ImageView,Callback),所以直接来看后面这个重载方法。

  • into(ImageView)提交请求

方法有点长,但其实很简单,不要畏难,耐着性子看完,增长了比较详细的注释了。

public void into(@NonNull ImageView target, @Nullable Callback callback) {
    long started = System.nanoTime();
    // 检查是否为主线程,Picasso 要求请求必须从主线程发起。
    checkMain();
    // target 合法性检查
    if (target == null) {
      throw new IllegalArgumentException("Target must not be null.");
    }
    // 若是没有设置 url 或者  resId,则取消当前 ImageView 的 Request,并直接返回
    if (!data.hasImage()) {
      picasso.cancelRequest(target);
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      return;
    }
    // 若是调用了 fit() ,也就是使得图片大小为适应 ImageView 的大小
    if (deferred) {
      if (data.hasSize()) {
        // fit() 与 resize() 不能同时调用,不然会抛出异常
        throw new IllegalStateException("Fit cannot be used with resize.");
      }
      int width = target.getWidth();
      int height = target.getHeight();
      if (width == 0 || height == 0) {
        // ImageView 没有具体的高度或者还没被渲染出来的状况下
        if (setPlaceholder) {
          setPlaceholder(target, getPlaceholderDrawable());
        }
        picasso.defer(target, new DeferredRequestCreator(this, target, callback));
        return;
      }
      data.resize(width, height);
    }
    // 构建 Request
    Request request = createRequest(started);

    if (shouldReadFromMemoryCache(request.memoryPolicy)) {
      // 容许从内存缓存读取则优先从内存缓存读取
      Bitmap bitmap = picasso.quickMemoryCacheCheck(request.key);
      if (bitmap != null) {
        // 缓存中存在有须要的图片,则取消并返回结果
        picasso.cancelRequest(target);
        RequestHandler.Result result = new RequestHandler.Result(bitmap, MEMORY);
        setResult(target, picasso.context, result, noFade, picasso.indicatorsEnabled);
        if (picasso.loggingEnabled) {
          log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
        }
        if (callback != null) {
          callback.onSuccess();
        }
        return;
      }
    }
   // 设置 PlasceHolder
    if (setPlaceholder) {
      setPlaceholder(target, getPlaceholderDrawable());
    }
   // 建立 ImageViewAction 并提交
    Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade,
        callback);
    picasso.enqueueAndSubmit(action);
  }
复制代码

方法里的代码都加了详细的注释,而且总结流程图以下。

Into.jpg

流程图上看也确实步骤比较多,但其实关键步骤就 2 个。其一,先判断是否内存缓存是否已经存储图片了,若是有就用内存缓存的图片。其二,若是没有就构造一个 ImageViewAction 来提交到 Picasso 的请求队列。说到缓存,这里有一个重要的知识点,关于缓存的 key,它是在 Request 被 build 出来后一块儿被建立的,其在 Request 的 createKey() 方法中。

private String createKey(StringBuilder builder) {
    Request data = this;
    // key 的主要部分
    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);
    }
   // key 的分隔线
    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);
    }

    //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();
  }
复制代码

代码看起来也有点长,但简单理解下,cache key 主要由主体部分 + 参数部分构成。主体部分通常是指 url 或者 resId,而参数部分主要就是其是否有旋转,resize 等。那这样一来就获得一个结论,就是同一个图片在内存中可能由于要显示的参数不同会有不一样的缓存。这样作的好处天然是提升了显示速度,而坏处天然就是占用的内存会较多。

2.4 入队请求,分发请求Dispatcher,执行请求 BitmapHunter

  • 入队到 Picasso

在 into(ImageView) 中,若是没有命中 Cache,最后一步就是封装 ImageViewAction,并提交给 picasso,由其进一步处理。

Action action = new ImageViewAction(picasso, target, request, errorDrawable, errorResId, noFade,
        callback);
picasso.enqueueAndSubmit(action);
复制代码

先来看一看 enqueueAndSubmit

void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (targetToAction.get(target) != action) {
      // This will also check we are on the main thread.
      cancelExistingRequest(target);
      targetToAction.put(target, action);
    }
    submit(action);
  }
复制代码

Picasso 中用 targetToAction 来保存了 target 与 action 之间的关系。这里首先从里面找 target 是否已经关联了其余的 action,若是已经关联了则先要将其取消。这里的场景就是特别适合 Adapter 了。在 Adapter 中因为 ImageView 是会被重用的,那么就必定会存在一个 target 对应不一样的 action 的状况。

  • 取消以前可能存在的 Request

这里先来看一下任务的取消 cancelExistingRequest() 的实现,而后再去看 submit()。

void cancelExistingRequest(Object target) {
    checkMain();
    Action action = targetToAction.remove(target);
    if (action != null) {
      action.cancel();
      // 从 dispatcher 中取消
      dispatcher.dispatchCancel(action);
    }
    if (target instanceof ImageView) {
      ImageView targetImageView = (ImageView) target;
     // 若是有延迟的 request,也要将其取消,这里假设没有吧。
      DeferredRequestCreator deferredRequestCreator =
          targetToDeferredRequestCreator.remove(targetImageView);
      if (deferredRequestCreator != null) {
        deferredRequestCreator.cancel();
      }
    }
  }
复制代码

因此主要是将 Dispatcher 中的 action 取消。Dispatcher 中主要用了一个 HandlerThread 来管理,而且全部关于请求的提交,取消等都是经过消息来执行的。以下所示。

static final int REQUEST_SUBMIT = 1;
  static final int REQUEST_CANCEL = 2;
  static final int HUNTER_COMPLETE = 4;
  static final int HUNTER_RETRY = 5;
  static final int HUNTER_DECODE_FAILED = 6;
  static final int NETWORK_STATE_CHANGE = 9;
  static final int AIRPLANE_MODE_CHANGE = 10;
  static final int TAG_PAUSE = 11;
  static final int TAG_RESUME = 12;
  static final int REQUEST_BATCH_RESUME = 13;
复制代码

消息如何执行,这里就不详细说明了。Dispatcher 中的 action 取消明显就是经过 REQUEST_CANCEL 来执行,而 REQUEST_CANCEL 对应的就是 performCance()。

void performCancel(Action action) {
   // 取消对应的 hunter
    String key = action.request.key;
    BitmapHunter hunter = hunterMap.get(key);
    if (hunter != null) {
      hunter.detach(action);
      if (hunter.cancel()) {
        hunterMap.remove(key);
        if (action.picasso.loggingEnabled) {
          log(OWNER_DISPATCHER, VERB_CANCELED, action.request.logId());
        }
      }
    }
    // 若是在 pause 队列中,则移除
    if (pausedTags.contains(action.getTag())) {
      pausedActions.remove(action.getTarget());
      if (action.picasso.loggingEnabled) {
        log(OWNER_DISPATCHER, VERB_CANCELED, action.request.logId(),
            "because paused request got canceled");
      }
    }
    // 失败队列中包含了,也移除
    Action remove = failedActions.remove(action.getTarget());
    if (remove != null && remove.picasso.loggingEnabled) {
      log(OWNER_DISPATCHER, VERB_CANCELED, remove.request.logId(), "from replaying");
    }
  }
复制代码

hunter 是什么呢?hunter 的中文意思是猎人或者搜寻者的意思。这里就是负责执行实际请求的类 BitmapHunter,它实现了 Runnable 接口,是 Dispatcher 中 ExecuteService 线程池的实际调度单位。这里先看一下它的 cancel(),后面还会讲它的 submit()。

boolean cancel() {
    return action == null
        && (actions == null || actions.isEmpty())
        && future != null
        && future.cancel(false);
  }
复制代码

future 是 Future<?> 类型,它是 ExecuteService.submit() 的返回值,这里咱们能够经过其 cancel() 方法来取消 BitmapHunter 的执行。

  • 提交 Request

回到 Picasso 的 enqueueAndSubmit() 中来,下一步就是 submit(action)。submit() 将 action 经过 Dispatcher.dispatchSubmit()提交到 Dispatcher ,Dispatcher 将其转化成消息 REQUEST_SUBMIT 发送到消息队列中,而 REQUEST_SUBMIT 所对应的处理方法是 performSubmit(),所以从 performSubmit() 的代码开始分析 Request 的提交。

void performSubmit(Action action, boolean dismissFailed) {
    ......
    // 已经提交了
    BitmapHunter hunter = hunterMap.get(action.request.key);
    if (hunter != null) {
      hunter.attach(action);
      return;
    }
    // 线程池已经关闭
    if (service.isShutdown()) {
      ......
      return;
    }
   // 建立一个新的 BitmapHunter
    hunter = forRequest(action.picasso, this, cache, stats, action);
   // 将 hunter 提交给线程池
    hunter.future = service.submit(hunter);
   // 已经提交的 hunter 保存在 hunterMap 中
    hunterMap.put(action.request.key, hunter);
    ......
  }
复制代码

代码中关键的逻辑是建立 hunter 并提交给线程 ExecuteService。这里须要关注一下 forRequest 的实现,其里面有一个关键步骤就是为 Request 选择一下合适的 Handler。

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher,
      PlatformLruCache cache, Stats stats, Action action) {
    Request request = action.request;
    List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
    // 选择合适的 Handler 并建立 BitmapHunter
    for (int i = 0, count = requestHandlers.size(); i < count; i++) {
      RequestHandler requestHandler = requestHandlers.get(i);
      if (requestHandler.canHandleRequest(request)) {
        return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
      }
    }
    // 一个都没有
    return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
  }
复制代码

假设这里给的是 url 地址,且是以 http / https 开头的,那被选中的就是 NetworkRequestHandler。

  • 执行 Request

当一个 Runnable 被提交到线程池 ExecuteService 中去以后,接下来就是等待其被度到,也就是执行其方法 run()。

@Override public void run() {
    try {
      ......
      result = hunt();
      ......
    } catch (NetworkRequestHandler.ResponseException e) {
     ......
    } finally {
      Thread.currentThread().setName(Utils.THREAD_IDLE_NAME);
    }
  }
复制代码

精简一下代码,关键就是调用 hunt()。

Result hunt() throws IOException {
    // 1.先判断缓存中是否存在,若是存在则直接返回
    if (shouldReadFromMemoryCache(data.memoryPolicy)) {
      Bitmap bitmap = cache.get(key);
      if (bitmap != null) {
        ......
        return new Result(bitmap, MEMORY);
      }
    }
    ......
    final AtomicReference<Result> resultReference = new AtomicReference<>();
    final AtomicReference<Throwable> exceptionReference = new AtomicReference<>();
    final CountDownLatch latch = new CountDownLatch(1);
    try {
      // 2. 经过 NetworkRequestHandler 加载图片,并等待其返回。
      requestHandler.load(picasso, data, new RequestHandler.Callback() {
        @Override public void onSuccess(@Nullable Result result) {
          resultReference.set(result);
          latch.countDown();
        }

        @Override public void onError(@NonNull Throwable t) {
          exceptionReference.set(t);
          latch.countDown();
        }
      });

      latch.await();
    } catch (InterruptedException ie) {
      ......
    }
    ......
    Bitmap bitmap = result.getBitmap();
    if (bitmap != null) {
      ......
      // 3.执行全部的 Transformation,获取最终的 result
      List<Transformation> transformations = new ArrayList<>(data.transformations.size() + 1);
      if (data.needsMatrixTransform() || result.getExifRotation() != 0) {
        transformations.add(new MatrixTransformation(data));
      }
      transformations.addAll(data.transformations);
      result = applyTransformations(picasso, data, transformations, result);
     .....
    }
    return result;
  }
复制代码

关键也就是注释中 3 个步骤: (1) 先判断缓存中是否存在,若是存在则直接返回。这个在 into() 中也有判断。 (2) 经过 NetworkRequestHandler 加载图片,并等待其返回。关于 NetworkRequestHandler 的 load() 方法的实现,其实就是 OkHttp 的请求过程,这里就没有必要展开了。同时这里使用了 CountDownLatch 类来使得当前线程能够等待异步线程执行完成。这里简要了解一下其原理,即当 CountDownLatch.await() 使得当线程进入 awaiting 状态后,只有经过 countDown() 使得其计数变成 0 后才会唤醒当前的线程。具体原理图以下。

CountDownLatch工做原理-图片来自网络

另外还使用了 AtomicReference,相似的还有 AtomicInteger 等,主要做用即是在并发编程中保证数据操做的原子性。这些都是并发编程的内容,这里只做了解便可。

(3) 执行全部的 Transformation,获取最终的 result。这里的 Transformation 就是经过 RequestCreator 的 transform() 方法所添加的。Transformation 是一个接口,通常咱们可在其方法 transform() 中进行自定义处理,处理完后再将新的图片返回。

到这里,请求的提交到执行就分完成了。至此,整个从 Picasso 的初始化到构建 Request 设置参数,再到提交 request 以及 执行 request 都分析完毕了。

4、总结

Picasso 整体来讲,其源码难度较小,分析起来也比较轻松。其中最重要的两个细节点在于:

  • 每个 Target 同一时刻只对应一个 Action,当同一个 Target 有新的请求时,当前 Action 会被取消掉。这就解决了 Adapter 中 ImageView 的重用问题,使得其不会因重用而致使图片显示错乱。
  • 图片在缓存中的 key,key 不只包括了 url 地址/资源 id,同时还包括了其形态参数,好比大小,旋转等。这就使得同一图片可能会存在多个缓存文件。

下面再以 Picasso 的框架图来完成这篇文章的所有总结。

Picasso.jpg

最后,感谢你能读到并读完此文章。受限于做者水平有限,若是存在错误或者疑问都欢迎留言讨论。若是个人分享可以帮助到你,也请记得帮忙点个赞吧,鼓励我继续写下去,谢谢。

相关文章
相关标签/搜索