Fresco源码分析之Hierarchy

上篇文章我们分析了Fresco中的DraweeView,对其中的一些原理以及方法进行了解析。在这过程中我们了解到,DraweeView中是通过DraweeHolder来统一管理的。而DraweeHolder又是用来统一管理相关的HierarchyController,如果想了解DraweeView相关的知识,可以先看下我的前一篇文章Fresco源码分析之DraweeView。今天这里进一步来分析Fresco中的Hierarchy

GenericDraweeHierarchyBuilder

GenericDraweeView的构造方法中会调用inflateHierarchy(context, atts)方法来创建一个GenericDraweeHierarchyBuilder对象,通过调用该对象的build方法来生成一个Hierarchy

如果你看了我上一篇文章,相信对这个方法你会感到很亲切。

所以这个类的主要作用是用来创建一个Hierarchy,通过builder模式来实例化包含相关信息的Hierarchy。如果你一步步深入了解Fresco的话,相信你对builder模式也将习以为常,因为后面你将经常与它碰面。这也是Fresco开源项目的主要设计模式。在该builder类中主要构建的信息有:

  • Drawable相关:placeholderImageretryImagefailureImageprogressBarImagebackgroundoverlayspressedStateOverlay
  • ScaleType相关:与Drawable相对应的placeholderImageScaleTyperetryImageScaleType等等。
  • 其它:fadeDuration渐变过渡时间与RoundingParams圆角相关信息等等

GenericDraweeHierarchy

通过上面的GenericDraweeHierarchyBuilderbuild方法会创建一个GenericDraweeHierarchy对象。这就是我们今天需要主要分析的类,也是Fresco的一个核心类。

SettableDraweeHierarchy

首先我们来分析一下它的类结构,它会实现SettableDraweeHierarchy接口,我们进入该接口发现一共有6个接口方法,它们分别为:

  1. void reset(); 重新初始化Hierarchy
  2. void setImage(Drawable drawable, float progress, boolean immediate); 设置实际需要展示的图片,其中progress表示图片的加载质量进度(在渐进式中会使用到)
  3. void setProgress(float progress, boolean immediate); 更新图片加载进度
  4. void setFailure(Throwable throwable); 图片加载失败时调用,可以设置failureImage
  5. void setRetry(Throwable throwable); 当图片加载失败时重新进行加载,可以设置retryImage
  6. void setControllerOverlay(Drawable drawable); 用来设置图层覆盖

这些方法在GenericDraweeHierarchy中都会做出相应的实现,同时最终都会在对应的DraweeView中的Controller来调用。

至于这些方法中的实现细节,由于代码比较多,这里就不一一列举出来,大家可以自行查看GenericDraweeHierarchy的源码。

DraweeHierarchy

上面的SettableDraweeHierarchy还有一个父类接口为DraweeHierarchy,在这个接口中只有一个接口方法为Drawable getTopLevelDrawable();。是不是对这个方法也有点熟悉呢?(路人甲:嗯,好像上篇文章提及过!)它是用来获取视图树的最顶层视图,其实说白了就是显示出来的Drawable。它主要在DraweeHolder中调用,最终也会由void setHierarchy(DH hierarchy)void setController(@Nullable DraweeController draweeController) 来调用

super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());

从而显示需要展示的图片。

