以下代码笔记基于 commitId:a07bd18608800555f7ba6a4dea038be63188b831,commit 时间:2013/3/19 9:18 AM。点击上面的 commitId 可以跳转到 github 看代码,配合本文阅读。
本系列的文章结构包括以下 5 个部分。重构 是同样功能的代码的变动。feature 是对比上次提交,这次提交的新功能。设计 是我觉得可以提一下的代码设计,这部分可能不同的程序员会做不同的设计。疑惑 是我看代码过程中觉得有问题或者不懂的地方。知识点 是关于 Java 或者安卓的一些通用知识。
上个版本,每一次请求都会直接从网络下载。这次新增了缓存,这样已经下载的图片就不需要重新下载。缓存分成了两部分,一是硬盘缓存,二是内存缓存。硬盘缓存可以让缓存数据在 app 退出后也存在,内存缓存可以让缓存数据加载更快。缓存策略采用的 LRU 算法,固定大小,如果满了,优先淘汰下载早的和不常使用的图片。具体实现可以看 DiskLruCache.java 和 LruMemoryCache.java 这两个类,本文先不展开。
上个版本,工作逻辑是放在 Request 的 run
方法里,可能由于工作逻辑涉及到的很多变量都在 Picasso.java 里面,所以把工作逻辑放到了 Picasso.java 里。下面来看 Picasso.java 里面的 void run(Request request)
方法。
这里我简单地把主要的代码逻辑阐述一下:
这个版本支持图片变换,内置了四种图片变换,用户还可以自定义图片变换,只需要实现 Transformation
接口里面的 Bitmap transform(Bitmap source)
方法。图片变换通过 Builder 配置。具体的变换算法,这里先不展开,主要看一下图片变换是在哪里应用的。可以看 Picasso.java 的 transformResult
方法,这个方法会把网络下载后的图片通过一系列的变换,再返回变换后的图片给到外面使用。
上面一直没有提到如果任务失败会怎么办,这个版本对失败情况也做了处理。在 Picasso.java 的 void loadFromStream(Request request)
方法里,可以看到 catch (IOException e)
后,会通过 handler 发送一个 RETRY_REQUEST
消息(成功时发送的是 PROCESS_RESULT
消息)。
失败重试没有涉及到 UI 线程操作,这里为什么要通过 handler 切换到主线程呢? handler 除了切换线程的功能,还有一个消息队列的功能。仔细看发送重试消息的代码 handler.sendMessageDelayed(handler.obtainMessage(RETRY_REQUEST, request), RETRY_DELAY);
,这里用的 sendMessageDelayed
,Delayed 代表不会马上执行,RETRY_DELAY = 500
会延时 500 毫秒。因为这时候失败了,如果马上重试,大概率也会失败,所以需要延时。
失败重试还有一个最多重试次数的设计,这可以跟踪 retryCount
变量来看相关逻辑。最后注意的是,重试前也要判断请求是否已经被取消。
可以通过 Biuder 来配置 bitmapOptions ,图片解码从 BitmapFactory.decodeStream(stream)
变成了 BitmapFactory.decodeStream(stream, null, request.bitmapOptions)
。
按道理工作逻辑应该要放到 worker 也就是 Request.java 里面,但是这次放到了 Picasso.java 里面。为什么会提出这个问题呢?可以看到在 handler 里出现的
request.picasso.complete(request)
和 request.setFuture(request.picasso.service.submit(request))
,这种代码读起来很绕。
要做缓存,首先要想的是缓存的 key 和 value 要存什么。从 Picasso.java 的 void loadFromStream(Request request)
方法里的 saveToCaches(path, result)
可以知道,
缓存的 key 是图片的 url,value 是变换后 bitmap 。下一个请求如果用同一个 url ,所需的变换可能不一样,但是却拿到上一次变换后的图片,这样是有问题的。应该 key 改成和变换相关,或者 value 保存原图,从缓存拿出来再做变换。后面我们可以关注 Picasso 是怎么修复这个问题的。
在 Builder 的 into
方法里,可以看到先查询了内存缓存里有没有这个图片,如果有就不会把 request 提交到线程池里。为什么呢?因为内存缓存读取很快,如果提交到线程池里还需要等待线程池分配,所以就没必要提交到线程池了。这个小的设计,让请求既可以快速响应,又减少了线程池的负担。
假设我们把缓存功能加上了,我们怎样快速地知道哪些图片是从网络下载的,哪些图片是从缓存加载的?Picasso 会给每个请求增加一个字段来标明图片来源,最后在图片渲染的时候,根据不同的来源,通过 setBackgroundColor()
来给图片设置不同的背景颜色,这样在 app 上面就可以一目了然了。这部分代码可以看 RequestMetrics.java 类和它的使用。
这里可以看 external 文件夹里面的文件。本文先不展开。
这里可以看 transformations 文件夹里面的文件。本文先不展开。