前段时间简单的分析了一下ImageLoader的源码,所以就想看看使用很火的一些其他的图片加载库的实现,跟ImageLoader对比起来有什么优缺点。所以本系列的几篇博文会时不时跟ImageLoader来个简单的对比来说明问题。闲言少叙,开始Picasso分析之旅吧。
本篇只是简单的对流程进行梳理,因为Picasso自己分析起来篇幅只多不少,所以本篇就简单的说明。具体的某个知识点的细节会另外开篇博客说明之。
其实既然是缓存,肯定核心也就是那么几点:memory cache,disk cache等等,总体上来说只是不同的库对它们处理的方式不同而已。
当年老毛同志怎么说的来着:”要从战略上藐视对方,从战术上重视对方“。所以为了减少对阅读源码的恐惧感,让我先从战略上藐视一下picasso吧。刚把这玩意的源码下载来一瞅,第一个感觉就是:我滴个乖乖,居然就一个包!神马各种Action、各种RequestHandler统统特么的一股脑的放在了一个包下面,感觉就像一个洁白的床上堆满了臭袜子、毛巾、衣服和拖鞋一样乱糟糟的。就算你这个作者是技术大神,也不能组织类的时候这么不修边幅啊!
好了,战略藐视完毕,还是从战术上来仔细的分析吧!
总的来说Picasso的流程很简单,当Picasso通过load方法获取图片的时候,需要经过如下步骤才能完成显示图片的流程:
1)将请求封装为Request对象,然后将Request对象进一步封装为Action(ImageAction)对象。
2)将Action(ImageAction)对象交给Dispather进行分发
3)最终将action交给BitmapHunter这个Runnable作为在线程池中线程的工作的单元(具体的是讲action持有的当前Reqeuest对象)
4)由RequestHandler来处理当前request,调用其load方法将加载完成后的图片交给PicassoDrawable显示图片。
代码流程如下:Picasso->load->创建request->创建action->Dispatcher分发action->RequestHandler的load方法处理具体的请求->PicassoDrawable显示图片。
上面去掉了许多的枝枝蔓蔓简单的说了一些流程,算是一个总纲吧,下面就围绕着这个总纲来说明Picasso的具体细节。
跟ImageLoaderConfiguration一样,Picasso也是利用了Builder模式来组建Picasso,用Builder模式的好处之一就是可以通过Builder来清晰的知道Picasso可都可以提供哪些对外的配置接口供我们使用,同时我们自己在客户端配置这些组件的话,Builder也可以提供默认的组件来使用。就让我们看看Picasso的Builder都提供了什么组件让客户端自由配置:
public static class Builder {
private final Context context;
private Downloader downloader;//配置自定义的义图片下载类
private ExecutorService service;//配置自定义的线程池
private Cache cache;//配置自定义缓存,特指内存换文
private Listener listener;//配置图片下载监听
private RequestTransformer transformer;//配置自定义的请求转换器
private List requestHandlers;
private Bitmap.Config defaultBitmapConfig;
private boolean indicatorsEnabled;
private boolean loggingEnabled;
先不说这些Builder怎么使用它们,事实上Picasso的with方法来初始化一个Picasso单例对象,事实上就是用的Builder默认的配置来build一个Picasso对象出来:
static volatile Picasso singleton = null;
public static Picasso with(@NonNull Context context) {
。。。
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
//通过Builder默认的配置来创建一个Picasso
singleton = new Builder(context).build();
}
}
}
return singleton;
}
那么这个默认的Builder在build的时候都做了些神马呢,其实不用多想也应该知道了,就是配置了诸如默认的下载器,请求转换器,默认的memory cache等:
public Picasso build() {
Context context = this.context;
if (downloader == null) {//客户端没有配置自己的Downloader
downloader = Utils.createDefaultDownloader(context);
}
if (cache == null) {//客户端没有配置自己的memory cahce
cache = new LruCache(context);//默认是LRU算法的memory cache
}
if (service == null) {//客户端没有配置自己的线程池
service = new PicassoExecutorService();
}
if (transformer == null) {//客户端没有配置自己的转换器
//配置默认的请求转换器,默认是对最初的Request不做转换
transformer = RequestTransformer.IDENTITY;
}
。。。此处暂时省略了重要的代码。。。
}
}
我们知道Picasso中通过with方法其实是用默认的builder配置来初始化Picasso的单例对象。其实在你调用with方法之前(注意是调用with方法之前),还可以调用setSingletonInstance(Picasso)结合Builder配置自己的单例Picasso对象:
static volatile Picasso singleton = null;
public static void setSingletonInstance(@NonNull Picasso picasso) {
......
synchronized (Picasso.class) {
if (singleton != null) {//这个非null判断就意味着setSingletonInstance必须在with方法之前调用
throw new IllegalStateException("Singleton instance already exists.");
}
//初始化单例对象
singleton = picasso;
}
}
这其实也算是单例模式+Builder模式的灵活应用吧!挺值得学习、借鉴!
跟ImageLoader相同,Picasso也提供了对不同 图片来源(比如assets里面的图片,Filie里面的图片,或者Drawable)的加载处理。不同之处就是ImageLoader对外提供了统一的显示方法,然后由Imageloader自己判断来源从而提供不同的流来获取图片,而Picasso提供了多个重载load方法来处理对应的情况:
我们就挑拣其中的一个load(Uri)来进行分析吧。
其实,Picasso的源码分析起来也很简单,层层跟进load方法调用路径就可以了。
在调用load的时候实际上返回的是一个RequestCreator:
//request的builder对象,此处感觉应该命名为requestBuilder好点
private final Request.Builder data;
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;
//通过构造器初始化request.Builder,并把uri传给builder
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}
也就是说在调用load方法的时候实际上并没有对图片资源进行加载,只是简单返回了一个RequestCreator对象,该对象在初始化的时候初始化了Request的Builder对象(好吧,又是Builder模式的应用)。
使用过Picasso的都知道,我们调用RequestCreator的into方法来完成工作的,那么就先简单的分析RequestCreator的into系列重载方法之一进行说明:
public void into(ImageView target, Callback callback) {
long started = System.nanoTime();
//判断是否是主线程
checkMain();
。。。此处有省略代码。。。
//建立一个请求对象
Request request = createRequest(started);
//简历请求的key
String requestKey = createKey(request);
。。。。此处有省略代码。。。
//创建action,实际上以一个ImageViewAction
Action action =
new ImageViewAction(picasso, target, request, memoryPolicy, networkPolicy, errorResId,
errorDrawable, requestKey, tag, callback, noFade);
//提交Action
picasso.enqueueAndSubmit(action);
}
上面的代码为了本篇博文的说明也是去掉了一些枝枝蔓蔓,甚至里面不乏重要的代码,在本篇不做论述,后面博文会讲到。into的主要逻辑就是把request+target封装交给action,然后由Picasso对此action进行提交。
为什么既然有了Request对象,又会需要一个Action呢?其实仔细分析源码可以发现Request侧重点在于请求本身:比如请求图片资源的uri,对请求的图片做什么处理等等一系列请求,主要是对Image做什么样的展示效果处理等。而Action的所含有的属性如下:
final Picasso picasso;//持有上文单利引用
final Request request;//上文提到的request
final WeakReference target;//target可能是ImageView,或者优先理解为ImageView,为弱引用,确保target被回收的时候不受影响
final boolean noFade;
final int memoryPolicy;//缓存策略
final int networkPolicy;//
final int errorResId;
final Drawable errorDrawable;
final String key;
final Object tag;
boolean willReplay;
boolean cancelled;
很显然Action的主要职责就是:对图片进行加载,配置图片的文件缓存和内存缓存策略以及是否重新加载等逻辑。使得责任分明,调理清晰。
Action是一个抽象的泛型类,提供了complete和error两个抽象方法、它的子类又如下几个:
GetAction、FetchAction、ImageViewAction、TargetAction在此处我们提交的是ImageViewAction.
让我们简单的看一下ImageViewAction的complete方法:
@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
}
//获取图片
ImageView target = this.target.get();
if (target == null) {
return;
}
//获取context
Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
//设置图片
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
//加载图片完成后执行的操作,有客户端配置自己的callback
if (callback != null) {
callback.onSuccess();
}
}
可以发现最终是由PicassoDrawable来完成图片的显示,所以继续跟进:
PicasDrawable是BitmapDrawable的子类:
static void setBitmap(ImageView target, Context context, Bitmap bitmap。。) {
Drawable placeholder = target.getDrawable();//现获取drawable
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
//生成一个dreawable对象
PicassoDrawable drawable =
new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
target.setImageDrawable(drawable);//最终图片就这么蹦跶出来了。
}
:
该类用来处理不同来源的图片,是个抽象类,功能类似于Imageloader对不同图片来源返回不同的输入流,在picasso只是用面向对象的方式处理来自网络(NetworkRequestHandler),Resoure资源图片(ResourceRequestHandler),asset文件中的图片(AssetRequestHandler)等等若干个Handler。在初始化Picasso的时候,Picasso默认实现的RequestHandler的上述子类是预加载到一个集合中去的!:
Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {
List<RequestHandler> allRequestHandlers =
new ArrayList<RequestHandler>(builtInHandlers + extraCount);
allRequestHandlers.add(new ResourceRequestHandler(context));
if (extraRequestHandlers != null) {
allRequestHandlers.addAll(extraRequestHandlers);
}
//添加默认RequestHandlers
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);
}
可以发现Picasso自己实现的handler在构造器初始化的时候就把自己实现的RequestHander的子类预先初始化好(姑且称之为Picasso内置的ReqeustHandler对象),当然用户也可以实现自己的RequestHandler通过Picasso.Builder.addRequestHandler来添加进去,当然大多数情况不需要自己提供ReqeuestHandler的实现。这种处理方式或者预先初始化的对象放入集合的方式其实在Gson源码中也有所体现。
那Picasso怎么知道是来自于哪个种类的图片呢?这就是ReqeuestHandler的功能了!RequestHandler时候一个抽象类,从名字上就可以知道它是处理具体请求的,它提供了两个重要的抽象方法来完成对不同种类图片的处理工作:
abstract canHandleRequest(Request data):判断某子类是否有能力处理当前请求
abstract load(Request request, int networkPolicy):在canHandleRequest方法返回true的时候,就用该RequestHandler的实现类的load方法来加载图片!
就让我们先简单分析Picasso怎么处理网络Reqeust的:NetworkRequestHandler
//判断当前请求的图片资源是否来自于服务器或者网络
public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}
//NetWorkReqesutHandler对load的处理
public Result load(Request request, int networkPolicy) throws IOException {
//下载图片资源,返回一个Response对象
Response response = downloader.load(request.uri, request.networkPolicy);
if (response == null) {
return null;
}
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
Bitmap bitmap = response.getBitmap();
if (bitmap != null) {
return new Result(bitmap, loadedFrom);
}
InputStream is = response.getInputStream();
if (is == null) {
return null;
}
。。。此处有省略代码。。。
return new Result(is, loadedFrom);
}
逻辑很简单,就是在判断当前Reqeuest为网络请求的时候调用load方法从网络中下载图片等处理。
那么ReqeuestHandler什么时候才真正起作用呢?沿着Picasso的脉络分析也很容易发现,在Picasso的Dispatcher在分发请求(从代码上来说应该是分发action)的时候会调用performSubmit方法,在此方法里面会来检测当前请求图片的种类来源,同样砍掉一些枝枝蔓蔓核心代码如下:
void performSubmit(Action action, boolean dismissFailed) {
//BitmapHunter获取了请求的key,Hunter是一个runnable
BitmapHunter hunter = hunterMap.get(action.getKey());
//重新低action进行包装
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
//提交hunter
hunter.future = service.submit(hunter);
}
该方法里面调用了forRequest方法,那么这个方法就是我们要找的方法了!!!
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats,
Action action) {
//获取当前请求对象
Request request = action.getRequest();
//获取picasso内置的RequestHandler对象和自定义的RequestHandler对象
List requestHandlers = picasso.getRequestHandlers();
//判断内置对象或者自定义的对象哪一个对象能处理当前请求
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
if (requestHandler.canHandleRequest(request)) {//如果能处理当前请求
//返回一个bitMapHandler
//把当前ReqeuestHandler对象交给BitmapHunter
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
//说明当前请求无效
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}
上面forRequest方法也很简单:主要执行了一下逻辑
1、获取当前请求Reqeuest对象
2、获取Picasso内置的RequestHandler以及自定义的RequestHandler集合。
3、判断集合中哪一个RequestHandler对象可以对当前Reqeuest进行处理(canHanlderReqest返回true)
4、把集合中能处理当前Request的RequestHandler对象交给BitMapHunter。
注意此时Picasso并没有立即调用RequestHandler对象的load方法进行处理,而是继续调用 service.submit(hunter);方法来在线程池中对BitmapHunter这个Runnable进行处理,所以我们不难猜测出来在run方法中必然调用了RequestHandler的load方法!
那么就继续追踪BimapHunter的run方法发现其调用了hunt()方法,那么hunt()方法是由做了什么呢?:
//BitmapHunter的run方法
public void run() {
result = hunt();//获取执行结果
}
//注意该方法返回了一个bitmap
Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//获取读取内存代码省略
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
//此处真是调用了load方法进行处理
RequestHandler.Result result = requestH andler.load(data, networkPolicy);
....此处省略大量代码....
return bitmap;
}
看到了吧!在hunt()方法中正式调用了forReqeuset过滤的ReqesutHandler对象的load方法完成了核心业务功能!
上面简单的介绍了一下Picasso的工作流程,而且故意省略了很多重要的地方因为篇幅需要暂时没做说明,会继续写博客慢慢抽丝拨茧分析,其实阅读源码很枯燥,有好多自己能体会到的地方有时候因为语言组织能力有限,只能自己体会而没办法写出来,Picasso的代码思路也很清晰,建议在业余的时间读读加深体会也是好的,好多东西自己钻研了才会真正的获得属于自己的收获。