您的位置:首页 > 其它

Fresco正传(2):DraweeView分析

2015-11-12 18:15 375 查看

正文

既然要分析
DraweeView
,那么就先看一下
DraweeView
的主要集成体系,然后在详细分析一下都做了些什么事情。

在看潘永强博客的时候,其中他提到:源码的分析分为广度和深度,先广度再深度,但是都要适当,避免陷入无止境的广度和深度的细节中去。

从这么多篇博文来看,源码的分析从上至下、先父类后子类、先构造再细节的分析思路是非常棒的。但是,每个人分析习惯不一样,就我来说更喜欢先从更易接触的类再深入父类、先看类注释再看详细的方法。后续的博客也会基本按照此种思路。

SimpleDraweeView
是官方文档中,我们最先接触类,也是最易使用的类。那么就从这个类入手,一步一步分析。先上一下继承体系图,虽不是从上至下的分析,但是也需要充分了解体系结构。



好的开发框架,在命名方面往往做的非常好,起到见名知意作用。那么,我们就从自己的开发经验推测一下每个类都做了什么。

SimpleDraweeView
是简单控件的意思,既然是简单,就会提供出简单明了、便易使用的方法,我想
setImageURI(Uri uri)
就是这样一个方法。

GenericDraweeView
是通用控件的意思,通过上一篇文章知道,
DraweeView
体系中,持有了
DraweeHierarchy
DraweeController
的引用,那么默认的东西,会不会是在这个类中提供的?。

DraweeView
是什么意思呢?,只从命名来看推测不出来什么。

SimpleDrawee



要分析一个类,就先看他的类注释。

/**
* This view takes a uri as input and internally builds and sets a controller.
* This class must be statically initialized in order to be used.
* If you are using the Fresco * image pipeline, use Fresco#initialize to do this.
*/


这个控件接收URI作为输入,并在内部建立和设置一个控制器。

这个类必须被静态初始化才能够被使用,如果你想使用Fresco默认的图片管道来加载图片,请使用Fresco的initlialize去做这件事。

相关注释一定会体现为代码表示,我们很轻松就能够找到做第一件事情的代码:

/**
* Displays an image given by the uri.
*/
public void setImageURI(Uri uri, @Nullable Object callerContext) {
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}


通过uri展示一张图片。在其内部,通过
mSimpleDraweeControllerBuilder
建造了一个
DraweeController
对象,并通过
setController()
方法,将控制器设置给了顶层的
DraweeView
类,当前从目前来看是这个样子的。而一般以
Builder
结尾的都是使用了建造者模式的类。

这是一个学习设计模式的博客

mSimpleDraweeControllerBuilder
是一个成员变量,搜索一下它在哪里被赋值的。

来到了
init()
方法,此方法被各个构造函数所调用。

private void init() {
if (isInEditMode()) {
return;
}
Preconditions.checkNotNull(sDraweeControllerBuilderSupplier, "SimpleDraweeView was not initialized!");
mSimpleDraweeControllerBuilder = sDraweeControllerBuilderSupplier.get();
}


虽然,代码不多,还是值得解释一下。
isInEditMode()
是判断当前是否是预览模式或者是编辑模式,通常在AndrdioStudio中预览布局时,会碰到自定义控件显示不出来的情况,加上这句话就可以了。

Preconditions.checkNotNull()
是Fresco的工具类,从命名来看,就是检查相关引用是否不为空,为空则直接报错.

下面一句则是,从
Supplier
(提供者、供应商)中获取一个
SimpleDraweeContoller
的建造器。特别说明的是,
Supplier
虽然不是某种设计模式之一,但是在Fresco被广泛使用,它可是提供单个对象类型的类,在语义上它可能是一个工厂、建造、或其他任意的东西。

sDraweeControllerBuilderSupplier
是一个静态成员变量,它的初始化就联系到了本类所做的第二件事,使用
Fresco.initialize(Context)
来初始化本类。

既然需要适当在广度上扩展,就接着看一下
Fresco
类。根据类的注释,可以知道:

这个类是Fresco的切入点。

在使用这个类之前,你必须初始化此类。而一种简单初始化的方式就是调用
Fresco.initialize(Context)


既然有简单的,那么就有非简单的,一共有两种方式:

/** Initializes Fresco with the default config. */
public static void initialize(Context context) {
ImagePipelineFactory.initialize(context);
initializeDrawee(context);
}

/** Initializes Fresco with the specified config. */
public static void initialize(Context context, ImagePipelineConfig imagePipelineConfig) {
ImagePipelineFactory.initialize(imagePipelineConfig);
initializeDrawee(context);
}


其中提到了默认的配置和指定的配置,这里不做过多的深入,对于指定的配置(也就两个参数的方法),你只需要知道在
ImagePipelineConfig
中有着大量的参数配置,以保证Fresco框架的可配置化,由于内部参数过多使用了建造者模式来解耦代码。

见名知意,
ImagePipelineFactory.initialize(context)
就是对配置初始化,这里就先略过了。

这样就引申到了
initializeDrawee(Context context)
方法:

private static void initializeDrawee(Context context) {
sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context);
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}


这段代码非常重要,它决定了Fresco使用的默认控制器是名为:
PipelineDraweeController
的类,先简略看一下类注释,该Controller是图片加载器与Model的桥梁,也就是说,如何使用
ImagePipeline
加载图片,并交给Model都是在这个类中控制的。当然,这也符合MVC模式的设计。

Drawee controller that bridges the image pipeline with {@link SettableDraweeHierarchy}.


接着就调用到了
SimpleDraweeView
静态初始化的方法。这样,从
Fresco
初始化到
SimpleDraweeView
的初始化至调用
setImageUri()
显示一张图片的流程就基本完整了。

