4

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

Fresco源码分析系列Github地址

关注

clipboard.png

Recommend

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


午后一小憩
2.9k 声望838 粉丝