盒子
盒子
Posts List
  1. SimpleDraweeView
  2. Fresco
  3. GenericDraweeView
  4. DraweeView
  5. DraweeHolder
  6. End

Fresco源码分析之DraweeView

Android中图片加载的框架很多,例如:FrescoPicassoGlideImageloader。它们都有各自的优点,但总的来说,使用起来方便简单、可配置性高与提供良好的缓存机制。由于平常主要用的还是Fresco,所以这里有必要对Fresco的原理进行深入研究。这样对于以后的使用与理解将会得到巨大的帮助。

Fresco是专注于对图片加载而设计的框架,所以对于以图片为主的App强烈推荐使用。Fresco对于图片的展示支持多种情况:backgroud image(背景图)、placeholder image(占位图)、actual image(加载的图片)、progress bar image(进度条)、retry image(重新加载的图片)、failure image(失败图片)与overlay image(叠加图)。Fresco既然支持这么多图片展示情况,那么它对这次图层的管理模式又是怎么样的呢?Fresco对于这些图层的管理都交给了Hierarchy,而这些图层的数据都通过Controller来设置。先不分析这些,这些后续文章会详细分析,今天先从Fresco的基本组件开始。

SimpleDraweeView

首先这是对Fresco的源码分析,所以在看这篇文章之前你应该要有使用Fresco的基础,如果没有的强烈推荐看下Fresco官方文档

我们使用Fresco进行图片加载,使用最多的还是已经封装好的SimpleDraweeView,而在SimpleDraweeView的构造方法中会调用init()方法,它的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void init(Context context, @Nullable AttributeSet attrs) {
if (isInEditMode()) {
return;
}
Preconditions.checkNotNull(
sDraweeControllerBuilderSupplier,
"SimpleDraweeView was not initialized!");
mSimpleDraweeControllerBuilder = sDraweeControllerBuilderSupplier.get();
if (attrs != null) {
TypedArray gdhAttrs = context.obtainStyledAttributes(
attrs,
R.styleable.SimpleDraweeView);
try {
if (gdhAttrs.hasValue(R.styleable.SimpleDraweeView_actualImageUri)) {
setImageURI(
Uri.parse(gdhAttrs.getString(R.styleable.SimpleDraweeView_actualImageUri)),
null);
} else if (gdhAttrs.hasValue((R.styleable.SimpleDraweeView_actualImageResource))) {
int resId = gdhAttrs.getResourceId(
R.styleable.SimpleDraweeView_actualImageResource,
NO_ID);
if (resId != NO_ID) {
setActualImageResource(resId);
}
}
} finally {
gdhAttrs.recycle();
}
}
}

这个方法做的事情很简单,但我们要注意的是它会对sDraweeControllerBuilderSupplier进行null判断,如果为null将会抛出异常。sDraweeControllerBuilderSupplier是供应类,通过它的get方法来获取DraweeControllerBuilder,这个是controller构造器,这个以后的章节会详细说明。空判断的目的就是在使用SimpleDraweeView之前必须初始化sDraweeControllerBuilderSupplier。在SimpleDraweeView中我们能找到它的初始化方法

1
2
3
4
public static void initialize(
Supplier<? extends SimpleDraweeControllerBuilder> draweeControllerBuilderSupplier) {
sDraweeControllerBuilderSupplier = draweeControllerBuilderSupplier;
}

它是一个static方法,在SimpleDraweeView初始化之前加载一次即可。这是SimpleDraweeView的关键。它还有一个关键方法

1
2
3
4
5
6
7
8
9
public void setImageURI(Uri uri, @Nullable Object callerContext) {
//通过controller 来保存Uri 等相关信息
DraweeController controller = mSimpleDraweeControllerBuilder
.setCallerContext(callerContext)
.setUri(uri)
.setOldController(getController())
.build();
setController(controller);
}

使用mSimpleDraweeControllerBuilder来构建一个Controller,而Controller是由build模式所创建,这里我们能看到uri也交由Controller管理。其实最终uri会封装成一个ImageRequestController真正持有的是uri的封装体ImageRequest。在SimpleDraweeView中它会重写setImageURI方法,最终也就是将ImageView中的原生方法给覆盖掉。还有其它的类似的setImageResourcesetImageBitmap等,Fresco都在这些方法上加了@Deprecated,意思就是说不推荐使用,如果使用的话就跟直接使用ImageView没什么区别,这就无法体验到Fresco的强大的特性。在Fresco中统一由setController来替代。

