Fresco的源码中,DraweeView的介绍简洁明了:我就是把DraweeHierarchy显示到屏幕上的家伙。那我们来分析一下相关代码,看看它的逻辑是什么样的。
1 时序图
首先可以用以下这个图初步理解SimpleDraweeView在调用了setUri(Uri uri)
之后的流程:
可以将这张图描述为以下信息:
DraweeView直接显示DraweeHierarchy;
DraweeController根据Uri获取数据源
DataSource
,并绑定数据订阅者DataSubscriber
;当
DataSource
可以更新数据时通知DataSubscriber
更新DraweeHierarchy。(在Fresco源码分析(4) - 异步加载数据中分析)
2 基类DraweeView
DraweeView<DH extends DraweeHierarchy>
是Fresco视图中的基类,使用的泛型必须是DraweeHierarchy
,它继承了ImageView
。
DraweeView
内部的函数不多,它们全部都是通过DraweeHolder的相应函数实现的。主要有以下这几个:
void init(Context context)
初始化DraweeHolder
;void setHierarchy(DH hierarchy)
设置图层树,会调用super.setImageDrawable(mDraweeHolder.getTopLevelDrawable())
将图层树显示出来;void setController(@Nullable DraweeController draweeController)
设置DraweeController
,像setHierarchy
一样同样也会显示图层树;Drawable getTopLevelDrawable()
获取通过包装好的图层树(见DraweeHierarchy构建图层);View
中的onAttachedToWindow()
、onDetachedFromWindow()
、onStartTemporaryDetach()
、onFinishTemporaryDetach()
四个回调函数,提供当视图被绑定/解绑到指定布局上时的回调函数,它们会触发DraweeHolder
的onDetach()
或onAttach()
;void onTouchEvent(MotionEvent event)
提供触屏反应。
在使用它之前要注意官方的API注释中有这么一段话:
你只能在将DraweeView仅仅当做ImageView来使用时才调用
setImageXXX
函数。
要记住它:无论什么形式的DraweeView
,目标图片的设置最终都是通过DraweeController
。我们会在下一章中介绍DraweeController
,它是连接DraweeHierarchy
与Image Pipeline
的桥梁。虽然setHierarchy
与setController
都会显示出图层树,但是实际上setHierarchy
在显示图片的时候只是把DraweeView
当做普通的ImageView
使用,要使用Fresco的缓存、加载机制,必须使用DraweeController
。
不过所幸Fresco实现了SimpleDraweeVew
帮我们来处理这些繁琐的过程,它封装了Controller
的使用。
2.1 DraweeHolder
DraweeHolder
充斥在DraweeView
的各个位置,每个DraweeView
的函数都是由它的对应函数执行的。它随着DraweeView
的产生而初始化。在深入了解视图绘制之前,我们有必要了解它是做什么的。
DraweeHolder
是用来维持DraweeHierarchy
和DraweeController
之间的沟通的。通过create( DH hierarchy, Context context)
来创建实例,DraweeHierarchy
通过第一个参数赋值,其中第二个参数用于registerWithContext(Context)
(该函数暂时没有完善好)。
它有几个主要使用的函数:
void setHierarchy(DH hierarchy)
在DraweeView.setHierarchy
中被调用,将DraweeHierarchy传给持有的DraweeController;void setController(DraweeController draweeController)
在DraweeeView.setController
中被调用无条件解绑旧的Controller(如果存在的话),并将旧DraweeController的DraweeHierarchy设置为null,并调用DraweeController.onDetach()
将它变为解除绑定状态;将持有的DraweeHierarchy(如果有的话)赋给新传入的DraweeController并调用DraweeController.onAttach()
让它变为绑定状态。(更换DraweeController确是一个非常耗时的过程,应该尽量避免为视图指定新的DraweeController,参考官方文档)void attachController()
若所属的DraweeView
未绑定DraweeController
,DraweeController
成员变量不为空并且已经设置过图层树之后,调用该成员变量的onAttach
方法;void detachController()
若所属的DraweeView
已绑定DraweeController
,DraweeController
成员变量不为空,调用该成员变量的onDetach
方法;void attachOrDetachController()
当已绑定DraweeController
并且所属的DraweeView
可见时,调用attachController
;否则调用detachController
。
特别地,DraweeHolder
继承了VisibilityCallback
,为DraweeHierarchy.RootDrawable
提供回调:当图层的Visibility属性改变的时候对DraweeController
调用attachOrDetachController
操作,当图层不可见时释放资源。
2.2 GenericDraweeView
GenericDraweeView
就是使用GenericDraweeHierarchy
图层树的视图,它承担了所有的xml属性交互工作。
实际上在图层树上目前Fresco也只实现了一个GenericDraweeHierarchy
,使用泛型是为了后续的开发便利。
它会在初始化的时候调用inflateHierarchy(Context context, AttributeSet attrs)
函数,从xml的中获取如下几个属性(如果存在的话):
fadeDuation
渐隐/渐显动画时间;aspectRatio
图片长宽比例,参考官方文档;`XXXImage
/XXXImageScaleType
各图层要显示的Drawable(除了目标显示图层)及它们的ScaleType
;RoundingParams
中的参数
在它的Measure过程中,会依次判断是否高度、宽度属性中有wrap_content
,会将先判断到的属性更正为实际长度。但是Fresco并不支持使用wrap_content。如果你非要使用,只能在width/height中使用一侧,然后搭配aspectRatio
使用。
在GenericDraweeView获取完xml属性之后,它会通过GenericDraweeHierarchyBuilder.build
创造一个与之对应的GenericDraweeHierarchy作为默认使用的图层树,并调用setHierarchy
方法将其传递给DraweeHolder
并显示出来。
2.3 SimpleDraweeView
在SimpleDraweeView中的函数就更少了,可以明确的说,它就只是将GenericDraweeHierarchy显示到UI界面上的空间而已。它比GenericDraweeView多出来的功能就是它内部提供了最简单的DraweeController实现。它有两个函数比较需要关注:
initialize
函数,这个函数会在Fresco.initialize
中调用,目的就是初始化DraweeControllerBuilder,用于构建DraweeController。setImageURI
这个函数我们会经常调用,因此有必要看看它的实现和普通ImageView的setImageXXX
方法有什么区别:
1 2 3 4 5 6 7 8 | public void setImageURI(Uri uri, @Nullable Object callerContext) { DraweeController controller = mSimpleDraweeControllerBuilder .setCallerContext(callerContext) .setUri(uri) .setOldController(getController()) .build(); setController(controller); } |
setController
会调用DraweeHolder.setController
,将图层树的控制权交给DraweeController,并显示出图层树。3 DraweeController
DraweeController是一个将Fresco中负责数据加载的组件组合起来并将信息反映到DraweeHierarchy的组件。它通过建造者模式初始化,基类AbstractDraweeControllerBuilder
使用了四个泛型:(括号中为PipelineDraweeControllerBuilder
所使用的类型)
BUILDER 建造者类型(AbstractDraweeControllerBuilder)
REQUEST 图像请求类型(ImageRequest)
IMAGE 图像类型(CloseableReference
)INFO 图像信息类型(ImageInfo)
在.build()
中会初始化DraweeController。下面我们会先介绍几个关键概念,然后介绍DraweeController的初始化过程。
3.1 ImageRequest
ImageRequest
存储着Image Pipeline处理被请求图片所需要的有用信息(Uri、是否渐进式图片、是否返回缩略图、缩放、是否自动旋转等)。
ImagePipeline仅仅用来装信息,而且一经初始化后就只能获取内容,无法改变内容(即Immutable)。 初始化ImageRequest只能通过ImageRequest.fromUri(Uri uri)
或ImageRequestBuilder.build()
来实现。
我们来看看它内部存储着一些什么信息:
ImageType
:若为ImageType.SMALL
,则所请求的图片会存储在专用小文件缓存中。具体参考Fresco缓存;SourceUri
:图片源Uri;SourceFile
:图片文件地址(若是本地文件的话);ProgressiveRenderingEnabled
若为true,则这个图片请求会返回质量递进的几次图片信息(渐进式图片);LocalThumbnailPreviewsEnabled
若为true,则这个图片请求会在访问本地图片时先返回一个缩略图;ResizeOptions
缩放尺寸,仅支持JPEG,而且不是每次都需要在ImageRequest中设置缩放尺寸的,具体使用请参照Fresco缩放和旋转图片;AutoRotateEnabled
是否允许图片旋转,具体参照Fresco缩放和旋转图片;RequestPriority
这个请求的优先级:LowestPermittedRequestLevel
最低允许从哪层缓存中取数据,参考最低请求级别;IsDiskCacheEnabled
若为false,则此图片请求不会从文件缓存中获取数据;PostProcessor
在图片请求成功之后对图片的处理操作,参考修改图片。
DraweeController是使用ImageRequest来初始化数据订阅者的。SimpleDraweeView
调用setUri(Uri)
会产生一个默认的ImageRequest
含有指定Uri信息,如果需要修改ImageRequest
其他信息,必须手动创建ImageRequest
,并在PipelineDraweeControllerBuilder
调用.build()
之前使用.setImageRequest
设置它。
3.2 可关闭的引用
Facebook在Java中实现了具有引用计数功能的类:SharedReference<T>
(注意不是Android里的SharedPreference)。它将类型为T
的对象进行包装,为其实现增加引用数、减少引用数、删除引用的功能。
特别地,它会使用专门用于实现释放资源的接口:ResourceReleaser<T>
,它内部只有一个函数:void release(T object)
。
当然,但是直接让程序员直接去操作它很可能会出现问题。所以CloseableReference
出现了,它为任何实现了Closeable
类的对象封装了引用计数、回收引用的功能。
CloseableReference
中有几个主要函数:
CloseableReference<T> of(T object, ResourceReleaser<T> releaser)
用于初始化引用计数(而不是使用构造函数),该函数会新建一个SharedReference
将对象包装起来,引用计数为1。若不传releaser,会使用默认的ResourceReleaser
,调用Closeable.close()
函数回收Closeable
引用;CloseableReference<T> clone()
用于添加对象引用,该函数会新建一个CloseableReference
,同时持有的SharedReference
引用+1, 不会创建SharedReference
对象;T get()
返回引用对象;void close()
减少一个引用计数,若引用计数减为0,则调用releaser的release(T object)
将对象释放。一旦创建了一个CloseableReference,当持有者离开作用域时就必须调用这个函数!(finally函数块是释放资源工作最好的地方)
最好不要直接使用这个工具,如果非用不可的话,你需要谨慎地操作它。使用方法参考Fresco中文文档。
Fresco中定义了CloseableImage
,它会在finalize
的时候调用close()
,有这两个类继承了它:
CloseableStaticBitmap
它内部持有一个CloseableReference<Bitmap>
包装目标Bitamp
,以及关于图像质量、旋转角度的信息。在close()
调用的时候会调用CloseableReference
的close()
函数释放资源,释放的原理是Bitmap.recycle();CloseableAnimatedBitmap
它内部持有一个List<CloseableReference<Bitmap>>
包装起每一帧的Bitmap
,还存有每一帧的时长。在close()
调用的时候会递归释放列表资源。
3.3 数据订阅
Fresco中使用DataSource与DataScriber进行异步数据请求。DataSubscriber具有以下几个函数:
onNewResult
在接收到新的数据;onFailure
数据加载失败;onCancellation
数据请求被取消;onProgressUpdate
加载进度更新。
DataSource在接受到Image Pipeline提供的数据时调用notifyDataSubscribers
使DataSubscriber做出反应。
更多对于数据订阅者的分析见Fresco源码分析(4) - 异步加载数据。
3.4 初始化Draweetroller
在AbstractDraweeControllerBuilder
(DraweeControllerBuilder
的基类)的build()
函数中会调用继承类实现的obtainController()
函数,在默认使用的PipelineDraweeControllerBuilder
中,它会做三件重要的事情:
obtainDataSourceSupplier()
根据Uri获取数据源DataSource
的Supplier;generateUniqueControllerId()
获取独一无二的Controller标识(使用静态递增的AtomicLong
变量记录唯一标识);getCallerContext()
获取调用者的Context
。
之后将上面得到的三个变量赋值给Controller,若是第一次设置DraweeController
,还会相应地初始化这几个组件(若存在的话):
RetryManager
管理失败重试的组件;GestureDetector
传递触屏事件的组件;DeferredReleaser
向主线程消息队列中添加释放资源任务的组件;ControllerListener
设置回调函数,具体参考 3.5 使用ControllerListener;DraweeHierarchy
设置图层树。
在setController
调用后,如果传入的DraweeController
发生了onAttach
,它就会调用subtitRequest()
提交数据请求(如果还没有提交的话),我们来看看这个函数是怎么实现的:
protected void submitRequest() { //一些初始化工作 final String id = mId; final boolean wasImmediate = mDataSource.hasResult(); final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { @Override public void onNewResultImpl(DataSource<T> dataSource) { boolean isFinished = dataSource.isFinished(); float progress = dataSource.getProgress(); T image = dataSource.getResult(); if (image != null) { onNewResultInternal(id, dataSource, image, progress, isFinished, wasImmediate); } else if (isFinished) { onFailureInternal(id, dataSource, new NullPointerException(), /* isFinished */ true); } } @Override public void onFailureImpl(DataSource<T> dataSource) { onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ true); } @Override public void onProgressUpdate(DataSource<T> dataSource) { boolean isFinished = dataSource.isFinished(); float progress = dataSource.getProgress(); onProgressUpdateInternal(id, dataSource, progress, isFinished); } }; mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); }
从这段代码中我们就可以看出初步的逻辑了,它会创建一个DataScriber
并将其绑定到DataSource
上,随后只要DataSource
处理好图片就会为绑定的DataScriber
发布消息。通过在AbstractDraweeController
中定义的onFailureInternal
、onNewResultInternal
、onProgressUpdateInternal
来对DraweeHierarchy做相应的改动,从而控制显示层。
3.5 使用ControllerListener
ControllerListener
提供DraweeController
主要事件的回调功能,主要有这几个事件:
onSubmit
在提交图片请求时调用;onFinalImageSet
最终图片加载完成时调用;onIntermediateImageSet
任何渐进式图片的过渡图片加载完成时调用;onIntermediateImageFailed
过渡图片加载失败时调用;onFailure
最终图片加载失败时调用;onRelease
释放图片资源时调用。
你可以继承它并在DraweeControllerBuilder
中设置,从而实现一些自定义的提醒事件。