通过本篇文章你将学到如下内容:
经过前面对核心 API 的介绍,我们已经对 Picasso 有个大概的了解了,接下来通过不同的业务逻辑,来整体上掌握 Picasso 的实现流程。
主要看四个功能的实现:
经典的调用:
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);
}
从中可以看出的是:
LruCache ,底层实现是 LinkedHashMap() 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() 方法了,前面我们已经介绍过,这里就不赘述了。
这段概括修改自: http://www.trinea.cn/android/android-image-cache-compare/
一张图片加载时打的 log:
这里写图片描述
除了发出请求,取消、暂停、恢复加载请求的需求也比较常见,比如我们在退出一个页面时,那些还未完成的请求就应该被取消;在快速滑动列表时,可以先暂停请求,等滑动停下时再恢复,这样可以避免发出大量的请求。
我们先来看看 Picasso 是如何实现 取消请求的吧 。
picasso.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.tag(context)
.into(view);
picasso.cancelRequest(view);
picasso.cancelTag(context);
Picasso 提供了两种取消方法:
先看取消特定目标的加载请求如何实现的:
//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);
}
从上面的代码可以看到,取消指定目标的请求,主要做的是以下几步:
Action.cancel() 方法,就是通过置一个标志位为 false,置空回调 BitmapHunter.detach(action) 和 BitmapHunter.cancel() 方法,停止 runnable 的执行 可以看到,取消一个请求要修改的状态好多。
接着看下通过 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 点:
```
//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 集合