转载

深入浅出 Picasso 源码(下):常见功能实现原理分析及总结

通过本篇文章你将学到如下内容:

      • 发起图片请求后的整体流程
      • 取消、暂停、恢复加载如何实现

分析过源码的朋友可以直接滑到文章底部看最后的总结,欢迎一起讨论。

常见功能实现分析

经过前面对核心 API 的介绍,我们已经对 Picasso 有个大概的了解了,接下来通过不同的业务逻辑,来整体上掌握 Picasso 的实现流程。

主要看四个功能的实现:

  1. 发起图片请求后的整体流程
  2. 取消、暂停、恢复加载如何实现
  3. 动态调整线程池数量的实现
  4. 缓存策略

发起图片请求后的整体流程

经典的调用:

Picasso.get() //1.获得 Picasso 单例
    .load(url) //2.创建 RequestCreator
    .placeholder(R.drawable.placeholder) 
    .error(R.drawable.error) 
    .fit() 
    .tag(context) 
    .into(view);    //3.发起请求

第一步 Picasso.get() 方法返回的是 Picasso 的单例,它通过 Picasso.Builder 构造:

public static Picasso get() {
  if (singleton == null) {
    synchronized (Picasso.class) {
      if (singleton == null) {
        if (PicassoProvider.context == null) {
          throw new IllegalStateException("context == null");
        }
        singleton = new Builder(PicassoProvider.context).build();
      }
    }
  }
  return singleton;
}

我们看看 Picasso.Builder.build() 方法:

public Picasso build() {
  Context context = this.context;

  if (downloader == null) {
    downloader = new OkHttp3Downloader(context);    //下载
  }
  if (cache == null) {
    cache = new LruCache(context);    //缓存
  }
  if (service == null) {
    service = new PicassoExecutorService();    //线程池
  }
  if (transformer == null) {
    transformer = RequestTransformer.IDENTITY;    //请求转换,可以用作 CDN
  }

  Stats stats = new Stats(cache);    //统计数据

  Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);

  return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
      defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}

从中可以看出的是:

  1. Picasso 的下载是使用 OkHttp3 实现的
  2. 缓存使用的 LruCache ,底层实现是 LinkedHashMap()
  3. 线程池是自定义的,我们后面介绍
  4. 默认的请求转换为不转换
Picasso.get() //1.获得 Picasso 单例
    .load(url) /

请求的第二步调用了 load(url) 方法:

public RequestCreator load(@Nullable Uri uri) {
  return new RequestCreator(this, uri, 0);
}

可以看到创建了一个 RequestCreator ,后面的配置都是调用它的方法。

Picasso.get() //1.获得 Picasso 单例
    .load(url) //2.创建 RequestCreator
    .placeholder(R.drawable.placeholder) 
    .error(R.drawable.error) 
    .fit() 
    .tag(context) 
    .into(view);    //3.发起请求

这些配置方法也很简单,就是修改属性:

public RequestCreator placeholder(@DrawableRes int placeholderResId) {
  //去掉检查方法
  this.placeholderResId = placeholderResId;
  return this;
}
public RequestCreator error(@DrawableRes int errorResId) {
  //去掉检查方法
  this.errorResId = errorResId;
  return this;
}
public RequestCreator fit() {
  deferred = true;
  return this;
}

配置好后调用 into(ImageView) 发起请求:

public void into(ImageView target) {
  into(target, null);
}

public void into(ImageView target, Callback callback) {
  long started = System.nanoTime();
  //1.延迟操作
  if (deferred) {   //延迟执行,配置 fit() 等操作后会进入这一步
    if (data.hasSize()) {
      throw new IllegalStateException("Fit cannot be used with resize.");
    }
    int width = target.getWidth();
    int height = target.getHeight();
    if (width == 0 || height == 0) {
      if (setPlaceholder) {
        setPlaceholder(target, getPlaceholderDrawable());
      }
      picasso.defer(target, new DeferredRequestCreator(this, target, callback));
      return;
    }
    data.resize(width, height);
  }

  Request request = createRequest(started);
  String requestKey = createKey(request);

  //2.缓存获取
  if (shouldReadFromMemoryCache(memoryPolicy)) {    //先去内存缓存中获取
    Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
    if (bitmap != null) {
      picasso.cancelRequest(target);  //已经有了,别再请求了
      setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);  //放进去
      if (picasso.loggingEnabled) {
        log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
      }
      if (callback != null) {
        callback.onSuccess();
      }
      return;
    }
  }

  if (setPlaceholder) {
    setPlaceholder(target, getPlaceholderDrawable());
  }

  //3.构造一个 action 去请求
  Action action =
      new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
          errorDrawable, requestKey, tag, callback, noFade);

  picasso.enqueueAndSubmit(action);
}

