以下代码笔记基于 commitId:12a29ab70ab96c955ad341d90b25ed9fbd794f9c,时间:2013/2/19 3:05 AM。点击上面的 commitId 可以跳转到 github 看代码,配合本文阅读。
本系列的文章结构包括以下 5 个部分。重构 是同样功能的代码的变动。feature 是对比上次提交,这次提交的新功能。设计 是我觉得可以提一下的代码设计,这部分可能不同的程序员会做不同的设计。疑惑 是我看代码过程中觉得有问题或者不懂的地方。知识点 是关于 Java 或者安卓的一些通用知识。
上次提交的 Downloader.java 只是一个 interface ,没有具体的实现。这次在 ApacheHttpLoader.java 实现了这个 interface ,里面用 AndroidHttpClient 来做图片下载功能。AndroidHttpClient 已经过时了,可以不看这部分代码。
上篇文章说到 Picasso.java 会充当任务控制的角色。上次提交的 Picasso.java 里面什么都没做,这次完善了 Picasso.java 的功能。主要分成三个部分
组合 Loader 来实现图片加载功能。可以看 Picasso.java 的 loader
变量。
线程分配用的线程池是 SingleThreadExecutor ,相当于单线程。可以看 Picasso.java 的 service
变量。
为什么用单线程呢?应该是一开始为了让项目快速运行起来,用单线程可以避免处理一些多线程并发问题。
取消之前的任务。
Picasso.java 维护一个任务(Request)和 ImageView 对应的 Map ,保存每一个请求和请求对应的 ImageView 。可以看 Picasso.java 的 Map<ImageView, Request> targetsToRequests
变量。
为什么要持有这个 Map 呢?这是考虑到如果来了个新的请求,和之前某个请求是同一个 ImageView ,这时候我们希望忽略之前的那个请求。要判断之前的请求是否对应同一个 ImageView ,则需要把之前的 ImageView 和 Request 用 Map 保存起来。
那么,有了这个 Map 如何取消之前的那个请求呢?任务提交到线程池后,会返回一个 future 对象,需要在 Request 里把这个对象起来。当发现之前的请求是同一个 ImageView 后,可以从 Request 里把 future 取出来,调用 future.cancel(true)
。如果此时请求还在线程池里等待执行,则会被线程池忽略。如果请求正在被执行,我们需要在请求执行完毕后,调用 future.isCancelled()
来判断请求是否已经被标记为失效,如果是,则不处理,这部分可以看 handler
的 handleMessage
方法。
上篇文章说到 Request.java 会充当 worker 的角色。上次提交的 Request.java 什么都没有,只是保存了请求的信息,这次提交完善了 Request.java 的功能。
可以看到它实现了 Runnable 接口,首先来看 run
方法,调用了 loader 来下载图片,再解码图片,因为安卓在子线程不能更新 UI ,所以通过 handler
来切换到主线程。再看 handler
的 handleMessage
方法,这里把下载好的图片设置给 ImageView 。
上篇文章说到 Builder 的 into
方法除了生成 Request 对象,还需要触发请求任务,所以这次提交在 into
方法里面将生成的 Request 对象提交给 picasso 的线程池,这样任务就可以被线程池执行。
Builder 还新增了配置 placeHolder 的功能,placeHolder 是在下载之前,ImageView 可以显示的图片,例如你可以设置一个加载中的提示图。placeHolder 是可以缺省的,在 into
方法里面只有判断 placeholderDrawable
或者 placeholderResId
不为空才会设置给 Request 对象。同样,假如你想设置一个默认的 placeHolder,你可以在 into
方法判断 placeholderDrawable
或者 placeholderResId
为空时,设置你想默认的 placeHolder。这就是 Builder 模式的方便之处。
这是考虑到图片的来源不一定是通过网络下载,也可以是从本地文件加载,方便后面扩展。
Request 和 ImageView 对应的 Map 用的是 WeakHashMap,和上一篇文章说的一样,这是为了避免内存泄露。仔细看,你还会发现除了在发现有重复的请求时,有调用 map 的 remove
方法,其他地方就没调用了。也就是说当请求结束后,没有把请求从这个 map remove 出去。这是一个 bug 吗?还是 WeakHashMap 带来的一个方便之处?
这个可以看我之前写的 Handler 机制模型 ,简单来说,Handler 是和 Looper 挂钩的,所以只需要找到主线程的 Looper 就行,代码如下:。
private static final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { } }; 复制代码
注意,这里的 handler
对象是全局的,不是每个 worker 独有的,所以要设为 static 。