关于Controller后续文章会详细分析。

Fresco

如果我们根据Fresco官方文档的正常步骤来使用的话就无需担心这一步,因为在我们在使用Fresco之前都要先调用Fresco.initialize(context)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void initialize(
Context context,
@Nullable ImagePipelineConfig imagePipelineConfig,
@Nullable DraweeConfig draweeConfig) {
if (sIsInitialized) {
FLog.w(
TAG,
"Fresco has already been initialized! `Fresco.initialize(...)` should only be called " +
"1 single time to avoid memory leaks!");
} else {
sIsInitialized = true;
}
// we should always use the application context to avoid memory leaks
context = context.getApplicationContext();
if (imagePipelineConfig == null) {
//初始化ImagePipeline工厂,包含ImagePipelineConfig 相关初始化配置信息
// (三级缓存、图片解码/编码、转化、渐变、bitmap配置、四种executor 分别为 io、decode、background、lightweight background)等
ImagePipelineFactory.initialize(context);
} else {
ImagePipelineFactory.initialize(imagePipelineConfig);
}
//初始化Drawee相关配置信息
initializeDrawee(context, draweeConfig);
}

除了初始化ImagePipeline之外,最后还会调用initializeDrawee (context, draweeConfig),我们来看下initializeDrawee做了什么

1
2
3
4
5
6
7
8
9
10
11
12
private static void initializeDrawee(
Context context,
@Nullable DraweeConfig draweeConfig) {
//构建PipelineDraweeControllerBuilderSupplier,
//其中ImagePipeline、PipelineDraweeControllerFactory、ControllerListener set集合
sDraweeControllerBuilderSupplier =
new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
//初始化SimpleDrawee
//初始化时通过调用PipelineDraweeControllerBuilderSupplier实现的Supplier的get()方法
// 返回配置信息的封装体PipelineDraweeControllerBuilder implements SimpleDraweeControllerBuilder
SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}

在这里我们会看到之前所提到的sDraweeControllerBuilderSupplierSimpleDraweeView中必须优先调用的initialize方法。相信现在应该明白的为什么在使用Fresco之前必须调用它的initialize方法了。因为它必须要初始化一些必要的配置信息,其中就包括使用的控件SimpleDraweeView的配置信息。

GenericDraweeView

上面所说的SimpleDraweeView的父类是GenericDraweeView,它做的事情很简单,处理xml相关的属性。它会通过inflateHierarchy方法进行初始化。

1
2
3
4
5
6
protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
GenericDraweeHierarchyBuilder builder =
GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);
setAspectRatio(builder.getDesiredAspectRatio());
setHierarchy(builder.build());
}

它是由GenericDraweeHierarchyBuilder来统一封装这些属性。最终通过build方法来构建GenericDraweeHierarchy,这就是Fresco的图层。然后通过setHierarchy将图层传递给DraweeHolderDraweeHolder是用来管理HierarchyController的。而DraweeHolder是在最底层的DraweeView中,这也是GenericDraweeView的父类。下面我们进入DraweeView,来看看它到底做了什么。

DraweeView

DraweeViewFresco最底层的控件,也是我们使用它展示图片的基础,它继承于ImageView,所以它能做的事也无非于在原生ImageView上做扩展或者方法重写,从而来实现自己的一套逻辑。先看下它的构造方法

1
2
3
4
public DraweeView(Context context) {
super(context);
init(context);
}

没什么特别的逻辑,就一个init方法,那么就进入init看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** This method is idempotent so it only has effect the first time it's called */
private void init(Context context) {
if (mInitialised) {
return;
}
mInitialised = true;
mDraweeHolder = DraweeHolder.create(null, context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ColorStateList imageTintList = getImageTintList();
if (imageTintList == null) {
return;
}
setColorFilter(imageTintList.getDefaultColor());
}
// In Android N and above, visibility handling for Drawables has been changed, which breaks
// activity transitions with DraweeViews.
mLegacyVisibilityHandlingEnabled = sGlobalLegacyVisibilityHandlingEnabled &&
context.getApplicationInfo().targetSdkVersion >= 24; //Build.VERSION_CODES.N
}

