参考文章:
Picasso源码解析
一、简介
介绍:Picasso,可译为“毕加索”,是Android中一个图片加载开源库。
源码地址:https://github.com/square/picasso
二、功能特点
1、功能列表
2、功能介绍
2.1 图片的异部加载
2.2 图片转换
使用最少的内存完成复杂的图片转换,转换图片以适合所显示的ImageView,来减少内存消耗
也可以customTransformer方法,进行图片的具体调整。
2.3 加载过程 & 错误处理
Picasso支持加载过程中和加载错误时显示对应图片。
2.4 在Adapter中的回收不在视野的ImageView和取消已经回收的ImageView下载进程
2.5 从不同资源源加载
支持多种数据源 网络、本地、资源、Assets 等
//加载资源文件
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
//加载本地文件
Picasso.with(context).load(new File("/images/oprah_bees.gif")).into(imageView2);
2.6 自动添加磁盘和内存二级缓存功能
2.7 支持优先级处理
每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。
2.8 支持飞行模式、并发线程数根据网络类型而变
手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4, 4g 为 3,3g 为 2
2.9 “无”本地缓存
无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。
三、Picasso源码解析
Picasso.with(this).load(imageUrl).into(imageView)
3.1 with
一个单例模式,为了保证线程安全,使用的是双重校验锁。在Picasso创建的过程中又使用了Builder模式,最大的特点就是链式调用,使调用者的代码逻辑简洁,同时扩展性非常好。下面我看一下new Builder()中的方法。
在Builder的构造方法中就只是获取到当前应用级别的上下文,也就说明了Picasso是针对应用级别的使用,不会是随着Activity或是Fragment的生命周期而产生变化,只有当当前的应用退出或是销毁时Picasso才会停止它的行为。
接下来,我们看看build方法中到底做了什么事情。
在这个方法中主要初始化了Downloader、LruCache、PicassoExecutorService、RequestTransformer、Stats、Dispatcher、并且返回一个Picasso对象。
3.1.1 downloader 下载器
首先,我们先看downloader下载器,如果downloader==null的话,就会执行Utils.createDefaultDownloader(context)方法去创建一个下载器。
createDefaultDownloader方法中首先使用java反射机制来查找项目中是否使用了okhttp网络加载框架,如果使用了则会使用okhttp作为图片的加载方式,如果没有使用,则会使用内置的封装加载器UrlConnectionDownloader。
注:由于okhttp3的包名已更换,所以在这里都是使用内置的封装下载器,这个是一个小bug等待完善。当修复之后Picasso+okhttp3则是最理想的加载方式。
当然我们自己也可以自定义下载器,使用okhttp3 作为加载器。代码如下:
OkHttp3Downloader的下载地址:
https://github.com/JakeWharton/picasso2-okhttp3-downloader
接下来我们先分析OkHttpDownloader,然后在分析UrlConnectionDownloader,看看他们源码中到底实现了什么东西。
OkHttpDownloader
OkHttpDownloader的构造方法:
在构造方法中通过Utils.createDefaultCacheDir(context)设置了文件缓存
private static final intMIN_DISK_CACHE_SIZE=5*1024*1024;// 5MB
private static final intMAX_DISK_CACHE_SIZE=50*1024*1024;// 50MB
通过Utils.calculateDiskCacheSize(cacheDir),设置缓存的大小。
其中StatFs用于获取存储空间。
getBlockCount():文件系统中总的存储区块的数量;
getBlockSize():文件系统中每个存储区块的字节数;
最大缓存大小是50M。
通过defaultOkHttpClient()方法设置的OkHttpClient请求客户端。
UrlConnectionDownloader 接下来分析UrlConnectionDownloader 这个默认的下载器。
UrlConnectionDownloader中使用的是系统自带的HttpURLConnection进行网络请求的。
这个设置的缓存大小是和OkHttpDownloader大小是一致的。
static final intDEFAULT_WRITE_TIMEOUT_MILLIS=20*1000;// 20s
static final intDEFAULT_CONNECT_TIMEOUT_MILLIS=15*1000;// 15s
3.1.2 LruCache
Retrofit的默认文件缓存采用的是LruCache。
LruCache的构造方法如下:
通过Utils.calculateMemoryCacheSize(context),设置了缓存大小。
activityManager.getLargeMemoryClass(),为单个应用的最大内存使用。
LruCache的内部实现是采用的LinkedHashMap,来保存缓存图片。
LinkedHashMap:它继承与HashMap、底层使用哈希表与双向链表来保存所有元素,
LinkedHashMap是Hash表和链表的实现,并且依靠着双向链表保证了迭代顺序是插入的顺序。双向循环链表。
HashMap:它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。
区别在于HashMap并不是按插入次序顺序存放的,而LinkedHashMap是顺序存放的。
关于HashMap和LinkedHashMap的源码分析,我们日后详解。
我们首先分析LruCache的set方法。
在set方法中,最终调用了map.put()方法,将数据放到Hash表里面。在这个方法的最后有一个trimToSize(maxSize),他到底实现了什么尼?,首先我们看看它的源码实现。
从源码中,我们看出,当所插入的元素大小size大于maxSize时,LinkHashMap就把最旧的一个元素删除掉。get方法相对简单,我们就看一下源码实现。
LruCache.get()方法:
3.1.3 PicassoExecutorService线程池
PicassoExecutorService的构造方法如下图所示:
PicassoExecutorService继承的是ThreadPoolExecutor线程池
谈到线程池,我们先了解一下线程池的有点:
重用线程池中的线程, 避免因为线程的创建和销毁所带来的性能开销.
有效控制线程池中的最大并发数,避免大量线程之间因为相互抢占系统资源而导致的阻塞现象.
能够对线程进行简单的管理,可提供定时执行和按照指定时间间隔循环执行等功能.
ThreadPoolExecutor 的配置参数
corePoolSize: 线程池的核心线程数,默认情况下, 核心线程会在线程池中一直存活, 即使处于闲置状态. 但如果将allowCoreThreadTimeOut设置为true的话, 那么核心线程也会有超时机制, 在keepAliveTime设置的时间过后, 核心线程也会被终止.
maximumPoolSize: 最大的线程数, 包括核心线程, 也包括非核心线程, 在线程数达到这个值后,新来的任务将会被阻塞.
keepAliveTime: 超时的时间, 闲置的非核心线程超过这个时长,讲会被销毁回收, 当allowCoreThreadTimeOut为true时,这个值也作用于核心线程.
unit:超时时间的时间单位.
workQueue:线程池的任务队列, 通过execute方法提交的runnable对象会存储在这个队列中.
threadFactory: 线程工厂, 为线程池提供创建新线程的功能.
handler: 任务无法执行时,回调handler的rejectedExecution方法来通知调用者.
在这个构造方法中,我们重点了解PriorityBlockingQueue和Utils.PicassoThreadFactory()两个类或者功能方法。
PriorityBlockingQueue:它是无界阻塞队列,容量是无限的,它使用与类PriorityQueue相同的顺序规则。它是线程安全的,是阻塞的,具体详解会在数据结构中了解。
PicassoThreadFactory()最终使用的是PicassoThread线程工厂。我们简单了解PicassoThread的实现。
3.1.4 RequestTransformer
RequestTransformer主要是对RequestCreator创建的Request进行转换,默认对Request对象不做处理。源码中也证实了这一点。
3.1.5 Stats 图片的状态
Stats的构造方法如下:stats主要是用来统计缓存,下载数量等数据,一言以蔽之,就是保存图片的一些状态信息。
HandlerThread的详解请阅读handlerThread详解
HandlerThread的主要优点在于他是用的是子线程的Looper,所以说不占用主线程度 资源。
Stats里面自己实现了一个Handler,代码如下:
3.1.6 Dispatcher 核心类
这个类在这里起到了一个调度器的作用,图片要不要开始下载以及下载后Bitmap的返回都是通过这个调度器来执行的,后面进行进行详细分析。我们先看一下Dispathcher的核心构造方法。
控制的中心,控制线程的加载和取消、网络监听、消息处理等。
几个重要的参数,我们上面已经介绍了。主要简单介绍DispatcherThread和DispatcherHandler。
DispatcherThread是一个HandlerThread,DispatcherHandler是自定义的消息分发的。源码如下:
我们以dispatchSubmit为例。最终会调用Dispatcher的dispatchSubmit()方法。
Dispatcher的dispatchSubmit()方法主要是获取BitmapHunter实例,由这个实例来执行实际的下载操作。BitmapHunter本身是Runnable的一个实现,而这个实例最终是交由Picasso线程池进行运行的。这个实例最终是要放到this.hunterMap=newLinkedHashMap(),循环双向队列中。
那么这个BitmapHunter加载图片成功或失败后是怎么通知UI的呢?我们前面提到Dispatcher在Picasso中起到了一个调度器的作用,当图片加载完毕后自然也是通过这个调度器来更新UI,上面我们得到BitmapHunter的run方法会执行响应的下载任务,那么我们就去这个run方法中去看看。
BitmapHunter.java
我们可以看到成功就会调用dispatcher.dispatchComplete(this)方法,失败就会调用dispatcher.dispatchFailed(this)方法。接下来我们就去Dispather方法中看看就这个是如何实现的。源码如下:
经过handler消息处理后,就会执行dispatcher.performComplete(hunter)或者dispatcher.performError(hunter, false)。
图片下载完成之后,首先放到LruCache中,其实就是把操作先暂存在一个list中,等空闲的时候再拿出来处理,这样做得好处也是尽量减少主线程的执行时间,一方面防止ANR,另一方面快速返回,响应页面的其他渲染操作,防止卡顿用户界面。然后下载任务从hunterMap删除。然后执行batch(hunter)方法。
private static final intBATCH_DELAY=200;// ms
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH,BATCH_DELAY);
延时200毫秒之后,就来到了handlerMessage方法中。最终执行dispatcher.performBatchComplete()方法。
这个mainThreadHandler是在Dispatcher实例化时由外部传递进来的,我们在前面的分析中看到,Picasso在通过Builder创建时会对Dispatcher进行实例化,在那个地方将主线程的handler传了进来,我们回到Picasso这个类,看到其有一个静态成员变量HANDLER,这样我们也就清楚了。
执行到这里,图片已经马上出来了,hunter.picasso.complete(hunter),Picasso中一个Action提供了请求前后的衔接工作,对于我们现在的情况,Picasso使用了ImageViewAction来进行处理,也就是在ImageViewAction中的complete方法完成了最后的图片渲染工作。
最后调用了PicassoDrawable.setBitmap(target,context,result,from,noFade,indicatorsEnabled)方法。
最后执行PicassoDrawable,从这个构造方法中,我们就明白了placeholder是如何设置的啦。
在PicassoDrawable方法中,实现了这个功能。
dispatcher.performError(hunter, false)就不带大家详细分析了。最后后调用ImageViewAction的error方法。
至此Dispather分析完毕,至此我们留下一个疑问Dispather.dispatchSubmit(Action action),从哪里开始调用的。
3.2 load()方法
接下来我们分析,Picasso中的load方法,图片是如何进行网络请求的。
待续。。。。。