给出一个示意图,这个图既非UML图、也非流程图,仅仅表达一些代码过程,个人觉得使用图形更形象一些:



GenericDraweeView



从类注释入手可以了解,本类的功能主要是通过解析XML的属性来构建出一个通用的
Hierarchy
并进行设置。此外,还提供了一个设置控件宽高比的方法。

本类比较简单,核心的方法在于
inflateHierarchy(Context context, @Nullable AttributeSet attrs)


方法只做的三件事:

1. 解析XML属性

2. 使用建造者模式构建
GenericDraweeHierarchy


3. 将
Hierarchy
设置给
DraweeView
体系

private void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
Resources resources = context.getResources();

// 解析XML属性
if (attrs != null) {
}

// 使用建造者模式,构建Hierarchy
GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(resources);
// ... ...

// 为DraweeView体系设置hierarchy
setHierarchy(builder.build());
}


到此,值得注意的是。与
DraweeView
有关的
DraweeHierarchy
DraweeController
均已出现,
DraweeHierarchy
对应的默认实现类是:
GenericDraweeHierarchy
DraweeController
对应的默认实现类是:
PipelineDraweeController


此外,本类做的第二件事,请参考博文:控制宽高比

DraweeView

先上一张图,更简洁的表达目的和想做的事。



还是先从类的注释入手吧。从注释中可以了解到如下几件事:

该控件用于展示从
DraweeHierarchy
获取的图像。

使用此控件之前,应该先为其设置
DraweeHierarchy
。由于创建一个
DraweeHierarchy
是一个昂贵的操作,推荐在创建的时候只做一次。

为了展示一张图片,
DraweeController
必须被设置。

Note:虽然这个控件是ImageView的直接子类,但是不支持ImageView的众多方法。

在这个类中,即将接触到一个其背后的男人
DraweeHolder
类,可以说
DraweeView
类中所做的所有操作,都是直接或者间接和
DraweeHolder
有关系的。
DraweeView
从其体系中获取到的
DraweeHierarchy
DraweeController
的信息都被其转交到了
DraweeHolder
类中。其实,这个就是一个解耦操作。假设,你想要自定一个控件,只想使用
DraweeHierarchy
DraweeController
的功能,难道还自己持有这两个类的引用? Just So So。
DraweeHolder
帮你省去了很多麻烦。

先看一下
DraweeHolder
是如何被创建的。

private void init(Context context) {
...
mDraweeHolder = DraweeHolder.create(null, context);
...
}


看样子平平无偿,浏览该类的其他方法你会发现,大部分的操作都是将信息转交给
DraweeHolder
的操作。但是,请不要忽略以下两个方法:

public void setController(@Nullable DraweeController draweeController) {
mDraweeHolder.setController(draweeController);
// 重点
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}
public void setHierarchy(DH hierarchy) {
mDraweeHolder.setHierarchy(hierarchy);
// 重点
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}


注意到了两个重点没?
mDraweeHolder.getTopLevelDrawable()
获取到的就是
DraweeHierarchy.getTopLevelDraweeable()


从之前的分析可以得知,
setHierarchy()
是先被调用而后才是
setController()


通过
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable())
这句话,就将Fresco的图片显示与ImageView结合了起来,这也是图片能够显示的原因。

如果你自定义控件了,千万不要忘记模仿这两个方法加上同样的语句哦。



既然,
DraweeView
已经将职责都转交给了
DraweeHolder
类,那么就来看看
DraweeHolder
类做了些什么。

还是先从类注释入手:

这个类持有了
DraweeController
DraweeHierarchy


这个类便于自定义控件使用

在浏览类的所有方法时,
onAttach()
onDetach()
方法引起了我的注意。

public void onAttach() {
mEventTracker.recordEvent(Event.ON_HOLDER_ATTACH);
mIsHolderAttached = true;
attachOrDetachController();
}

public void onDetach() {
mEventTracker.recordEvent(Event.ON_HOLDER_DETACH);
mIsHolderAttached = false;
attachOrDetachController();
}


通过注释可以知道
onAttach()
的核心作用是获取
DraweeController
以显示图像;
onDetach()
的核心作用是释放用于显示图像的资源。他们都共同调用了
attachOrDetachController()
方法。

private void attachOrDetachController() {
if (mIsHolderAttached && mIsVisible && mIsActivityStarted) {
attachController();
} else {
detachController();
}
}


你会发现,当同时满足
onAttach()
被调用、控件可见时、Activity启动时才会调用
attachController()
方法,既然需要同时满足这么多条件,想来这一定是个非常重要的方法。

可以说,Fresco核心加载的逻辑都是由这段代码引出的。

private void attachController() {
if (mIsControllerAttached) {
return;
}
mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);
mIsControllerAttached = true;
if (mController != null && mController.getHierarchy() != null) {
mController.onAttach();
}
}

private void detachController() {
if (!mIsControllerAttached) {
return;
}
mEventTracker.recordEvent(Event.ON_DETACH_CONTROLLER);
mIsControllerAttached = false;
if (mController != null) {
mController.onDetach();
}
}


其中
mIsControllerAttached
是保证核心逻辑在一个控件中只被执行一次。在
attachController()
方法中,如果同时满足了
DraweeController
不为空和
DraweeHierarchy
不为空,才会调用
DraweeController
onAttach()
方法。

到了这里,你是不是非常好奇在
DraweeController
onAttach()
的方法中,到底做了怎么样重要的逻辑? (^__^) 嘻嘻……



最后

DraweeView的体系分析就基本完成了,如果觉得对您有帮助,多多留言哦。

github:https://github.com/biezhihua

总览:http://blog.csdn.net/biezhihua/article/details/49783817
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: