fresco解析之Hierarchy

来源:互联网 发布:json在线编辑器 编辑:程序博客网 时间:2024/05/22 01:45

这几篇博文是我在研究fresco源码的过程中的思考,作个记录,写成文字,是想让我的思路更加清晰。在我看众多开源代码的时候,我总有困惑,不知何处下手分析、不能给明白背后的设计原理、进而导致分析完之后,除了代码阅读能力及个别原理掌握外,无法形成灵活运用。这些问题曾一直让我非常的纠结,也走了很多弯路。直到有一天突然想到,为什么不用自己的所拥有的知识去“猜测”一下整个架构设计背后的思路?要知道,当某个软件问世时,其作者肯定已经对其作了大量的思考,结合场景和功能,对其进行编码。也就是说,作者是有一定的思考过程的。对我而言,这个思考过程非常重要,我认为学习这个思考过程是学习一个开源软件的精髓。所以,我开始从整体到局部的角度来“观察”开源软件——对开源的软件:首先,从整体上明白其主要功能,这个过程需要观察软件运行的逻辑;之后,试图找到“最小功能模块”,这有可能是一个或多个类。深入各个类,即编码过程,详细了解原作者在编码的过程中是如何“深入”思考的;最后,再研究作者是如何把各个类,或者小的功能模块串起来的。这个结论对我来说是非常辛苦才得来的,但收获也是颇大的。设想,如果能够从整体的“架构的角度”学习,能够学习其作者“高屋建瓴”的本领,还能够学到编码过程中作者的“思考”,那剩下的可能就是简单的编码了。作为程序员,编码是很简单的事情,重点是编码背后的“思考”。啰啰嗦嗦,我也不知道自己写清楚我的观点没有。总而言之一句话,通过“揣测”作者的“思路”来学习开源软件,会对自身提高很快。


本片博文是我的第一篇实践,将以我的理解,“猜测”fresco设计思路:

1、联想

联想fresco的使用过程:使用 fresco 先展示一个默认的placeholder,等真正的图片准备好之后展示这个真正的图片,其中可能有进度条、过渡动画。如果真正的图片失败了,还需要加载重试、或者失败的图片。这一系列过程比较类似“状态设计模式”,比如初始状态是一个默认的holder图片,失败的状态展示失败图片,中间会涉及到不同“状态”下对应的状态转换。这个“状态”在fresco里面叫做hierarchy,即层次,这里的层次比较接近于Tree,但是比Tree更轻量级,后面可以了解到,所谓的hierarchy,就是数组。一个hierarchy也好,还是一个tree也好,其最基本的就是它的“根”,也可以理解为初始状态、树的根。那如何对这个所谓的hierarchy,即层次进行抽象呢?在fresco中,这样的hierarchy叫做DraweeHierarchy。在我的方法里,hierarchy可以作为一种“最小功能模块”来研究。通过继承关系,可以找到最“顶端”的那个类/接口,对于DraweeHierarchy这个interface来说,它只要一个方法,就是getTopLevelDrawable(),返回值是Drawable。它背后的设计逻辑大概是:本继承关系下,每个“后代”都应该能够提供这样一个方法,可以返回“根”Drawable。

public interface DraweeHierarchy {/**   * Returns the top level drawable in the corresponding hierarchy. Hierarchy should always have   * the same instance of its top level drawable.   * @return top level drawable   */Drawable getTopLevelDrawable();}

2、hierarchy

对于一个hierarchy来说,对其可以进行的操作显然更多,绝不仅仅是getTopLevelDrawable,因此需要抽象出更多的“通用的”方法。“状态”的转变就是一个最直观的例子,也就是上面说的“一会展示placeholder,一会展示失败图片…某个逻辑下展示这个,某个逻辑下展示那个……”。因此fresco抽象出一个SettableDraweeHierarchy,这个“层次”是settable的。那究竟对谁进行set呢?为了解耦,这里没有包含此类信息,但是它解耦出来可以“怎么set”、“set什么”。因此,它又抽象出一些set方法。

