前面写的5篇博客让我们对Picasso的用法有了很详细的了解,Android的框架层出不穷,每年都会开源出大量的优秀的框架,如果我们只是站在一个使用者的角度去不断的接触框架,而不去学习框架底层的实现原理,那样顶多也只能一直当新手,而不是老司机了,下面我们就从最基本的入手,从源码的角度去剖析Picasso的工作原理(该分析都是基于Picasso 2.5.2版本).Let’s Go.
官方给出的一个最简单的用法,一个链式调用就能实现一张图片的请求及展示:
Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);
只需这一行代码,Picasso就能从我们提供的URL地址中去请求图片,然后将图片展示到targetImageView
中,我们就从最简单的开始,一层层的还原Picasso最底层的本质.
Picasso.with(context)
public static Picasso with(Context context) {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}
这段代码就是为了初始化Picasso,获取一个Picasso的实例,该类代码具有很大的共性,很多框架在获取实例时基本都是采用了 单例模式+建造者模式
- 单例模式特点:
- 采用后加载机制,保证实例只有在被使用的时候才被创建
- 采用双重检验锁模式,保证了在多个线程同时请求的过程中,只创建唯一的单例对象
- 同步对象为类本身,保证了该对象的同步范围在整个java虚拟机中,也就是全局的
- 利用建造者模式创建一个默认的Picasso对象,一般来说使用建造者模式,是因为构建该对象本身时需要创建其他的类对象,也是保证了多个类的初始化,看代码
new Builder(context).build()
/** Create the {@link Picasso} instance. */
public Picasso build() {
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
if (cache == null) {
cache = new LruCache(context);
}
if (service == null) {
service = new PicassoExecutorService();
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
Stats stats = new Stats(cache);
picasso.Dispatcher dispatcher = new picasso.Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}
在该方法中主要对初始化了以下参数:
- Downloader
- 下载工具类,该
Utils.createDefaultDownloader(context);
如果OKHttp可用的话,会返回一个OkHttpDownloader
,否则的话会返回一个UrlConnectionDownloader
(内部使用HttpURLConnection实现)
- 下载工具类,该
Cache
该类的默认实现为
LruCache
,也就是利用最近最少使用算法进行缓存,内部是一个LinkedHashMap
,这里要注意的是,一般我们在初始化LinkedHashMap
时,会限制initialCapacity
容量值,但此处是在初始化LruCache
时通过调用Utils.calculateMemoryCacheSize(context)
来计算出最大的堆内存值然后划分出7分之一,也就是15%作为容量大小,所以在每次调用set()
方法进行缓存时,都会调用trimToSize()
不停的计算当前可使用的空间大小,超出范围就删除之前保存的数据;Utils.calculateMemoryCacheSize(context)
的代码块
static int calculateMemoryCacheSize(Context context) { ActivityManager am = getService(context, ACTIVITY_SERVICE); //计算出最大的堆内存值,也就是可用大小值 boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0; int memoryClass = am.getMemoryClass(); if (largeHeap && SDK_INT >= HONEYCOMB) { memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am); } //分配各15%的大小 // Target ~15% of the available heap. return 1024 * 1024 * memoryClass / 7; }
ExecutorService
- 默认实现类
PicassoExecutorService
,也就是ThreadPoolExecutor
子类而已,默认线程数是3,但是PicassoExecutorService
会在不同的网络状态下调整线程数,wifi
下为4个线程,4G
状态为3个线程,3G
状态下为2个线程,2G
状态下为1个线程.
- 默认实现类
- RequestTransformer
- 一个接口,是为了在图片展示之前进行预处理,我们之前在Picasso的用法中讲到过,详见Picasso最全使用教程 四
- Stats
- 主要记录一些信息,比如下载的总大小值,缓存大小值,在之前的博客中有用到过,详见Picasso最全使用教程 四,用到了
StatsSnapshot
- 主要记录一些信息,比如下载的总大小值,缓存大小值,在之前的博客中有用到过,详见Picasso最全使用教程 四,用到了
Dispatcher
负责分发和处理 Action,包括提交、暂停、继续、取消、网络状态变化、重试等等。这个类是我们着重要分析的类,我们先来看一下默认的操作都有哪些
在构造方法中,将之前的
ExecutorService
,Downloader
,Cache
,Stats
进行了赋值在构造方法第一行就创建了
DispatcherThread
类,启动了一个线程在构造方法中创建一个
DispatcherHandler
,并将DispatcherThread.getLooper()
作为参数传入该handler的构造函数中,意味着该Handler处理的数据都是从DispatcherThread
发出的- 初始化并添加了一个对网络监听的广播
上面就是关于.with(context)
所做的分析,我们继续向下分析.
.load(String url)方法源码解析
我们现在看.load(String url)
方法都做了什么操作,其实.load(url)
最后调用的依然是.load(Uri uri)
public RequestCreator load(String path) {
//对path进行判空,省略部分代码
....
return load(Uri.parse(path));
}
//其实调用的是这个方法
public RequestCreator load(Uri uri) {
return new RequestCreator(this, uri, 0);
}
所以加载图片的本质就是我们通过Picasso
调用了load(url)
返回了一个new出来的RequestCreator
示例,同时将Picasso
,uri
地址, 还有resourceId
也就是0,作为构造参数传入,下面我们就看看该构造函数都做了哪些操作;
RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
- 先判断Picasso是否已经启动,如果没有启动,就直接抛出异常
- 对picasso进行赋值
这里又用到了建造者模式使用
Request.Builder()
将创建的Builder
赋值给data
,我们来看该部分的代码代码如下:
this.uri = uri; this.resourceId = resourceId; this.config = bitmapConfig; }
我们看到,这里只是做了赋值操作,并没有做其他事,
Builder
还有其他几个构造方法,也都只进行了赋值操作,并未做其他处理,等我们分析完之后,就会明白此处代码的用意. 贴出来两个,另一个Builder (Request request)
就不在贴出,部分代码如下public Builder(Uri uri) { setUri(uri); }
public Builder(int resourceId) { setResourceId(resourceId); }
最重要的方法.into(target)
方法解析
.into(target)
方法是ReqeustCreator
中的方法,所以我们去一窥究竟,其实.into(target)
还是在内部调用的.into(target,callback)
,看代码
public void into(ImageView target) {
into(target, null);
}
下面我们就着重看一下.into(target,callback)
都做了哪些操作,代码较多,我们逐一进行分析
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
//1 检测是否在主线程中执行,如果不是就抛异常
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//2 该判断是只有在uri为null,并且resourceId为0的情况下才会执行相应逻辑,取消请求显示默认图片,然后结束
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}
//3 是否延时加载,默认为false,只有当调用了 .fit() 才会执行该逻辑
if (deferred) {
//调用 .fit() 之前,不能调用 .resize(w,h) ,否则抛出异常
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
//target是否已经完全绘制出来,如果没有,则执行相关操作
if (width == 0 || height == 0) {
//是否设置了默认图片,在进行延时请求前先展示默认图片
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
// 加入延时队列,同时创建一个DeferredRequestCreator去测量target的宽高,结束任务
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
//target已经绘制完成,调用resize方法,并继续向下执行
data.resize(width, height);
}
//4 创建一个request,并根据request生产一个requestKey
Request request = createRequest(started);
String requestKey = createKey(request);
//5 判断可否从内存中读取资源,默认为可以
if (shouldReadFromMemoryCache(memoryPolicy)) {
//从内存中获取,如果能获取到就取消请求操作,并执行callback.onSuccess()的回调,结束任务
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
if (bitmap != null) {
picasso.cancelRequest(target);
setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}
//6 判断是否设置了默认图片,在进行请求前先展示默认图片
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//7 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}
部分功能说明已在代码中标出,但还是用一张示意图来进行解析比较简单易懂
图画的比较丑,但是看着还算比较清晰,我们在按照代码,图示,再用文字描述一遍,让更有深的认识;
- 检测是否在主线程中执行,如果不是就抛异常,可能是要更新UI显示,所以要在主线程中执行代码逻辑
- 该判定方法
data.hasImage()
容易让人误解为是否已经有图片获取好了,但其实是为了判断在Builder创建的时候,是否设置了uri
或者resourceId
,如果没有设置uri
并且resourceId
为0,说明该请求是一个错误请求,就可以加载默认图片并取消请求操作; - 是否延时加载,默认为false,只有当调用了
.fit()
才会执行该逻辑,如果有同学不太理解.fit()
的作用,可翻看我之前写的博客,其实就是为了重新计算target的宽高值,而且不能在调用了.resize()
之后再次调用.fit()
- 创建一个request,并根据request生产一个requestKey
- 判断可否从内存中读取资源,默认为
true
,如果有同学不知道怎么设置跳过内存读取这一步骤,可翻看我之前写的博客 - 判断是否设置了默认图片,在进行请求前先展示默认图片
- 这时是的确需要联网请求了,生成对应的 ImageViewAction,并将 action 添加到 Picasso的请求队列中
OK,到现在我们已经大致了解了Picasso.with(this).load("http://i.imgur.com/DvpvklR.png").into(target);
都做了哪些操作,至于第7步之后又做了哪些操作,我们将继续分析,还原她的本质,愿大家都有美好的一天…