当前位置: 首页 > 工具软件 > Fresco > 使用案例 >

android fresco的底层,Fresco源码分析之DraweeView

田慈
2023-12-01

在Android中图片加载的框架很多,例如:Fresco、Picasso、Glide与Imageloader。它们都有各自的优点,但总的来说,使用起来方便简单、可配置性高与提供良好的缓存机制。由于平常主要用的还是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()方法,它的源码如下:

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中我们能找到它的初始化方法

public static void initialize(

Supplier extends SimpleDraweeControllerBuilder> draweeControllerBuilderSupplier) {

sDraweeControllerBuilderSupplier = draweeControllerBuilderSupplier;

}

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

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会封装成一个ImageRequest,Controller真正持有的是uri的封装体ImageRequest。在SimpleDraweeView中它会重写setImageURI方法,最终也就是将ImageView中的原生方法给覆盖掉。还有其它的类似的setImageResource与setImageBitmap等,Fresco都在这些方法上加了@Deprecated,意思就是说不推荐使用,如果使用的话就跟直接使用ImageView没什么区别,这就无法体验到Fresco的强大的特性。在Fresco中统一由setController来替代。

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

Fresco

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

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做了什么

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);

}

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

GenericDraweeView

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

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将图层传递给DraweeHolder。DraweeHolder是用来管理Hierarchy与Controller的。而DraweeHolder是在最底层的DraweeView中,这也是GenericDraweeView的父类。下面我们进入DraweeView,来看看它到底做了什么。

DraweeView

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

public DraweeView(Context context) {

super(context);

init(context);

}

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

/** 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()是来控制图层的显示与隐藏、绑定与解绑的回调函数,它们都分别会调用mDraweeHolder的onAttach()与onDetach()。其实最终调用的还是Controller中的onAttach()与onDetach()。

boolean onTouchEvent(MotionEvent event)控制控件的触摸,如果Controller有效的话会调用Controller中的onTouchEvent。

void setAspectRatio(float aspectRatio)用来设置DraweeView显示的宽高比例。

setImageDrawable、setImageBitmap、setImageResource与setImageURI这些方法都是原生ImageView的方法,但在DraweeView中这些方法都被加上了@Deprecated标记。标明不推荐使用,如果一定使用的话,那么DraweeView将会退化成一个普通的ImageView。因为在DraweeView中都是通过Controller来体现它的缓存、加载机制等特性。

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

DraweeHolder

A holder class for Drawee controller and hierarchy.

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

public static DraweeHolder create(

@Nullable DH hierarchy,

Context context) {

DraweeHolder holder = new DraweeHolder(hierarchy);

holder.registerWithContext(context);

return holder;

}

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

onAttach()与onDetach(),都会调用attachOrDetachController(),根据情况分别调用attachController()与detachController(),最终调用的就是Controller的onAttach()与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的主要方法,都跟Controller与Hierarchy相关。而DraweeHolder又与DraweeView相连,所以最终还是要回到Controller与Hierarchy中。

End

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

关注

Recommend

 类似资料: