正文
既然要分析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.youkuaiyun.com/biezhihua/article/details/49783817