搭建项目,无非两个思路。一般情况下都是由上而下、从抽象到具体、先搭框架再实现细节,但是对人员的综合素养要求很高。我们选择另一条相反的思路,由下而上从细节到框架。等到对设计模式、组织架构等知识有一定的理解和实践经验之后,再在实际项目中采用第一种思路。
现在我们就拿使用范围广、难度也适中的图片加载器(ImageLoader)作为训练项目。
一、核心功能
如果仅仅是为了实现加载图片的功能,那么下面的代码完全满足:
测试代码:
效果图:

实际开发过程中,往往不是只加载一张图就完事了,而是以列表的形式展现给用户,所以我们就需要考虑图片加载速度,节省用户流量,防止大图片引发 OOM,多线程等问题。
我们先对上面的示例增加内存缓存和线程池功能,内存缓存直接使用 ANDROID 提供的 LruCache 类。
到目前为止,我们的 ImageLoader 中含有线程池、缓存系统、网络请求等,很消耗资源。因此,没有理由让它构造多个实例。使用单例模式来保证 ImageLoader 类只有一个实例存在。代码改造如下:
现在我们再审视代码,发现 ImageLoader 的内存缓存、多线程、网络加载等功能之间严重耦合,缓存相关逻辑、图片加载显示逻辑、多线程切换逻辑严重混淆在一起。如果我们要对内存缓存功能进行扩展优化,实现二级缓存,那么我们只能在 ImageLoader 类修改,还要保证不影响其他功能。这样,随着功能的增多,ImageLoader 类会越来越大,代码也越来越复杂,更别谈扩展性、灵活性。
必须对 ImageLoader 进行拆分,让各个功能独立出来。比如内存初始化、存取逻辑可以单独抽出缓存类来 ImageCache;下载网络图片逻辑也是一样处理。
二、职责拆分
ImageCache
首先我们先把 ImageCache 类拆分出来,它负责缓存相关的职责,比如初始化缓存大小,并提供存(put)、取(get)、移除(remove)接口。代码如下:
其中 LruCache 是线程安全地类,所以在多线程环境中,没有必要对 put、get 等使用 synchronized 关键字进行同步。
这样一来,ImageLoader 只需要持有 ImageCache 的对象即可,不必关心其中的细节。因为 ImageLoader 是单例对象,那么持有的缓存也是只有一个实例的。那么 ImageLoader 可以向其他功能提供统一的访问接口 getCache()。ImageLoader 需要的修改的代码如下:
RequestThreadPool
接下来把异步请求代码块移植到 RequestThreadPool 类中,由 RequestThreadPool 专门负责异步下载。这样 ImageLoader 类就非常简单了:
毕竟大部分业务逻辑全迁移到 RequestThreadPool 类中,而 ImageLoader 只作为程序入口。RequestThreadPool 代码如下:
Loader
到这里结束了吗?没有,还可以继续拆,拆 RequestThreadPool 类,将其中下载模块拆成一个新类 Loader:
从上述代码中可以看出 Loader 加载图片的过程有如下几个步骤:
- 判断缓存中是否含有该图片;
- 如果有则将图片直接投递到UI线程,并且更新UI;
- 如果没有缓存,则从对应的地方获取到图片,并且将图片缓存起来,然后再将结果投递给UI线程,更新UI;
既然 Loader 类承担了下载图片职责,那么 RequestThreadPool 类就只有调度异步任务的职责了,从 ImageLoader 接收到一个图片加载请求之后,创建/分配线程,调用 Loader 提供的接口执行图片加载程序。其代码如下:
这里有个问题,线程池每执行一个异步任务,都会创建新的 Loader 对象。很明显这是不合理的,我们可以使用单例模式来优化 Loader 类。代码不再给出,请大家参照 ImageLoader 的单例形式自行编写。
由一个类拆成 4 个类,虽然避免了将来 ImageLoader 类的臃肿膨胀,但也使得整个系统的变得复杂,这样做值得吗?我只想说,这才刚开始。欲知后事如何,请看下篇文章。