下面我们回到之前的GenericDraweeHierarchy类。在开始分析它之前,我们先来了解一下它的另一个感念-层级树或者说图层树,我这里就把它说成图层树吧。那么我们来看下Fresco的图层树是什么样的:

 *  o RootDrawable (top level drawable)
 *  |
 *  +--o FadeDrawable
 *     |
 *     +--o ScaleTypeDrawable (placeholder branch, optional)
 *     |  |
 *     |  +--o Drawable (placeholder image)
 *     |
 *     +--o ScaleTypeDrawable (actual image branch)
 *     |  |
 *     |  +--o ForwardingDrawable (actual image wrapper)
 *     |     |
 *     |     +--o Drawable (actual image)
 *     |
 *     +--o null (progress bar branch, optional)
 *     |
 *     +--o Drawable (retry image branch, optional)
 *     |
 *     +--o ScaleTypeDrawable (failure image branch, optional)
 *        |
 *        +--o Drawable (failure image

根据上面所展示的层次结构,我们可以发现最底层是由RootDrawable来构成,它有一个直接子分支为FadeDrawable。而在FadeDrawable中又有5个直接子分支,分别为placeholder branchactual image branchprogressBar image branchretry image branchfailure image branch。至于这些image的作用相信不用我再多做说明了,这些image除了actual image是必须要明确指定的,其它的都是可选择的配置。因此RootDrawableFadeDrawable是一定存在的。虽然其它的都是可选的配置,但无论你是否选择了,它们的层级结构都会保留在图层树中。还有每一个层级都有自己独立的scale type,当然rounding(圆角)也是支持的。

其实除了这5个分支,与它们同一层次的还有background imageoverlay image,它们分别位于placeholder image之前与failure image之后。background相信都知道,因为图片都支持backgroundsrcoverlay为图层覆盖。至于为什么没有在上面的结构中显示,我这里也不得而知,猜测可能是这两个并不是Fresco主要常用的特性。

那么这些图层结构是通过layers数组来体现的,可以来看下GenericDraweeHierarchy的源码

  GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
    mResources = builder.getResources();
    mRoundingParams = builder.getRoundingParams();
 
    mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable);
 
    int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 1;
    numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0;
 
    // layer indices and count
    int numLayers = OVERLAY_IMAGES_INDEX + numOverlays;
 
    // array of layers
    Drawable[] layers = new Drawable[numLayers];
    layers[BACKGROUND_IMAGE_INDEX] = buildBranch(builder.getBackground(), null);
    layers[PLACEHOLDER_IMAGE_INDEX] = buildBranch(
        builder.getPlaceholderImage(),
        builder.getPlaceholderImageScaleType());
    layers[ACTUAL_IMAGE_INDEX] = buildActualImageBranch(
        mActualImageWrapper,
        builder.getActualImageScaleType(),
        builder.getActualImageFocusPoint(),
        builder.getActualImageColorFilter());
    layers[PROGRESS_BAR_IMAGE_INDEX] = buildBranch(
        builder.getProgressBarImage(),
        builder.getProgressBarImageScaleType());
    layers[RETRY_IMAGE_INDEX] = buildBranch(
        builder.getRetryImage(),
        builder.getRetryImageScaleType());
    layers[FAILURE_IMAGE_INDEX] = buildBranch(
        builder.getFailureImage(),
        builder.getFailureImageScaleType());
    if (numOverlays > 0) {
      int index = 0;
      if (builder.getOverlays() != null) {
        for (Drawable overlay : builder.getOverlays()) {
          layers[OVERLAY_IMAGES_INDEX + index++] = buildBranch(overlay, null);
        }
      } else {
        index = 1; // reserve space for one overlay
      }
      if (builder.getPressedStateOverlay() != null) {
        layers[OVERLAY_IMAGES_INDEX + index] = buildBranch(builder.getPressedStateOverlay(), null);
      }
    }
 
    // fade drawable composed of layers
    mFadeDrawable = new FadeDrawable(layers);
     mFadeDrawable.setTransitionDuration(builder.getFadeDuration());
 
    // rounded corners drawable (optional)
    Drawable maybeRoundedDrawable =
        WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);
 
    // top-level drawable
    mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);
    mTopLevelDrawable.mutate();
 
    resetFade();
  }

