Fresco是Facebook提供的一个开源图片加载与管理库。它的功能很强大,可以从网络、本地存储和Android资源文件中加载图片,它完全自己负责图片加载与显示,不需要你为细节去操心。
Fresco含有3级缓存设计(2级内存,1级文件)。
Android2.3及以上的系统都可以使用Fresco。在Android5.0以下的系统上,Fresco将图片放在一个特别的内存中(ashmem heap),而非Java Heap中,而5.0及以上的系统由于对内存的管理比之前的版本优化很多,所以图片缓存直接放在Java Heap中了。
布局中引入:
<com.facebook.drawee.view.SimpleDraweeViewandroid:id&#61;"&#64;&#43;id/my_image_view"android:layout_width&#61;"120dp"android:layout_height&#61;"wrap_content"fresco:viewAspectRatio&#61;"1"fresco:fadeDuration&#61;"300"fresco:actualImageScaleType&#61;"fitCenter"fresco:placeholderImage&#61;"&#64;mipmap/ic_launcher"fresco:failureImage&#61;"&#64;mipmap/ic_launcher"fresco:roundAsCircle&#61;"true"fresco:roundedCornerRadius&#61;"10dp"fresco:roundTopLeft&#61;"true"fresco:roundTopRight&#61;"true"fresco:roundBottomLeft&#61;"true"fresco:roundBottomRight&#61;"true"fresco:roundingBorderWidth&#61;"1dp"fresco:roundingBorderColor&#61;"#00ffff"/>
testIv.setImageURI("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/logo_white_fe6da1ec.png");
继承于 View, 负责图片的显示。一般情况下&#xff0c;使用SimpleDraweeView 即可
ImageRequest存储着Image Pipeline处理被请求图片所需要的有用信息(Uri、是否渐进式图片、是否返回缩略图、缩放、是否自动旋转等)。
有时候我们需要监听图片显示的过程&#xff0c;比如在失败&#xff0c;中间过程&#xff0c;成功时做一些事情。我们可以这么做&#xff1a;
为SimpleDraweeView 指定一个 DraweeController
为DraweeController 指定一个 ControllerListener
在ControllerListener 的回调方法里处理 失败&#xff0c;中间过程&#xff0c;成功时的事情
Uri uri;
DraweeController controller &#61; Fresco.newControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri);
.build();
mSimpleDraweeView.setController(controller);
上面的代码指定了一个 ControllerListener &#xff0c;它包含一些回调方法&#xff1a;
onFinalImageSet 加载完成
onIntermediateImageSet 加载中间过程
onFailure 加载失败
Fresco 支持许多URI格式。见下表&#xff1a;
类型 Scheme 示例
远程图片: http://, https:// HttpURLConnection 或者参考 使用其他网络加载方案
本地文件: file:// FileInputStream
Content provider: content:// ContentResolver
asset目录下的资源: asset:// AssetManager
res目录下的资源: res:// Resources.openRawResource
特别注意&#xff1a;Fresco 不支持 相对路径的URI. 所有的URI都必须是绝对路径&#xff0c;并且带上该URI的scheme。
Fresco 是一个典型的 MVC 模型&#xff0c;只不过把 Model 叫做 DraweeHierarchy。
M : DraweeHierarchy
V : DraweeView
C : DraweeController
DraweeView
–| GenericDraweeView
——| SimpleDraweeView
DraweeView (Viewer)
获取和设置Hierarchy&#43;Controller&#xff0c;DraweeView的相关信息在DraweeHolder中
DraweeHolder是一个辅助的类&#xff0c;解耦的设计方式&#xff0c;将需要设置以及传递控制的信息&#xff0c;全部交给DrawHolder来实现
/** This method is idempotent so it only has effect the first time it&#39;s called */private void init(Context context) {if (mInitialised) {return;}mInitialised &#61; true;mDraweeHolder &#61; DraweeHolder.create(null, context);if (Build.VERSION.SDK_INT >&#61; Build.VERSION_CODES.LOLLIPOP) {ColorStateList imageTintList &#61; getImageTintList();if (imageTintList &#61;&#61; null) {return;}setColorFilter(imageTintList.getDefaultColor());}}
再查看剩余的DraweeView的程序&#xff0c;发现其均将只是将相关事件传递给DraweeHolder&#xff0c;这是一种解耦的设计方式&#xff0c;以后就是不采用DraweeView&#xff0c;采用其他的方式&#xff0c;照样可以使用这套逻辑
GenericDraweeView
解析在xml中设置的属性
protected void inflateHierarchy(Context context, &#64;Nullable AttributeSet attrs) {GenericDraweeHierarchyBuilder builder &#61;GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);setAspectRatio(builder.getDesiredAspectRatio());setHierarchy(builder.build());}
具体看下inflateHierarchy方法&#xff0c;这个方法中主要做的是解析并设置xml属性&#xff0c;这里采用的是建造者模式GenericDraweeHierarchyBuilder。
SimpleDraweeView
从外界设置ConrolllerBuilderSupplier
可以设置ImageUri
核心的业务逻辑位于DraweeView中
在控件初始化时&#xff0c;初始化了一个DraweeHolder
我们看下它的setImageURI方法的实现&#xff1a;
public void setImageURI(Uri uri, &#64;Nullable Object callerContext) {DraweeController controller &#61; mSimpleDraweeControllerBuilder.setCallerContext(callerContext).setUri(uri).setOldController(getController()).build();setController(controller);}
DraweeController
–| AbstractDraweeController
—-| PipelineDraweeController
DraweeController:
获取和设置Hieraychy
view的各种事件通知过来&#xff0c;controller来控制这些逻辑的操作&#xff08;onAttach/onDetach/onTouchEvent/getAnimatable&#xff09;
AbstractDraweeController
最关键的功能&#xff1a; 实现了客户端向服务端的提交请求&#xff0c;即向DataSource中注册观察者&#xff0c;在有结果返回的时候&#xff0c;在主线程通知客户端更新即可&#xff0c;即设置Hierarychy的drawable即可
参照之前的分析方式&#xff0c;仍然采用先构造&#xff0c;然后具体方法的顺序
2.1 构造方法&#xff0c;设置了UI线程池&#xff0c;重试&#xff0c;以及手势相关的信息
public AbstractDraweeController(DeferredReleaser deferredReleaser,Executor uiThreadImmediateExecutor,String id,Object callerContext) {mDeferredReleaser &#61; deferredReleaser;mUiThreadImmediateExecutor &#61; uiThreadImmediateExecutor;init(id, callerContext, true);}
private void init(String id, Object callerContext, boolean justConstructed) {mEventTracker.recordEvent(Event.ON_INIT_CONTROLLER);// cancel deferred releaseif (!justConstructed && mDeferredReleaser !&#61; null) {mDeferredReleaser.cancelDeferredRelease(this);}// reinitialize mutable state (fetch state)mIsAttached &#61; false;mIsVisibleInViewportHint &#61; false;releaseFetch();mRetainImageOnFailure &#61; false;// reinitialize optional componentsif (mRetryManager !&#61; null) {mRetryManager.init();}if (mGestureDetector !&#61; null) {mGestureDetector.init();mGestureDetector.setClickListener(this);}if (mControllerListener instanceof InternalForwardingListener) {((InternalForwardingListener) mControllerListener).clearListeners();} else {mControllerListener &#61; null;}mControllerViewportVisibilityListener &#61; null;// clear hierarchy and controller overlayif (mSettableDraweeHierarchy !&#61; null) {mSettableDraweeHierarchy.reset();mSettableDraweeHierarchy.setControllerOverlay(null);mSettableDraweeHierarchy &#61; null;}mControllerOverlay &#61; null;// reinitialize constant stateif (FLog.isLoggable(FLog.VERBOSE)) {FLog.v(TAG, "controller %x %s -> %s: initialize", System.identityHashCode(this), mId, id);}mId &#61; id;mCallerContext &#61; callerContext;}
2.2 具体方法&#xff0c;在这里做分析时&#xff0c;我们重点关注图片如何获取&#xff0c;因而我们关注的核心方法是onAttach()&#xff0c;在这里实现了图片请求的机制&#xff0c;以及图片获取到如何回调&#xff0c;如何显示到UI层的控制&#xff0c;在下面的程序中&#xff0c;看到核心的设置的方法是submitRequest()
&#64;Overridepublic void onAttach() {if (FLog.isLoggable(FLog.VERBOSE)) {FLog.v(TAG,"controller %x %s: onAttach: %s",System.identityHashCode(this),mId,mIsRequestSubmitted ? "request already submitted" : "request needs submit");}mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);Preconditions.checkNotNull(mSettableDraweeHierarchy);mDeferredReleaser.cancelDeferredRelease(this);mIsAttached &#61; true;if (!mIsRequestSubmitted) {submitRequest();}}
此处以第一次请求为例&#xff0c;这样分析比较简单&#xff0c;查看下面的方法&#xff0c;在请求时&#xff0c;设置请求的进度为0&#xff0c;获取到数据源(DataSource)&#xff0c;然后给数据源注册观察者(DataSubscriber)&#xff0c;先查看下面的SubmitRequest方法
protected void submitRequest() {final T closeableImage &#61; getCachedImage();if (closeableImage !&#61; null) {mDataSource &#61; null;mIsRequestSubmitted &#61; true;mHasFetchFailed &#61; false;mEventTracker.recordEvent(Event.ON_SUBMIT_CACHE_HIT);getControllerListener().onSubmit(mId, mCallerContext);onNewResultInternal(mId, mDataSource, closeableImage, 1.0f, true, true);return;}mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT);getControllerListener().onSubmit(mId, mCallerContext);mSettableDraweeHierarchy.setProgress(0, true);mIsRequestSubmitted &#61; true;mHasFetchFailed &#61; false;mDataSource &#61; getDataSource();if (FLog.isLoggable(FLog.VERBOSE)) {FLog.v(TAG,"controller %x %s: submitRequest: dataSource: %x",System.identityHashCode(this),mId,System.identityHashCode(mDataSource));}final String id &#61; mId;final boolean wasImmediate &#61; mDataSource.hasResult();final DataSubscriber
到了这里&#xff0c;一次请求已经完成了&#xff0c;请求的结果会在回调中执行&#xff0c;但是请求是如何生成的呢&#xff1f;我们并没有看到具体发送请求的逻辑&#xff0c;这个疑问我们先记录下来&#xff08;暂且标记为Q1&#xff09;。先来看看对于请求结果是如何处理的&#xff0c;以新的一次请求结果为例&#xff0c;onNewResultImpl&#xff08;&#xff09;方法&#xff0c;而onNewResultImpl方法&#xff0c;以image不为空为例&#xff0c;最终会调用AbstractDraweeController.onNewResultInternal()方法。下面我们来看看&#xff0c;是如何处理这次新的请求的结果。
1.判断是否是想要的数据源&#xff0c;即查看数据信息是否是当前请求的信息&#xff0c;如果不是&#xff0c;直接释放了资源
2.如果是想要的数据源&#xff0c;创建对应的drawable&#xff0c;设置当前显示的drawable&#xff0c;释放之前缓存的drawable对象和Image对象
private void onNewResultInternal(String id,DataSource
好了&#xff0c;就是获取到图像后续的操作&#xff0c;这个其实就是我们UI的操作&#xff0c;分析到此即可&#xff0c;其他的情况&#xff0c;我们参照这个分析的方式分析即可。下面我们来解决一下之前的Q1问题,数据源的请求是如何发送出去的&#xff0c;这个问题就比较复杂了&#xff0c;我们需要通过至少四篇的博客来分析这个请求的过程。
PipelineDraweeController
(以PipelineDraweeController为例)在通过builder.build()创建Controller的过程中,会调用obtainDataSourceSupplier来获取所需的DataSourceSupplier,进而在请求图片(submitRequest)的过程中获取当前的DataSource. 而通过在DataSource中订阅DataSourceSubscriber,使得请求到的数据在改变时能够通过controller将获取到的图片或者中间结果传递到DraweeHierarchy中,最终显示出来(…写得有点绕…x_x),对这个过程有个大概的了解有助于于之后DataSource模块进行连接。
PipelineDraweeController: Fresco默认的实现,也就是SimpleDraweeView中使用的, 用来桥接image pipeline和 SettableDraweeHierarchy
DraweeHierachy
用于获取顶层的drawable
先来看看DraweeHierachy的源码&#xff0c;发现其为接口&#xff0c;并且只有一个方法&#xff0c;就是用于获取顶层的Drawable
public interface DraweeHierarchy {/*** Returns the top level drawable in the corresponding hierarchy. Hierarchy should always have* the same instance of its top level drawable.* &#64;return top level drawable*/Drawable getTopLevelDrawable();
}
SettableDraweeHierachy&#xff1a;
图像可以被重置
图像可以设置进度
设置失败
设置重试
设置controllerOverlay
在理解获取顶层的Drawable时&#xff0c;需要首先理解Drawable的继承结构
public interface SettableDraweeHierarchy extends DraweeHierarchy {public void reset();public void setImage(Drawable drawable, float progress, boolean immediate);public void setProgress(float progress, boolean immediate);public void setFailure(Throwable throwable);public void setRetry(Throwable throwable);public void setControllerOverlay(Drawable drawable);
}
ImagePipelineFactory\ImagePipelineConfig
ImagePipelineConfig这里使用了建造者模式&#xff0c;为Fresco提供非常多的可配置选项。
这其中配置了比较核心的几项
mBitmapMemoryCacheParamsSupplier 内存缓存数据的策略
mCacheKeyFactory 缓存键值对的获取
mExecutorSupplier 获取本地读写线程池&#xff0c;网络数据线程池&#xff0c;解码线程池&#xff0c;以及后台线程池
mImageDecoder 解码器
网络数据获取器
......
ImagePipeLineConfig是一个比较核心的类&#xff0c;通过这个&#xff0c;我们可以得知&#xff0c;Freco初始化时&#xff0c;配置了大量的策略&#xff0c;可配置项很多&#xff0c;也就让我们的使用更加灵活和易于拓展
以上我们分析了一遍&#xff0c;但是并未提到网络请求的位置的方式&#xff0c;具体的网络请求是在哪里呢&#xff1f;
我们回去看AbstractDraweeController.submitRequest()方法&#xff0c;当时我们并未说明getDataSource()方法是如何实现的。
&#64;Overrideprotected DataSource
那么,这里就要考虑Supplier是从哪里初始化的,然后做了怎样的操作,查看当前的mDataSourceSupplier赋值的地方,得知,初始化的地方有两种方式,一种是构造,一种是直接的initialize,initialize的方法注释中已经告诉我们,这个是controller在detach的时候调用的,所以我们只需要查看构造即可
PipelineDraweeController的构造方法&#xff1a;
public PipelineDraweeController(Resources resources,DeferredReleaser deferredReleaser,AnimatedDrawableFactory animatedDrawableFactory,Executor uiThreadExecutor,MemoryCache
private void init(Supplier
那么现在需要考虑的就是构造方法在哪里进行的调用,Alt&#43;F7快捷键查看调用的地方,发现只有一处地方,就是我们的PipelineDraweeControllerFactory.newController()的方法,而这个方法我们之前已经分析过了
,现在稍微总结一下:
就是说每次在生成请求的时候,即AbstractDraweeController在submitRequest的时候,都会去向Supplier中获取到数据源DataSource,这个数据源是在AbstractDraweeControllerBuilder.getDataSourceSupplierForRequest()中的get方法中获取到的数据源,所以已经将服务端的supplier的生成和客户端的supplier的使用已经结合了起来.
但是还是没看到请求是怎么发送出去的,然后请求回来的数据是如何更新UI的.
在前面我们已经分析了请求回来的数据是如何更新的,这个是通过给DataSource订阅观察者,然后去更新UI数据,这个具体的细节,我们之后再分析,这个已经超出了我们要分析的如何发送请求的范围
在AbstractDraweeControllerBuider的buildController方法&#xff1a;
/** Builds a regular controller. */protected AbstractDraweeController buildController() {AbstractDraweeController controller &#61; obtainController();controller.setRetainImageOnFailure(getRetainImageOnFailure());controller.setContentDescription(getContentDescription());controller.setControllerViewportVisibilityListener(getControllerViewportVisibilityListener());maybeBuildAndSetRetryManager(controller);maybeAttachListeners(controller);return controller;}
obtainController的具体实现在PipelineDraweeControllerBuilder中&#xff1a;
protected PipelineDraweeController obtainController() {DraweeController oldController &#61; getOldController();PipelineDraweeController controller;if (oldController instanceof PipelineDraweeController) {controller &#61; (PipelineDraweeController) oldController;controller.initialize(obtainDataSourceSupplier(),generateUniqueControllerId(),getCacheKey(),getCallerContext());} else {controller &#61; mPipelineDraweeControllerFactory.newController(obtainDataSourceSupplier(),generateUniqueControllerId(),getCacheKey(),getCallerContext());}return controller;}
其中调用到了AbstractDraweeControllerBuilder的obtainDataSourceSupplier方法&#xff1a;
/** Gets the top-level data source supplier to be used by a controller. */protected Supplier
具体这个请求是怎么发送出去的呢?之前有讲到在DraweeController的onAttach中获取了dataSource并且订阅了观察者,用于处理datasource返回的结果.来看一下实现中这个dataSource到底是怎么获取的。
我们再看一下getDataSourceSupplierForRequest方法&#xff1a;
/** Creates a data source supplier for the given image request. */protected Supplier
这里调用到了getDataSourceForRequest方法&#xff0c;该方法在PipelineDraweeControllerBuilder中
&#64;Overrideprotected DataSource
这里主要是通过调用了mImagePipeline.fetchDecodedImage方法&#xff0c;可见是通过Pipeline来获取的这个datasource.其内部是发起了一个submitFetchRequest返回一个DataSource.Pipeline也是Fresco一个重要的组成部分。下面会单独说&#xff1a;
简单来说pipeline就是实现了三级缓存,解码,变形等等,完成了提供可呈现图片的所有工作.Facebook官方中已经说明,ImagePipeline负责完成加载图像,并且将结果反馈(以回调或者说观察者的方式)出来.
这里先解释几个概念
Producer: 为了实现业务的隔离而设计的接口; 将pipeline中需要进行的每一项任务作为一个producer,通常将前一个producer作为参数传递个下一个producer,从而实现面向接口的业务模块剪的隔离.
Consumer: producer生产的结果会最终传递到consumer中,再通过实现了DataSource接口的适配器通知外部
public DataSource
我们去具体的看mProducerSequenceFactory中的代码&#xff1a;
The returned DataSource must be closed once the client has finished with it.* &#64;param imageRequest the request to submit* &#64;return a DataSource representing the pending decoded image(s)*//*** Submits a request for execution and returns a DataSource representing the pending decoded image(s).*
public DataSource
}//******************************
//1.第一步 getDecodedImageProducerSequence
//******************************
//返回一个用于请求 解码图片的队列,可见这个队列是和imageRequest相关的,源码中,imageRequest是一个不可改变的JavaBean,其中包含了所有Pipeline请求图片所需要的所有信息.
public Producer
}//从这里就可以清楚地看到,针对不同的uri类型生成了不同的FetchSequence也就是Producer
private Producer
}
至此我们终于看到具体的网络请求和三级缓存的位置了。
采用了大量的设计模式
采用了生产者消费者模式
面向接口编程的思想非常浓
MVC设计思想
在该代码阅读过程中&#xff0c;我参考了很多资料&#xff0c;最终才对这个流程稍微的有点认识&#xff0c;这个开源项目代码完整度非常高&#xff0c;必须站在一个比较高的设计思考上去思考才能理解得更好一些&#xff0c;我的目的也仅仅是对主流程实现的理解&#xff0c;其他很多的细节点并未去阅读&#xff0c;有赖于未来需要时再去进一步的学习。
Fresco简介
http://blog.v5.cn/2015/10/30/fresco%E7%AE%80%E4%BB%8B/
Fresco用法总结基础篇
http://www.lai18.com/content/9608860.html
Fresco之强大之余的痛楚
http://www.jianshu.com/p/5364957dcf49
Fresco源码解析 - Hierarchy / View / Controller
http://blog.csdn.net/feelang/article/details/45126421
Fresco 源码分析(一) DraweeView-DraweeHierarchy-DraweeController(MVC) DraweeView的分析
http://www.cnblogs.com/pandapan/p/4634563.html
Fresco的一点研究
http://frankls.cn/2015/12/02/study-of-fresco