Picasso从使用到原理详解

Picasso是SquareUp公司开源的专门为Android平台量身制作的图片加载库。通过Picasso,用户可以方便的将图片加载到特定的ImageView中,而不用关心图片是在一个文件夹里,还是在一个服务器上。那么,Picasso是如何何实现图片的快速加载了?

  • 利用两级缓存机制对图片进行缓存: 加载一个图片时,首先从内存中查看是否存在;如果不存在,则查看外部存储是否有该图片。这时,如果还没有找到,则通过网络端下载图片;
  • 利用OkHttp库进行图片下载,下载后保存到缓存,下次请求时无需从网络端下载;如果出现网络错误,会自动重试下载;

使用示例

Picasso提供了好几个调用接口,可以从文件或资源文件,也可以从网络下载图片.比如,现在要从网络上下载一张图片,并将其加载到一个ImageView上去,可以这么来调用接口:

1
2
3
4
5
6
7
8
9
10
11

public static void load(Context context, String url, ImageView target, @DrawableRes int placeholder){

Picasso.with(context)
.load(url)
.placeholder(placeholder) // 未下载完之前显示的占位图片
.centerCrop() // 保持图片尺寸与屏幕长宽比一致
.resize(512,(int)(512*DisplayUtil.getScreenRatio(context)))
.into(target);
}

那么,如果想要监听图片加载完成这一事件,则需要自己手动实现`Target这个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

private class ImageTarget implements Target{
private ImageView image;

public ImageTarget(ImageView view){
this.image = view;
}

@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Log.v(TAG,"onBitmapLoaded(): bitmap size " + bitmap.getByteCount() + ",loaded from " + from);
image.setImageBitmap(bitmap);
}

@Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.v(TAG,"onBitmapFailed()");
image.setImageDrawable(errorDrawable);
}

@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {

}
}

然后将其作为参数传入:

1
2
3
4
5
6
7
8
9
10
11

public static void load(Context context, String url, Target target, @DrawableRes int placeholder){

Picasso.with(context)
.load(url)
.placeholder(placeholder)
.centerCrop()
.resize(512,(int)(512*DisplayUtil.getScreenRatio(context)))
.into(target);
}

具体代码可参考: https://github.com/runningforlife/AndroidExamples 中ImageLoader部分代码

源码解析

下图是Picasso的简单框图,用户调用into(ImageView iv)接口后,Picasso将其该下载图片的请求包装成一个ImageViewAction,接着由Picasso负责发送到Dispatcher中;Dispatcher接着会把该Action继续封装成一个可执行对象BitmapHunter,接着将其提交到线程池PicassoExecutorService中执行;如果此时发现内存中已经有图片,则直接返回,否则BitmapHunter将通过一个Downloader下载图片,下载图片完成后,BitmapHunter会将结果返回给Dispatcher,最后由Dispatcher告知Picasso图片加载完成。

这里就来看一看Picasso库的几个核心类:

Picasso.java

从构造函数来看,Picasso类构造时需要初始化整个库,通过RequestCreator来产生图片加载请求Request,发送到Dispatcher中准备执行; 等图片加载完成后,还需要通过主线程告知用户图片已经加载完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats, Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
...
// 请求分发
this.dispatcher = dispatcher;
// 缓存
this.cache = cache;
...
// 构造不同的RequestHandler对象,用以处理不同类型的Request
allRequestHandlers.add(new ResourceRequestHandler(context));
if (extraRequestHandlers != null) {
allRequestHandlers.addAll(extraRequestHandlers);
}
allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
allRequestHandlers.add(new MediaStoreRequestHandler(context));
allRequestHandlers.add(new ContentStreamRequestHandler(context));
allRequestHandlers.add(new AssetRequestHandler(context));
allRequestHandlers.add(new FileRequestHandler(context));
allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
requestHandlers = Collections.unmodifiableList(allRequestHandlers);
// 统计图片下载请求
this.stats = stats;
}

几个重要函数:

  • load(...): 加载图片,资源可是一个URL,也可以是一个文件路径,可以是一个资源文件ID;
  • cancelRequest(ImageView): 取消某个ImageView的图片加载请求;
  • invalidate(...) : 清除缓存;
  • shutdown: 关闭Picasso,取消所有已存在的Request;
  • enqueueAnsSubmite(Action) : 将图片下载动作ImageViewAction保存到一个Map,并发送给Dispatcher处理;

ImageViewAction.java

每一个图片下载请求都封装成一个ImageViewAction,当图片下载完成后,有错误,则调用error(Exception)接口,若用户设置了错误时对应的图片,则直接显示该图片;没有错误,则调用complete(Bitmap, LoadedFrom),将下载完成的Bitmap显示出来。

  • complete(Bitmap,LoadedFrom): 图片加载完成

Dispatcher.java

接收来自Picasso.java的请求动作,产生一个BitmapHunter可执行对象用于下载图片;Dispatcher并不是在一个请求完成之后就发送给Picasso,而是采用批处理的方式,等到完成的动作达到4个时,才一起发送出去。同时,有错误发生网络错误时,需要重新尝试下载;网络状态变化(网络切换)时,Dispatcher会调整线程池中线程的个数,并且重新提交失败的下载请求。

  • performSubmit(Action): 将请求动作提交到线程执行服务PicassoExecutorService
  • performRetry(BitmapHunter): 重新执行BitmapHunter对象;

BitmapHunter.java

用于加载图片的可执行对象,其首先尝试从缓存中读取图片,如果没有则从外部存储或者网络中进行加载。加载完成后,解码生成一个Bitmap返回给Dispatcher

  • hunt() : 加载图片,优先从cache中读取,若没有,则从网络端下载;完成后,返回一个Bitmap对象;
  • getResult(): 返回加载的图片Bitmap;

PicassoExecutorService.java

继承自ThreadPoolExecutor,采用优先级阻塞队列PriorityBlockingQueue对执行请求BitmapHunter进行优先级排序,优先级高的始终位于队列的前端。另外,根据当前网络的状态来调整线程池的线程数目: 如果是WIFI连接,则线程数设置为4;4G网络下的线程数设置为3;而3G的线程数只有2个。

  • submit(Runnable) : 将可执行对象加入到线程池中,并返回一个FutureTask<?>对象;
  • adjustThreadCount(NetworkInfo) : 根据网络状态调整线程池线程数;