根据源码可以明显的看出不管overlay是否存在,它都会保留图层层次,当然如果存在的话,就根据实际情况在OVERLAY_IMAGES_INDEX之后增加图层层次。layers是一个Drawable数组,这里通过Drawable buildBranch(@Nullable Drawable drawable, @Nullable ScaleType scaleType) 方法来创建对应的Drawable。在最后,创建了FadeDrawable并将layers传递给它,在这里FadeDrawable的特性应该有点眉目了吧,其实它内部做的就是对Drawable数组进行操作。之后RootDrawable也出现了(关于Drawable文章后面会统一分析),这样之前所提到的层级结构就形成了。

既然actual image是一定存在的,那么在它真正展示之前image中的显示用什么来控制的呢?其实就是我们之前所提到的SettableDraweeHierarchy来控制。在真实的图片展示出来之前,它可以用来展示placeholder image等相关图层。具体的我们可以来看一下它里面实现的一些方法。

setImage

这里就拿void setImage(Drawable drawable, float progress, boolean immediate) 来进行深入分析,那么下面来看下它的源码:

  @Override
  public void setImage(Drawable drawable, float progress, boolean immediate) {
    drawable = WrappingUtils.maybeApplyLeafRounding(drawable, mRoundingParams, mResources);
    drawable.mutate();
    mActualImageWrapper.setDrawable(drawable);
    mFadeDrawable.beginBatchMode();
    fadeOutBranches();
    fadeInLayer(ACTUAL_IMAGE_INDEX);
    setProgress(progress);
    if (immediate) {
      mFadeDrawable.finishTransitionImmediately();
    }
    mFadeDrawable.endBatchMode();
  }

首先该方法会使用WrappingUtils工具类并且结合RoundingParams参数来重新生成一个可以附带圆角的Drawable。然后将新的Drawable交由mActualImageWrapper,结合上面的层次结果分析,会很容易知道这就是需要真实显示的图层。它是一个ForwardingDrawable类型,该类主要是对传入的目标Drawable进行相应的原生方法操作。下一步调用FadeDrawablebeginBatchMode()方法,该方法的作用主要为了图层批处理做标记,防止在批处理时进行invalidate操作。直到最后的endBatchMode()方法调用之后才标识着图层批处理操作结束。该批处理操作的目的是将actual image图层显示出来,所以首先调用fadeOutBranches()方法:

  private void fadeOutBranches() {
    fadeOutLayer(PLACEHOLDER_IMAGE_INDEX);
    fadeOutLayer(ACTUAL_IMAGE_INDEX);
    fadeOutLayer(PROGRESS_BAR_IMAGE_INDEX);
    fadeOutLayer(RETRY_IMAGE_INDEX);
    fadeOutLayer(FAILURE_IMAGE_INDEX);
  }

这里对上述提到的所有图层进行fadeOutLayer()操作,继续进入fadeOutLayer()方法

  private void fadeOutLayer(int index) {
    if (index >= 0) {
      mFadeDrawable.fadeOutLayer(index);
    }
  }

发现也很简单,无非就是调用了FadeDrawablefadeOutLayer()方法。

  public void fadeOutLayer(int index) {
    mTransitionState = TRANSITION_STARTING;
    mIsLayerOn[index] = false;
    invalidateSelf();
  }

在之前已经提到过FadeDrawable本质上可以理解为时一个Drawable数组,内部都是围绕着数组整体进行操作。对应的还有fadeInLayer()

  private void fadeInLayer(int index) {
    if (index >= 0) {
      mFadeDrawable.fadeInLayer(index);
    }
  }

FadeDrawable中除了用来保存相应的Drawable数组mLayers,还有与其相对应的mIsLayerOn布尔数组,该数组用来标识各个Hierarchy中的图层是否需要展示。所以fadeOutLayer()是对index处的图层进行隐藏标识。最终的显隐操作都会转化为在void draw(Canvas canvas)方法中进行alpha操作。

