以下代码笔记基于 commitId:07ee0f5ee53383eaa06b619879b247f628a77133,时间:2013/2/16 2:46 PM。点击上面的 commitId 可以跳转到 github 看代码,配合本文阅读。
本系列的文章结构包括以下 5 个部分。重构 是同样功能的代码的变动。feature 是对比上次提交,这次提交的新功能。设计 是我觉得可以提一下的代码设计,这部分可能不同的程序员会做不同的设计。疑惑 是我看代码过程中觉得有问题或者不懂的地方。知识点 是关于 Java 或者安卓的一些通用知识。
设计
链式调用可以让代码更加易读和更容易调用。
Picasso 的主要功能是帮用户下载图片,图片主要用于在 ImageView 中显示。
最简单的情况,我们可以写这样一个函数 load(String url, ImageView imgView)
来实现这个功能。而 Picasso 一开始的 API 是这么设计的 load(url).into(imgView)
,这可以很轻松地用口语读出来:load an url into a imageView ,我把这种方式称为链式调用。
链式调用还可以让代码更容易调用,调用借助 into 方法的语义,可以清楚地知道 imageView 应该是 into 的参数,而如果用第一种 API,参数需要按照固定顺序,需要记住第一个参数是 url ,第二参数是 imageView ,然后这样调用load(url, imgView)
。如果考虑到需要更多的参数,比如要增加设置加载过程的缩略图和加载失败的图,第一种就可能变成load(url, imgView,R.drawable.placeholder, R.drawable.error)
,这样就更难区分参数的位置,而链式调用可以变成这样:
load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(imgView);
非常清晰好记。
Builder 模式与链式调用
这样的链式调用可以用 Builder 模式来实现,组合各种参数来生成一个对象。可以看到下面的代码里面有一个静态内部类叫做 Builder,它负责生成 Request 对象。如果有一些参数不是必须的,利用 Builder 模式可以很简单的实现可选参数和默认参数的功能。关于 Builder 模式和多参数构造,《Effective Java》的第二章的 Item 2 也有讲述。
在这个版本,还没实现下载图片功能,所以 into 方法只是 new 了一个 Request , 后面的版本中,into 还需要触发下载图片的功能。
public class Request {
private final String url;
private final WeakReference<ImageView> target;
Request(String url, ImageView imageView) {
this.url = url;
this.target = new WeakReference<ImageView>(imageView);
}
public static class Builder {
private final String url;
public Builder(String url) {
this.url = url;
}
public Request into(ImageView target) {
return new Request(url, target);
}
}
}
类的分工
这次的提交主要是四个类 Cache.java、Downloader.java、Picasso.java、Request.java,将缓存和下载功能分成两个类来实现很符合直觉,需要注意的是如何区分 Picasso.java 和 Request.java 的功能。我是这么理解的,这个项目本质上是一个多线程下载的库,所以需要一个类来做控制的功能,例如任务分配、线程池配置等,这对应的Picasso.java。还需要一个类来对应每一个任务,通常称为 worker ,负责保存任务的信息,执行对应的任务。worker 通常是个 Runnable ,扔到线程池里面,等待被执行。
知识点
弱引用 ImageView
Request 里面的 ImageView 变量用了弱引用WeakReference<ImageView> target
。这是为了避免造成内存泄露。
如果用强引用,可能外面的 ImageView 已经销毁,但是任务还没有执行,这时候任务持有 ImageView 的强引用,会导致 ImageView 涉及到的资源不会马上被回收,要等到任务结束才被回收。
用了 WeakReference,外面的 ImageView 销毁后可以正常(注意,不是马上)回收。注意,需要在任务结束时,判断 target.get()
是否返回空,如果为空,就代表 ImageView 已经被回收了,所有不需要处理这次请求了。
final 修饰变量
可以看到有很多 final 修饰的变量,如果变量只需要设置一次,后面不需要变更,最好设置为 final 。
静态内部类
如果内部类不要访问外部类的东西,最好设置为 static ,例如 Builder 。