import android.graphics.drawable.Drawable;/** * Interface that represents a settable Drawee hierarchy. Hierarchy should display a placeholder * image until the actual image is set. In case of a failure, hierarchy can choose to display * a failure image. * * <p>IMPORTANT: methods of this interface are to be used by controllers ONLY! * * <p>* Example hierarchy: //备注:这个例子就是一个“层次”型的Drawable,具体这些Drawable如何组织,如何存储还没说。 * *   o FadeDrawable (top level drawable) *   | *   +--o ScaleTypeDrawable *   |  | *   |  +--o ColorDrawable (placeholder image) *   | *   +--o ScaleTypeDrawable *   |  | *   |  +--o BitmapDrawable (failure image) *   | *   +--o ScaleTypeDrawable *      | *      +--o SettableDrawable *         | *         +--o BitmapDrawable (actual image) * *   SettableDraweeHierarchy in the given example has a FadeDrawable as its top level drawable. *   Top level drawable can be immediately put into view. Once the actual image is ready, it will *   be set to the hierarchy's SettableDrawable and fade animation between the placeholder and the *   actual image will be initiated. In case of failure, hierarchy will switch to failure image. *   All image branches are wrapped with ScaleType drawable which allows separate scale type to be *   applied on each. * */public interface SettableDraweeHierarchy extends DraweeHierarchy {/**   * Called by controller when the hierarchy should be reset to its initial state. Any image   * previously set by {@code setImage} should be detached and not used anymore.   */void reset();/**   * Called by controller when the future that provides the actual image completes successfully.   * Hierarchy should display the actual image.   * @param drawable drawable to be set as the temporary image   * @param progress number in range [0, 1] that indicates progress   * @param immediate if true, image will be shown immediately (without fade effect)   */void setImage(Drawable drawable, float progress, boolean immediate);/**   * Called by controller to update the progress.   * Hierarchy can choose to hide the progressbar when progress is set to its final value of 1.   * @param progress number in range [0, 1] that indicates progress   * @param immediate if true, progressbar will be shown/hidden immediately (without fade effect)   */void setProgress(float progress, boolean immediate);/**   * Called by controller when the future that provides the actual image completes with failure.   * Hierarchy can choose to display between different images based on cause of failure.   * @param throwable cause of failure   */void setFailure(Throwable throwable);/**   * Called by controller when the future that provides the actual image completes with failure,   * but the controller is prepared to kick off a retry when the user clicks on the image.   * Hierarchy can choose to display a retry image.   * @param throwable cause of failure   */void setRetry(Throwable throwable);/**   * Called by controller if it needs to display some controller overlay.   * @param drawable drawable to be displayed as controller overlay   */void setControllerOverlay(Drawable drawable);}

上面的几个set方法很好理解,设置图片、进度条、失败、重试等。一个不可忽视的地方是,这几个函数接受的参数与Drawable有关,也就是说,本interface,即SettableDraweeHierarchy以及1.中的DraweeHierarchy都是与“可Drawable的东西”关联的,View、ImageView都是“可Drawable”的,这里已经隐含了“Hierarchy”是用来辅助“展示”用的。

3、GenericDraweeHierarchy

从名字理解,它就是“通用的DraweeHierarchy”。在fresco里,它需要实现1.和2.中所定义的方法。public class GenericDraweeHierarchy implements SettableDraweeHierarchy 。下面来看看它是如何实现上述的方法的:

