项目地址 : 统一图片加载框架:一套API,两个加载库html
我发现,市面上最主流的加载框架大概只有这Fresco,Glide,Picasso,而Glide又脱胎于Picasso,他们的API结构是很相似的,只要可以兼容这Fresco和Glide这两个库,基本就能够造成一个统一的图片加载框架。git
可是实际上,在构造统一的图片加载框架的时候,真正难题在于Fresco,由于它的侵入性太强了,它要求咱们使用它定义的图片容器,所以,如何使Fresco非侵入的接入到咱们的开发项目中,这成了一个比较困难的问题,以前发过一篇文章非侵入式的使用Fresco,提出了不少方案,可是整体而言没有一个特别完美的,可是后来研究源码以及Fresco自定义View发现了一种更简单更完美的非侵入式加载的方案。在这里,为了方便你们理解,我先从Fresco的结构提及。github
相信你们都看过这张图,这是Fresco的数据处理模块Image Pipline的结构图,若是省去其内部的处理逻辑,咱们能够简单理解成下面这种结构图缓存
通常咱们并无在项目中直接Image Pipline,而是使用DraweeView或者SimpleDraweeView来加载图片,那么,当咱们使用DraweeView来加载图片的时候,整个加载过程是怎样的呢?bash
上图是咱们常常说起Fresco的MVC的结构,也是Fresco中比较完整的图片加载的过程:DraweeView(V层)发送请求给DraweeController(C层),C层经过与Image pipeline交互获取到数据,而后把数据更新到M层,M层再更新数据展现再V层。这基本上就是Fresco内部基本的加载流程了。框架
咱们去看V层的DraweeView的源码时,发现内部十分简单ide
public class DraweeView<DH extends DraweeHierarchy> extends ImageView {
private final AspectRatioMeasure.Spec mMeasureSpec = new AspectRatioMeasure.Spec();
private float mAspectRatio = 0;
private DraweeHolder<DH> mDraweeHolder;
private boolean mInitialised = false;
...
...
}
复制代码
从它的内部属性,能够看到,除了控制图片宽高比例的属性外,只剩下一个mDraweeHolder,看起全部的图片加载请求都是要经过它的,那么这个DraweeHolder里面到底有什么呢?ui
public class DraweeHolder<DH extends DraweeHierarchy>
implements VisibilityCallback {
private boolean mIsControllerAttached = false;
private boolean mIsHolderAttached = false;
private boolean mIsVisible = true;
// M层的控制类
private DH mHierarchy;
// C层的控制类,负责与数据处理模块作交互
private DraweeController mController = null;
private final DraweeEventTracker mEventTracker = DraweeEventTracker.newInstance();
...
...
...
}
复制代码
从源码中能够发现,其实DraweeHolder内部就是持有了DraweeHierarchy和DraweeController这两个类的引用,至关于包裹了M层和C层。this
能够说,DraweeHolder负责了Fresco全部的核心操做的调度。而DraweeView只是做为图片容器,只是承担一些生命周期之类的信号传递,真正的图片加载的工做都是由它内部的这个DraweeHolder来实现的。编码
根据上面的一系列的观察,咱们能够思考这样一个问题:DraweeView继承自ImageView,Fresco内部主要的复杂的工做都是由DraweeHolder负责,那么咱们是否是能够这么理解:ImageView和DraweeView之间,只差了一个DraweeHolder
因而,咱们就会考虑尝试在外部构造这个DraweeHolder,而后在合适的时机,把数据给ImageView来展现?这样不就至关于把ImageView“变成”DraweeView了么?这样就能相对完美的实现一个非侵入式的图片加载方案。
根据fresco的自定义View(中文文档)的内容,咱们发现,构造DraweeHolder大体上有以下要求:
也就是说,只要有能力实现上面几种要求,咱们就能够在本身构造一个DraweeHolder,实现Fresco的内部调度。
上面提到的一些条件咱们也并非所有都要实现,具体那些是重要的,咱们能够作一些分析。
基本上,咱们在构建DraweeHolder的几条要求中,基本上只有最后一条是须要须要咱们重视的,就是处理 attach/detach 事件。(固然,若是我说错了,欢迎指出个人问题)
上面的一整套的分析下来,真正的编码实现反而很简单了。咱们只须要模仿DraweeView的加载流程,去构造咱们ImageView的加载流程,
关键代码以下:
{
...
...
DraweeController controller;
if (draweeHolder == null) {
draweeHolder=DraweeHolder.create(hierarchy,options.getViewContainer().getContext());
controller=controllerBuilder.build();
}else {
controller= controllerBuilder.setOldController(draweeHolder.getController()).build();
}
// 请求
draweeHolder.setController(controller);
ViewStatesListener mStatesListener=new ViewStatesListener(draweeHolder);
// 外部传入的须要加载图片的ImageView
imageView.addOnAttachStateChangeListener(mStatesListener);
// 判断是否ImageView已经 attachToWindow
if (ViewCompat.isAttachedToWindow(imageView)) {
draweeHolder.onAttach();
}
// 保证每个ImageView中只存在一个draweeHolder
imageView.setTag(R.id.fresco_drawee,draweeHolder);
// 设置好Drawable,准备拿到图片数据
imageView.setImageDrawable(draweeHolder.getTopLevelDrawable());
}
public class ViewStatesListener implements View.OnAttachStateChangeListener{
private DraweeHolder holder;
public ViewStatesListener(DraweeHolder holder){
this.holder=holder;
}
@Override
public void onViewAttachedToWindow(View v) {
this.holder.onAttach();
}
@Override
public void onViewDetachedFromWindow(View v) {
this.holder.onDetach();
}
}
复制代码
还存在的瑕疵:观察DraweeView的源码,我发现它还在onStartTemporaryDetach,onFinishTemporaryDetach这两个方法,我查阅了一下,是ListView中的View在滑动过程当中,被缓存的时候调用的生命周期方法。我找了好久,好像也没有能够监听这个的方法,不过由于咱们如今使用的更多的应该是RecycleView而不是ListView,并且内存没有被主动释放,可是到了阈值,内存仍是会被释放的。这不构成大问题。
根据这个方案,我从新整理了统一图片加载框架。
项目地址 : 统一图片加载框架:一套API,两个加载库
我很早就写了这个统一的图片加载框架,目前涵盖了Glide和Fresco,只须要引入相关的依赖,就能够依托上层的加载框架来调用Glide或者Fresco这种底层库,这种方式除了高度集中的使用了图片库以外,能够实现两种图片库之间几乎无代价的切换。
一套API,两种加载库。
这套非侵入式的方案其实在去年年底的时候作内部技术分享的时候就分享了,不事后来一直比较忙,而后我又很能拖延,也就一直没有整理成文章,致使如今已通过去了好几个月了,感受再不写之后就烂在草稿箱里了,最后就一气呵成把它整理出来了。
统一图片加载框架的想法起源于去年咱们的项目由于一些需求必须引入Fresco图片库,可是咱们的项目一直使用的是Glide,所以,在一段时间内,咱们的项目同时引用了两个图片加载库(捂脸)
所以我考虑慢慢把整个项目从Glide无缝过渡到Fresco中,这样就能够撤掉一个图片库了,所以作了一个统一图片加载框架,磨平两个库的差别性,保证一套API在两个底层库中能有一样的效果。
整个过程针对Fresco的非侵入式的方案想了不少种,目前这是最简单,并且相对完美的方案了。