那么再回到之前的setImage()方法中,fadeOutBranches()是对相关的图层进行隐藏标识,然后再通过fadeInLayer(ACTUAL_IMAGE_INDEX)方法改变actual image图层的标识,将它改变成显示状态。最后如果有progressBar image图层的话,也将会由setProgress(progress)方法来体现。immediate是用来判断是否之后的显隐操作立马实现或者渐变过渡实现(内部就是对alpha进行百分比操作,内部有个mDurationMs,该时间值也是文章开头提到的builder中的fadeDuration)。这样整个的实际图片展示流程我们已经分析完毕,所以Hierarchy中最重要的还是对图层概念的理解。下面再对GenericDraweeHierarchy中的一些其它方法进行简要的说明:

  • Drawable buildActualImageBranch(Drawable drawable, @Nullable ScaleType scaleType, @Nullable PointF focusPoint, @Nullable ColorFilter colorFilter) 构建实际展示图片的图层分支,内部对于Drawable的创建还是借助WrappingUtils工具类
  • Drawable buildBranch(@Nullable Drawable drawable, @Nullable ScaleType scaleType) 构建除实际展示图片之外的其它图层分支,Drawable的创建也是借助WrappingUtils工具类
  • void resetFade() 初始化图层结构

主要的就这几个吧,其它的都已经在上面分析流程中详细说明了。

Drawable

最后再整理一下Fresco中的一些相关的自定义的Drawable子类

  1. ArrayDrawableDrawable数组的集合体,通过layers数组来管理Drawable,内部的都是对数组集合中的每一个Drawable进行操作,类似与Android原生的LayerDrawable,只是它并不支持addremove操作
  2. ForwardingDrawable:对传入的目标Drawable即操作对象进行封装处理,该新类的方法可以调用目标对象对应的方法,同时保留目标Drawable的各个状态,不依赖与目标类的细节实现,提高新类的稳定性,该方式可以称之为复合。Fresco中绝大多数自定义Drawable都是它的子类。
  3. AutoRotateDrawable:它继承于ForwardingDrawable,实现的是对Drawable的旋转操作
  4. FadeDrawable:它继承于ArrayDrawable,之前也详细提到过,Hierarchy中的主要图层集合体。主要是通过mLayersmIsLayerOn数组来控制数组中各个Drawablealpha值,即显隐
  5. MatrixDrawable:它继承于ForwardingDrawable,顾名思义通过矩阵来改变Drawable状态。
  6. OrientedDrawable:它也继承于ForwardingDrawable,它不同于AutoRotateDrawable的是,它只支持90度的倍数角度旋转。
  7. ProgressBarDrawable:进度条Drawable,支持横竖方向。
  8. RoundedBitmapDrawable:继承于BitmapDrawable,根据Bitmap来创建有关圆角的Drawable,主要在WrappingUtils类中使用,用例构建全新的圆角Drawable
  9. RoundedColorDrawable:继承于Drawable,根据Color来创建圆角Drawable,主要在WrappingUtils类中使用,用例构建全新的圆角Drawable
  10. RoundedCornersDrawable:继承于ForwardingDrawable,用于设计overLay覆盖图片圆角,主要在WrappingUtils类中使用
  11. ScaleTypeDrawable:继承于ForwardingDrawable,用于缩放类型的Drawable

End

本篇文章主要是分析Fresco中有关Hierarchy相关的实现与原理,通过分析发现Hierarchy中都是对Drawable图层进行处理,并没有其它的缓存、请求之类的逻辑。所以如果你使用Fresco的时候只使用Hierarchy的话,就与别的ImageView没有多大的区别,真正的图层操作与缓存控制都在Controller中,所以下篇文章将进入Controller解析,来详细了解它的实现细节。

Fresco源码分析系列Github地址

Recommend

Android共享动画兼容实现
Kotlin最佳实践
RecyclerView下拉刷新与上拉更多
Android高仿微信之mvp实现(四)
php与android的简单交互
tensorflow-梯度下降,有这一篇就足够了
博客

你可能感兴趣的:(Fresco源码分析之Hierarchy)