@Overridepublic void reset() { // 备注:两个函数调用  resetActualImages();resetFade();}

a 分析resetActualImages()

mActualImageWrapper类型是ForwardingDrawable:这个类直接继承Drawable,并且继承了一堆跟Drawable有关的接口,并且拥有一个Drawable成员mCurrentDelegate。它的设计思路也很有意思,所有对该类进行的操作,都将转移到mCurrentDelegate身上,但是跟Drawable有关的接口(接口中的函数,在架构里,大部分的角色都是会被回调)函数,都在本类里实现,然后后mCurrentDelegate就“坐享其成”:

public class ForwardingDrawable extends Drawable    implements Drawable.Callback, TransformCallback, TransformAwareDrawable, DrawableParent {  /** The current drawable to be drawn by this drawable when drawing is needed */  private Drawable mCurrentDelegate;  private final DrawableProperties mDrawableProperties = new DrawableProperties();  protected TransformCallback mTransformCallback;

下面就是mCurrentDelegate“坐享其成”的例子。这样设计的好处是,不管mCurrentDelegate类型是什么样子的,它的Callback的实现都将是ForwardingDrawable里面设定好的,这样想起来,还算蛮“简洁的”。

DrawableUtils.setCallbacks(mCurrentDelegate, this, this);

继续。mEmptyActualImageDrawable的定义为new ColorDrawable(Color.TRANSPARENT)——表示一种颜色。从mActualImageWrapper的wrapper可以感觉到它是一个“包装着”(联想包装者模式?),它更像是一种“包装”或者“代理”。从ForwardingDrawable可以看到,它内部有一个Drawable的成员mCurrentDelegate,所有对mActualImageWrapper的操作都将“代理给”(传递给?)mCurrentDelegate。fresco框架为了解耦和“优雅”,用到了很多设计模式。

private void resetActualImages() {  mActualImageWrapper.setDrawable(mEmptyActualImageDrawable);}

下面来看ForwardingDrawable中相应的函数调用:

@Overridepublic Drawable setDrawable(Drawable newDrawable) {return setCurrent(newDrawable);}public Drawable setCurrent(Drawable newDelegate) {  Drawable previousDelegate = setCurrentWithoutInvalidate(newDelegate);  invalidateSelf(); // 备注,这个是Drawable的方法,会引起Drawable.Callback的invalidateDrawable被调用(如果Callback不为空)  return previousDelegate;}protected Drawable setCurrentWithoutInvalidate(Drawable newDelegate) {  Drawable previousDelegate = mCurrentDelegate;DrawableUtils.setCallbacks(previousDelegate, null, null);DrawableUtils.setCallbacks(newDelegate, null, null);DrawableUtils.setDrawableProperties(newDelegate, mDrawableProperties);DrawableUtils.copyProperties(newDelegate, previousDelegate);DrawableUtils.setCallbacks(newDelegate, this, this);mCurrentDelegate = newDelegate;  return previousDelegate;}

上面几个代码很容易看懂,主要功能是“把新的Drawable,即newDrawable保存下来,返回旧的Drawable,即mCurrentDelegate”。
小结:把ColorDrawable类型的 mEmptyActualImageDrawable 保到ForwardingDrawable类型的ActualImageWrapper。ForwardingDrawable作为一个继承自Drawable,又拥有一个Drawable成员的类型,对它的操作都“代理到”mCurrentDelegate。

b 分析resetFade()

mFadeDrawable的类型是FadeDrawable,它继承自ArrayDrawable,后者继承自Drawable。从名字中可以“感觉到”,ArrayDrawable包含了一组Drawable,实际上也是如此。ArrayDrawable包含了一组Drawable,即private final Drawable[] mLayers,在该数组中,下标大的元素会被绘制在最上面。FadeDrawable也有一组Drawable,private final Drawable[] mLayers,其中可能包括,5 个Drawable,分别为placeHolderImage、ProgressBarImage、AcutalImage、RetryImage、FailureImage。该数组可能还存在更多的值,也就是各种overlay,下面会说。但重要的是上面5个(这5个不一定都同时存在,后面就不强调这一点了)。

private void resetFade() {if (mFadeDrawable != null) {    mFadeDrawable.beginBatchMode();// turn on all layers (backgrounds, branches, overlays)mFadeDrawable.fadeInAllLayers();// turn off branches (leaving backgrounds and overlays on)fadeOutBranches();// turn on placeholderfadeInLayer(mPlaceholderImageIndex);mFadeDrawable.finishTransitionImmediately();mFadeDrawable.endBatchMode();}}

ArrayDrawable也不复杂,它继承自Drawable,并且实现一些Callback函数。它里面的一个tip跟ForwardingDrawable一样,会把它里面的mLayers的该有的Callback都设置到ArrayDrawable,有一个“坐享其成”。

public class ArrayDrawable extends Drawable    implements Drawable.Callback, TransformCallback, TransformAwareDrawable {  private TransformCallback mTransformCallback;  private final DrawableProperties mDrawableProperties = new DrawableProperties();  // layers  private final Drawable[] mLayers;  // drawable parents for the layers (lazily created)  private final DrawableParent[] mDrawableParents;  // temp rect to avoid allocations  private final Rect mTmpRect = new Rect();  // Whether the drawable is stateful or not  private boolean mIsStateful = false;  private boolean mIsStatefulCalculated = false;  private boolean mIsMutated = false;

还有一个需要重点说明的是:为什么要一组Drawable?前面说过,不同的“状态”切换,会有不同的图片显示出来。它是怎么实现的?难道多个View?显然不是。难道对View不停地setDrawable?好像可行,但是切换动画不咋地。看看fresco怎么实现的:重点看它的draw方法,它重载了Drawable函数,任何对ArrayDrawable的draw都引起一组Drawable的draw,即mLayers。其实它背后的逻辑是:把一组Drawable一次性都渲染出来。哈哈,是不是很奇妙,假设本来想给ImageView设定一个Drawable,结果这个Drawable被修改过的,设定的是一个多个Drawable的组合,good。

@Overridepublic void draw(Canvas canvas) {  for (int i = 0; i < mLayers.length; i++) {    Drawable drawable = mLayers[i];    if (drawable != null) {      drawable.draw(canvas);    }  }}

FadeDrawable内部也不复杂,不过一个值得关注的小点在于它是如何处理fade过程的。在FadeDrawable中:

@Overridepublic void draw(Canvas canvas) {boolean done = true;  float ratio;  switch (mTransitionState) { // 备注:将fade分为三个过程,starting、running、donecase TRANSITION_STARTING:// initialize start alphas and start timeSystem.arraycopy(mAlphas, 0, mStartAlphas, 0, mLayers.length);mStartTimeMs = getCurrentTimeMs();// if the duration is 0, update alphas to the target opacities immediatelyratio = (mDurationMs == 0) ? 1.0f : 0.0f;// if all the layers have reached their target opacity, transition is donedone = updateAlphas(ratio); // 备注:mTransitionState = done ? TRANSITION_NONE : TRANSITION_RUNNING;      break;    case TRANSITION_RUNNING:      Preconditions.checkState(mDurationMs > 0);// determine ratio based on the elapsed timeratio = (float) (getCurrentTimeMs() - mStartTimeMs) / mDurationMs; // 备注:ratio的值从 0 到 1// if all the layers have reached their target opacity, transition is donedone = updateAlphas(ratio); // 备注:根据时间流逝,计算alpha值mTransitionState = done ? TRANSITION_NONE : TRANSITION_RUNNING;      break;    case TRANSITION_NONE:// there is no transition in progress and mAlphas should be left as is.done = true;      break;}for (int i = 0; i < mLayers.length; i++) {    drawDrawableWithAlpha(canvas, mLayers[i], mAlphas[i] * mAlpha / 255); // 备注:调用某个具体的Layer的draw : drawable.draw(canvas);}if (!done) { // 如果未完成,循环调用 draw    invalidateSelf();}}
private boolean updateAlphas(float ratio) {boolean done = true;  for (int i = 0; i < mLayers.length; i++) {int dir = mIsLayerOn[i] ? +1 : -1;// 备注:还可以某个Layer的alpha值增加或减少// determines alpha value and clamps it to [0, 255]mAlphas[i] = (int) (mStartAlphas[i] + dir * 255 * ratio); // 备注:ratio是时间流逝比率,重新计算某Layer的alpha值:从mStartAlphas是个数组推测,每个Layer的起始alpha值可以不同    if (mAlphas[i] < 0) {      mAlphas[i] = 0;}if (mAlphas[i] > 255) {      mAlphas[i] = 255;}// determines whether the layer has reached its target opacityif (mIsLayerOn[i] && mAlphas[i] < 255) {      done = false;}if (!mIsLayerOn[i] && mAlphas[i] > 0) {      done = false;}  }return done;}

此外,FadeDrawable让某个Layer fadein/fadeout,让其他的Layer相应的作“逆操作”—— fadeout/fadein。

public void finishTransitionImmediately() {  mTransitionState = TRANSITION_NONE;  for (int i = 0; i < mLayers.length; i++) {    mAlphas[i] = mIsLayerOn[i] ? 255 : 0;}  invalidateSelf();}

小结:FadeDrawable的功能也更像是一个“wrapper”,它包含一组Drawable,并操控着这组Drawable。

回到GenericDraweeHierarchy:

/** * A SettableDraweeHierarchy that displays placeholder image until the actual image is set. * If provided, failure image will be used in case of failure (placeholder otherwise). * If provided, retry image will be used in case of failure when retrying is enabled. * If provided, progressbar will be displayed until fully loaded. * Each image can be displayed with a different scale type (or no scaling at all). * Fading between the layers is supported. Rounding is supported. * * <p>* Example hierarchy with a placeholder, retry, failure and the actual image: *  <pre>*  o RootDrawable (top level drawable) // 备注:RootDrawable作为“根”必须存在,上面提到共有5个Drawable,其中actual iamge必须存在,记得上面提到说,它在初始化时,被设为一个ColorDrawable,其他都是可选的。 *  | *  +--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) *  </pre>* * <p>* Note: * <ul>* <li> RootDrawable and FadeDrawable are always created. * <li> All branches except the actual image branch are optional (placeholder, failure, retry, * progress bar). If some branch is not specified it won't be created. Index in FadeDrawable will * still be reserved though. * <li> If overlays and/or backgrounds are specified, they are added to the same fade drawable, and * are always being displayed. * <li> ScaleType and Matrix transformations will be added only if specified. If both are * unspecified, then the branch for that image is attached to FadeDrawable directly. Matrix * transformation is only supported for the actual image, and it is not recommended to be used. * <li> Rounding, if specified, is applied to all layers. Rounded drawable can either wrap * FadeDrawable, or if leaf rounding is specified, each leaf drawable will be rounded separately. * <li> A particular drawable instance should be used by only one DH. If more than one DH is being * built with the same builder, different drawable instances must be specified for each DH. * </ul>*/public class GenericDraweeHierarchy implements SettableDraweeHierarchy {private final Drawable mEmptyActualImageDrawable = new ColorDrawable(Color.TRANSPARENT);  private final Resources mResources;  private RoundingParams mRoundingParams;  private final RootDrawable mTopLevelDrawable; // 必须存在的“根”  private final FadeDrawable mFadeDrawable; // 控制“5”个Image fadein/fadeout  private final ForwardingDrawable mActualImageWrapper; // 真正打算显示的那个Image  private final int mPlaceholderImageIndex; // 备注:各个下标  private final int mProgressBarImageIndex;  private final int mActualImageIndex;  private final int mRetryImageIndex;  private final int mFailureImageIndex;

GenericDraweeHierarchy是一个功能完善的体系了。它包含了不同了Drawable,以及规定如何对这些Drawable进行操作。看它的构造函数:

GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) { // 备注:这是一个Builder模式,GenericDraweeHierarchy根据从builder传递过来的值,完成自身的构造  mResources = builder.getResources();mRoundingParams = builder.getRoundingParams();mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable); // 备注:默认的 Image  int numBackgrounds = (builder.getBackgrounds() != null) ? builder.getBackgrounds().size() : 0;  int numOverlays = (builder.getOverlays() != null) ? builder.getOverlays().size() : 0;numOverlays += (builder.getPressedStateOverlay() != null) ? 1 : 0;// layer indices and countint numLayers = 0;  int backgroundsIndex = numLayers; // 备注:初始化各个下标numLayers += numBackgrounds;mPlaceholderImageIndex = numLayers++;mActualImageIndex = numLayers++;mProgressBarImageIndex = numLayers++;mRetryImageIndex = numLayers++;mFailureImageIndex = numLayers++;  int overlaysIndex = numLayers;numLayers += numOverlays;// array of layersDrawable[] layers = new Drawable[numLayers];  if (numBackgrounds > 0) {int index = 0;    for (Drawable background : builder.getBackgrounds()) {      layers[backgroundsIndex + index++] = buildBranch(background, null); // 备注:null 代表Drawable的scaleType,下同....}  }  layers[mPlaceholderImageIndex] = buildBranch(      builder.getPlaceholderImage(),builder.getPlaceholderImageScaleType());layers[mActualImageIndex] = buildActualImageBranch(      mActualImageWrapper,builder.getActualImageScaleType(),builder.getActualImageFocusPoint(),builder.getActualImageMatrix(),builder.getActualImageColorFilter());layers[mProgressBarImageIndex] = buildBranch(      builder.getProgressBarImage(),builder.getProgressBarImageScaleType());layers[mRetryImageIndex] = buildBranch(      builder.getRetryImage(),builder.getRetryImageScaleType());layers[mFailureImageIndex] = buildBranch(      builder.getFailureImage(),builder.getFailureImageScaleType());  if (numOverlays > 0) {int index = 0;    if (builder.getOverlays() != null) {for (Drawable overlay : builder.getOverlays()) {        layers[overlaysIndex + index++] = buildBranch(overlay, null); // 备注:从这里可以看出,可能会存在上述 5 个Image以外的Drawable}    }if (builder.getPressedStateOverlay() != null) {      layers[overlaysIndex + index] = buildBranch(builder.getPressedStateOverlay(), null);}  }// fade drawable composed of layersmFadeDrawable = new FadeDrawable(layers); // 构造 FadeDrawablemFadeDrawable.setTransitionDuration(builder.getFadeDuration()); // 设置渐变时间// rounded corners drawable (optional)Drawable maybeRoundedDrawable =      WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams); // 圆角Drawable// top-level drawablemTopLevelDrawable = new RootDrawable(maybeRoundedDrawable); // 备注:“根”DrawablemTopLevelDrawable.mutate();resetFade();}

GenericDraweeHierarchyBuilder就是Builder设计模式的完美实现。拥有所有的“属性”,通过不断的set和链式编程,最终调用build(),生成一个GenericDraweeHierarchy实例:

public GenericDraweeHierarchy build() {  validate();  return new GenericDraweeHierarchy(this);}public class RootDrawable extends ForwardingDrawable implements VisibilityAwareDrawable {  @VisibleForTesting  @Nullable  Drawable mControllerOverlay = null;@Nullableprivate VisibilityCallback mVisibilityCallback;

小结:GenericDraweeHierarchy的功能也不复杂。总结起来,它是“管理”“5”+个Drawable的一个类。

总结:

到目前为止,一切都不复杂。hierarchy体系就是把不同的Drawable组织起来,统一管理。其中用到的设计模式还是蛮值得参考的。它相当于MVC中的M,数据都是它“提供”的。

0 0