有缓存时会去缓存取,否则就构造一个 action 调用 picasso.enqueueAndSubmit(action) 方法提交请求:

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

void submit(Action action) {
  dispatcher.dispatchSubmit(action);
}

这个提交方法就是把要执行的操作和对象(这里是要显示的 ImageView)保存到一个 map 里,如果之前有这个 ImageView 的请求,就取消掉,避免重复加载。

最后调用了 dispatcher.dispatchSubmit(action) ,然后又调用到了 performSubmit(action) 方法:

public void dispatchSubmit(Action action) {
  handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
public void performSubmit(Action action) {
  performSubmit(action, true);
}

/**
 * 提交获取请求
 * @param action
 * @param dismissFailed
 */
public void performSubmit(Action action, boolean dismissFailed) {
  if (pausedTags.contains(action.getTag())) {   //如果暂停集合里有这个 action 的 tag,这次就先不请求,返回
    pausedActions.put(action.getTarget(), action);
    return;
  }

  //如果已经创建了这个 action 对应的 BitmapHunter,就把数据添加到待操作列表,不重复创建了
  BitmapHunter hunter = hunterMap.get(action.getKey());    
  if (hunter != null) {
    hunter.attach(action);
    return;
  }

  if (service.isShutdown()) {    //如果线程池退出,就直接结束
    return;
  }

  //这一步是遍历 picasso 的 requestHandlers,找到合适的 requestHandler,构造 BitmapHunter
  hunter = forRequest(action.getPicasso(), this, cache, stats, action);
  hunter.future = service.submit(hunter);    //提交任务
  hunterMap.put(action.getKey(), hunter);
  if (dismissFailed) {
    failedActions.remove(action.getTarget());
  }
}

接着就是执行 BitmapHunter 的 run() 方法了,前面我们已经介绍过,这里就不赘述了。

总结一下发起图片请求后的整体流程:

  • 类调用次序:Picasso -> RequestCreator -> Dispatcher -> BitmapHunter -> RequestHandler -> PicassoDrawable
  • 一句话概括: Picasso 收到加载及显示图片的任务,创建 RequestCreator 并将它交给 Dispatcher,Dispatcher 创建 BitmapHunter (并为它找到具体的 RequestHandler) 提交到线程池,BitmapHunter 调用具体 RequestHandler,任务通过 MemoryCache 及 Handler(数据获取接口) 获取图片,图片获取成功后通过 PicassoDrawable 显示到 Target 中。

这段概括修改自: http://www.trinea.cn/android/android-image-cache-compare/

一张图片加载时打的 log:

深入浅出 Picasso 源码(下):常见功能实现原理分析及总结

这里写图片描述

取消、暂停、恢复加载如何实现

除了发出请求,取消、暂停、恢复加载请求的需求也比较常见,比如我们在退出一个页面时,那些还未完成的请求就应该被取消;在快速滑动列表时,可以先暂停请求,等滑动停下时再恢复,这样可以避免发出大量的请求。

我们先来看看 Picasso 是如何实现 取消请求的吧

picasso.load(url) 
                .placeholder(R.drawable.placeholder) 
                .error(R.drawable.error) 
                .tag(context) 
                .into(view);

        picasso.cancelRequest(view);   
        picasso.cancelTag(context);

Picasso 提供了两种取消方法:

  1. picasso.cancelRequest(view); //1.取消特定目标的加载请求
  2. picasso.cancelTag(context); //2.通过 tag 批量取消

先看取消特定目标的加载请求如何实现的:

//Picasso.cancelRequest(view)
public void cancelRequest(@NonNull ImageView view) {
  cancelExistingRequest(view);
}
//picasso.cancelExistingRequest(view)
void cancelExistingRequest(Object target) {
  checkMain();
  Action action = targetToAction.remove(target);    //1.移除要加载数据 map 中的数据
  if (action != null) {
    action.cancel();    //2.取消就是通过置一个标志位为 false,置空回调
    dispatcher.dispatchCancel(action);  //3.移除调度器里保存的未被执行的 action
  }
  if (target instanceof ImageView) {
    ImageView targetImageView = (ImageView) target;
    DeferredRequestCreator deferredRequestCreator =
        targetToDeferredRequestCreator.remove(targetImageView);   //获取这个 ImageView 可能有的延迟执行,取消
    if (deferredRequestCreator != null) {
      deferredRequestCreator.cancel();
    }
  }
}
//Dispatcher.performCancel(action) 
void performCancel(Action action) {
  String key = action.getKey();
  BitmapHunter hunter = hunterMap.get(key);
  if (hunter != null) {
    hunter.detach(action);  //移除 hunter 中的这个 action
    if (hunter.cancel()) {  //这个 hunter 没有操作了,移除
      hunterMap.remove(key);
    }
  }

  if (pausedTags.contains(action.getTag())) {    //如果处于暂停状态,也从暂停列表里移除
    pausedActions.remove(action.getTarget());
  }
}
//BitmapHunter.cancel()
public boolean cancel() {
  return action == null
      && (actions == null || actions.isEmpty())
      && future != null
      && future.cancel(false);
}

从上面的代码可以看到,取消指定目标的请求,主要做的是以下几步:

  1. 取消保存在 Picasso targetToAction map 里的数据
  2. 调用这个目标对应的 Action.cancel() 方法,就是通过置一个标志位为 false,置空回调
  3. 调用 action 对应的 BitmapHunter.detach(action)BitmapHunter.cancel() 方法,停止 runnable 的执行
  4. 如果处于暂停状态,也从暂停列表里移除

可以看到,取消一个请求要修改的状态好多。

接着看下通过 tag 批量取消如何实现:

public void cancelTag(@NonNull Object tag) {
  List<Action> actions = new ArrayList<>(targetToAction.values());
  for (int i = 0, n = actions.size(); i < n; i++) {
    Action action = actions.get(i);
    if (tag.equals(action.getTag())) {
      cancelExistingRequest(action.getTarget());
    }
  }
  //...
}

哈哈,其实就是遍历 Picasso 的 targetToAction 列表,如果其中的 action 的 tag 和指定的 tag 一致,就挨个调用上面取消指定目标的方法取消了。

接着看看如何实现的暂停请求。

暂停请求只有一个方法 picasso.pauseTag(context) ,最后调用到 Dispatcher.performPauseTag(tag) 方法:

//picasso.pauseTag(context)
public void pauseTag(@NonNull Object tag) {
  if (tag == null) {
    throw new IllegalArgumentException("tag == null");
  }
  dispatcher.dispatchPauseTag(tag);
}
void performPauseTag(Object tag) {
  if (!pausedTags.add(tag)) {   //首先添加暂停的 set 集合里,如果返回 false,说明这个 tag 已经暂停了
    return;
  }

  //遍历所有的 BitmapHunter,解除、暂停请求
  for (Iterator<BitmapHunter> it = hunterMap.values().iterator(); it.hasNext();) {
    BitmapHunter hunter = it.next();

    Action single = hunter.getAction();
    List<Action> joined = hunter.getActions();
    boolean hasMultiple = joined != null && !joined.isEmpty();

    //这个 Hunter 已经完成请求了,看看下一个是不是你要找的
    if (single == null && !hasMultiple) {
      continue;
    }

    if (single != null && single.getTag().equals(tag)) {  //找到了要暂停的
      hunter.detach(single);  //解除
      pausedActions.put(single.getTarget(), single);  //添加到暂停结合里
    }

    if (hasMultiple) {
      for (int i = joined.size() - 1; i >= 0; i--) {
        Action action = joined.get(i);
        if (!action.getTag().equals(tag)) {
          continue;
        }

        hunter.detach(action);
        pausedActions.put(action.getTarget(), action);
      }
    }

    //如果这个 hunter 没有请求并且停止成功了,就移除
    if (hunter.cancel()) {
      it.remove();
    }
  }
}

从上面的代码和注释可以看到,暂停指定 tag 的请求比较简单,就这么 2 点:

  1. 把这个 tag 添加到暂停 set 集合里,在其他的提交请求里会根据这个集合判断,如果一个请求在暂停集合里,就不会继续执行
  2. 遍历所有的 BitmapHunter,解除、暂停和这个 tag 关联的请求

最后看 Picasso 如何恢复指定 tag 对应的请求呢?

```

//picasso.resumeTag(context);

public void resumeTag(@NonNull Object tag) {

dispatcher.dispatchResumeTag(tag);

}

//Dispatcher.performResumeTag(tag)

void performResumeTag(Object tag) {

//如果这个 tag 并没有暂停,就返回

if (!pausedTags.remove(tag)) {

return;

}

//遍历暂停的 action 集合

原文  https://xiaozhuanlan.com/topic/0735824691
正文到此结束
Loading...