Fresco源码分析之DraweeView

来源:互联网 发布:刷游戏币软件 编辑:程序博客网 时间:2024/04/30 07:20

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()方法,它的源码如下:

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会封装成一个ImageRequestController真正持有的是uri的封装体ImageRequest。在SimpleDraweeView中它会重写setImageURI方法,最终也就是将ImageView中的原生方法给覆盖掉。还有其它的类似的setImageResourcesetImageBitmap等,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);  }

在这里我们会看到之前所提到的sDraweeControllerBuilderSupplierSimpleDraweeView中必须优先调用的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将图层传递给DraweeHolderDraweeHolder是用来管理HierarchyController的。而DraweeHolder是在最底层的DraweeView中,这也是GenericDraweeView的父类。下面我们进入DraweeView,来看看它到底做了什么。

DraweeView

DraweeViewFresco最底层的控件,也是我们使用它展示图片的基础,它继承于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()是来控制图层的显示与隐藏、绑定与解绑的回调函数,它们都分别会调用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

A holder class for Drawee controller and hierarchy.

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

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的原理,下篇文章将会详细分析相关的原理,敬请期待!

Recommend

Fresco源码分析之Hierarchy
Android共享动画兼容实现
Kotlin最佳实践
RecyclerView下拉刷新与上拉更多
Android高仿微信之mvp实现(四)
tensorflow-梯度下降,有这一篇就足够了
更多精彩文章

阅读全文
0 0
原创粉丝点击