️This article has been over 2 years since the last update.
A brief source code review of Picasso
好久没有碰Android了,花一个小时看下Android下的Picasso吧
1 | git clone --depth=1 https://github.com/square/picasso.git |
最近看厂里的糟糕代码太多,看优秀的代码速度反而比以前快多了,Picasso由于把大量的网络请求等累活都转到OkHttp3中了,相比于Spring、Tomcat等重量级项目,架构简单得多。
本文按照What How Why原则进行试点写作,按照28定律,看到How的人就是少数的20%了。
本文代码基于Picasso的eac9b2b 版本进行的分析
1. Picasso是什么
Picasso是一个Android下的网络图片加载框架,内部自动实现了线程池控制、网络下载、异步更新界面等功能,举个例子。
1 | Picasso.with(context).load("http://example.com/123.png") |
通过使用Picasso,可以将重心放在业务上,而不用担心图片加载、线程池管理等性能问题。
2. Picasso主要技术点分析
- 网络请求与下载:直接复用OkHttp,支持HTTP2.0,本文只用知道OkHttp是一个网络框架即可。
- 消息队列(MQ):直接复用的Android自带的消息队列Handler,比服务器开发各种MQ简单多了
- 请求分发与回调:在消息队列Handler的基础上进行封装为RequestCreator与Dispathcer,一个用于发送,一个根据Message.What进行路由
- 图片缓存的实现: 对LinkedHashMap的包装实现LruCache,内部维护着hitCount与missCount,用于对缓存进行统计。
- 内部线程池:通过网络广播监听器,构造PicassoExecutorService可以在不同网络下设置线程的数量,比如WIfi下是4个线程,4G下是3个线程;并且通过优先队列控制任务的优先级
总体上来说,Picasso是一个代码易懂,不用注释都可以读的项目。
2.1. 图片请求与下载
以下为无缓存进行首次网络请求的场景。当调用Picasso.with(context).load(url)
后,将构造一个RequestCreator,它将请求的字符串转为了一个Uri的包装对象
接着调用RequestCreator.into(imageview)
时,后面经过多次构造,最后产生了一个Action对象,内部有Url,执行入队操作
1 | //将请求进行入队,即放入Handler中 |
对REQUEST_SUBMIT
进行findUsage搜索,可以发现请求被路由到这里了
1 | //method: DispatcherHandler.handlerMessage() |
充分利用intellij的FindUsage的方法,可以减少读码的时间
接着分析performSubmit,可以发现它构造了一个BitmapHunter,这里开始构造匹配Uri的BitmapHunter
1 | //method: performSubmit() |
内部代码太长太丑,总的来说就是一个filter操作,根据Uri的Scheme匹配不同的RequestHandler,这里就用js伪代码讲吧
1 | //method: forRequest(); |
由于我们的请求是网络请求,它的Scheme的regex是http[s]
,因此最后过滤出来的是NetworkRequestHandler,它内部维护着一个Downloader,在Picasso最新版中由OkHttp3Downloader实现。
接着hunter将请求提交给PicassoExecutorService线程池
1 | hunter.future = service.submit(hunter); |
最后终于交给干活的人(OkHttp3Downloader)了,走到NetworkRequestHandler的load方法
1 | //交给OkHttp3Downloader进行处理网络 |
load方法就是OkHttp的同步阻塞调用的网络请求了,请求完成后将Body解码为Bitmap。
注意这里是同步阻塞调用,也就是说OkHttp内部没有维护请求队列与线程池
最后完成请求后通过Handler发送消息
1 | void dispatchComplete(BitmapHunter hunter) { |
经过多次Handler的消息传递,最终调用Action的complete方法,在UI线程更新ImageView
1 | //method: ImageViewAction.complete() |
综上,总的来说,就是首先构造一个Uri,然后构造Action,根据Uri选择匹配的RequestHandler,最终交给定制过的Picasso线程池执行RequestHandler的load方法,获取并解码图片后,通过Handler在UI线程更新imageVIew中的PicassoDrawable
2.2. Picasso的缓存实现
Picasso维护着一个基于内存的LruCache,通过maxSize控制缓存容量
1 | //file: LruCache.java |
关于LRU,要注意的是OkHttp是基于文件的,而Picasso是基于内存的缓存,它们两个是没有关系的。
有关图片的缓存优化,这里就可以看出来了。主要就是通过减小Bitmap的体积或者LRU的
maxSize
来实现了,具体就是用更小的图片Config、分辨率、用更小的LRUcache。
2.3. Picasso中的线程池
Picasso专门定制了一个线程池,可以控制优先级与线程数,构造函数如下
1 | PicassoExecutorService() { |
注意PriorityBlockingQueue是基于堆排序实现的,每个Runnable任务需要实现Comparable接口,以实现优先级的区分,通俗的说,就是优先级高的任务可以插队到前面执行。
至于线程池中线程数量的控制,通过NetworkBroadcastReceiver广播接收器控制,当网络状态发生改变后,通过Handler发送网络情况,线程池收到消息后,对线程数量进行调整。
1 | //method: PicassoExecutorService.adjustThreadCount() |
3. 总结
Picasso通过高效利用Handler消息队列,实现异步请求、下载与分发;通过RequestHandler列表,支持多种Uri协议下的图片下载;通过LruCache实现基于内存的缓存;通过定制的线程池,实现优先级与线程数的控制;
最后总的推荐就是,Picasso代码量不高,推荐各位有时间看一下,以后的大趋势是Android会少而精,看不懂源码的说不定会失业的。