我们可以看到它会通过mInitialised来判断是否需要初始化,源码注释也说明的该方法只会调用一次。这是因为创建Hierarchy的代价太大,所以只会创建一次,以后都会使用同一个mDraweeHolder中的Hierarchy,所以会看到这里就必须初始化一个mDraweeHolder。在DraweeView中还有以下几个主要方法:

  • void setHierarchy(DH hierarchy)设置Hierarchy,同时会将Hierarchy交由mDraweeHolder管理,最后还会调用super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());来将Hierarchy中的图层树显示出来。
  • Drawable getTopLevelDrawable()会通过mDraweeHolder中的Hierarchy来获取图层树。
  • void setController(@Nullable DraweeController draweeController)设置Controller,同时也会将Hierarchy中的图层树显示出来。
  • void onAttachedToWindow()void onDetachedFromWindow()void onStartTemporaryDetach()void onFinishTemporaryDetach()是来控制图层的显示与隐藏、绑定与解绑的回调函数,它们都分别会调用mDraweeHolderonAttach()onDetach()。其实最终调用的还是Controller中的onAttach()onDetach()
  • boolean onTouchEvent(MotionEvent event)控制控件的触摸,如果Controller有效的话会调用Controller中的onTouchEvent
  • void setAspectRatio(float aspectRatio)用来设置DraweeView显示的宽高比例。
  • setImageDrawablesetImageBitmapsetImageResourcesetImageURI这些方法都是原生ImageView的方法,但在DraweeView中这些方法都被加上了@Deprecated标记。标明不推荐使用,如果一定使用的话,那么DraweeView将会退化成一个普通的ImageView。因为在DraweeView中都是通过Controller来体现它的缓存、加载机制等特性。

上面这些就是DraweeView的主要涉及到的方法与特性,不过在DraweeView中基本上每一个方法都涉及到了DraweeHolder,那它到底是干什么的呢?别急下面就轮到它了。

DraweeHolder

1
A holder class for Drawee controller and hierarchy.

上面的是官方注释,说明DraweeHolder是用来管理HierarchyController的,同时也是它们之间的联系的桥梁。DraweeView以及它的子类都是通过它来间接操作ControllerHierarchy

1
2
3
4
5
6
7
public static <DH extends DraweeHierarchy> DraweeHolder<DH> create(
@Nullable DH hierarchy,
Context context) {
DraweeHolder<DH> holder = new DraweeHolder<DH>(hierarchy);
holder.registerWithContext(context);
return holder;
}

它是通过公有的静态方法来创建自身实例的。在上面的DraweeViewinit方法中会调用。在其内部的DraweeEventTracker,是用来记录事件的传递,方便dubug的调试。如果不需要的话,可以在Fresco.initialize()之前调用DraweeEventTracker.disable()。那么剩下的方法其实基本上在DraweeView中都说过。

  • onAttach()onDetach(),都会调用attachOrDetachController(),根据情况分别调用attachController()detachController(),最终调用的就是ControlleronAttach()onDetach()
  • Drawable getTopLevelDrawable()调用mHierarchy.getTopLevelDrawable()获取图层树。
  • void setController(@Nullable DraweeController draweeController)设置Controller,在设置之前会先判断是否已经wasAttached,如果是的话就先调用detachController(),然后清除老的Controller,再将Hierarchy设置到新的Controller中。最后再attachController()进行绑定显示图层。
  • void setHierarchy(DH hierarchy)设置Hierarchy,如果Controller有效的话就与Hierarchy建立链接,将Hierarchy设置到Controller中。

以上就是DraweeHolder的主要方法,都跟ControllerHierarchy相关。而DraweeHolder又与DraweeView相连,所以最终还是要回到ControllerHierarchy中。

End

这次主要是分析了Fresco中的基本组件DraweeView与它的子类。如果你还想进一步了解HierarchyController的原理,下篇文章将会详细分析相关的原理,敬请期待!

Fresco源码分析系列Github地址

支持一下
赞赏是一门艺术