[关闭]
@linux1s1s 2016-07-19T03:14:16.000000Z 字数 8803 阅读 2040

Fresco源码分析(3) - DraweeView显示图层树

Fresco 2016-07


转载 作者:Desmond

Fresco的源码中,DraweeView的介绍简洁明了:我就是把DraweeHierarchy显示到屏幕上的家伙。那我们来分析一下相关代码,看看它的逻辑是什么样的。

1 时序图

首先可以用以下这个图初步理解SimpleDraweeView在调用了setUri(Uri uri)之后的流程:

DraweeView

可以将这张图描述为以下信息:

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() 四个回调函数,提供当视图被绑定/解绑到指定布局上时的回调函数,它们会触发DraweeHolderonDetach()onAttach()
- void onTouchEvent(MotionEvent event) 提供触屏反应。

在使用它之前要注意官方的API注释中有这么一段话:

你只能在将DraweeView仅仅当做ImageView来使用时才调用setImageXXX函数。

要记住它:无论什么形式的DraweeView,目标图片的设置最终都是通过DraweeController。我们会在下一章中介绍DraweeController,它是连接DraweeHierarchyImage Pipeline的桥梁。虽然setHierarchysetController都会显示出图层树,但是实际上setHierarchy在显示图片的时候只是把DraweeView当做普通的ImageView使用,要使用Fresco的缓存、加载机制,必须使用DraweeController

不过所幸Fresco实现了SimpleDraweeVew帮我们来处理这些繁琐的过程,它封装了Controller的使用。

2.1 DraweeHolder

DraweeHolder充斥在DraweeView的各个位置,每个DraweeView的函数都是由它的对应函数执行的。它随着DraweeView的产生而初始化。在深入了解视图绘制之前,我们有必要了解它是做什么的。

DraweeHolder是用来维持DraweeHierarchyDraweeController之间的沟通的。通过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未绑定DraweeControllerDraweeController成员变量不为空并且已经设置过图层树之后,调用该成员变量的onAttach方法;
- void detachController() 若所属的DraweeView已绑定DraweeControllerDraweeController成员变量不为空,调用该成员变量的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实现。它有两个函数比较需要关注:

  1. public void setImageURI(Uri uri, @Nullable Object callerContext) {
  2. DraweeController controller = mSimpleDraweeControllerBuilder
  3. .setCallerContext(callerContext)
  4. .setUri(uri)
  5. .setOldController(getController())
  6. .build();
  7. setController(controller);
  8. }

setController会调用DraweeHolder.setController,将图层树的控制权交给DraweeController,并显示出图层树。

3 DraweeController

DraweeController是一个将Fresco中负责数据加载的组件组合起来并将信息反映到DraweeHierarchy的组件。它通过建造者模式初始化,基类AbstractDraweeControllerBuilder使用了四个泛型:(括号中为PipelineDraweeControllerBuilder所使用的类型)

.build()中会初始化DraweeController。下面我们会先介绍几个关键概念,然后介绍DraweeController的初始化过程。

3.1 ImageRequest

ImageRequest存储着Image Pipeline处理被请求图片所需要的有用信息(Uri、是否渐进式图片、是否返回缩略图、缩放、是否自动旋转等)。

ImagePipeline仅仅用来装信息,而且一经初始化后就只能获取内容,无法改变内容(即Immutable)。 初始化ImageRequest只能通过ImageRequest.fromUri(Uri uri)ImageRequestBuilder.build()来实现。

我们来看看它内部存储着一些什么信息:

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中有几个主要函数:

最好不要直接使用这个工具,如果非用不可的话,你需要谨慎地操作它。使用方法参考Fresco中文文档

Fresco中定义了CloseableImage,它会在finalize的时候调用close(),有这两个类继承了它:

3.3 数据订阅

Fresco中使用DataSource与DataScriber进行异步数据请求。DataSubscriber具有以下几个函数:

DataSource在接受到Image Pipeline提供的数据时调用notifyDataSubscribers使DataSubscriber做出反应。

更多对于数据订阅者的分析见Fresco源码分析(4) - 异步加载数据

3.4 初始化Draweetroller

AbstractDraweeControllerBuilderDraweeControllerBuilder的基类)的build()函数中会调用继承类实现的obtainController()函数,在默认使用的PipelineDraweeControllerBuilder中,它会做三件重要的事情:

之后将上面得到的三个变量赋值给Controller,若是第一次设置DraweeController,还会相应地初始化这几个组件(若存在的话):

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中定义的onFailureInternalonNewResultInternalonProgressUpdateInternal来对DraweeHierarchy做相应的改动,从而控制显示层。

3.5 使用ControllerListener

ControllerListener 提供DraweeController主要事件的回调功能,主要有这几个事件:

你可以继承它并在DraweeControllerBuilder中设置,从而实现一些自定义的提醒事件。

4 类图

DraweeView Diagram

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注