1. 程式人生 > >Fresco:SimpleDraweeView如何顯示並載入圖片

Fresco:SimpleDraweeView如何顯示並載入圖片

使用Fresco, 如果僅僅是想簡單下載一張網路圖片,在下載完成之前,顯示一張佔位圖,那麼簡單使用 SimpleDraweeView 即可。那麼SimpleDraweeView是如何如何顯示並載入圖片呢?

我們知道在使用Fresco前,需要呼叫

Fresco.initialize(context);

Fresco.initialize做哪些工作

該函式有兩個實現:

/** Initializes Fresco with the default config. */
public static void initialize(Context context) {
  ImagePipelineFactory.initialize(context);
  initializeDrawee(context);
}

/** Initializes Fresco with the specified config. */
public static void initialize(Context context, ImagePipelineConfig imagePipelineConfig) { ImagePipelineFactory.initialize(imagePipelineConfig); initializeDrawee(context); }

第一個無需配置ImagePipelineConfig,這種情況下使用預設配置。會構造一個只包含mContext值的ImagePipelineConfig物件,該物件中其他成員變數為空。

該函式主要做兩件事,第一初始化ImagePipelineFactory,第二初始化Drawee:將使用配置的ImagePipelineConfig(預設的,或使用自定義引數的)構造的ImagePipeline賦值給Fresco的靜態成員變數sDraweeControllerBuilderSupplier 的成員變數mImagePipeline.

private static void initializeDrawee(Context context) {
  sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context);
  SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}

然後使用sDraweeControllerBuilderSupplier初始化SimpleDraweeView。sDraweeControllerBuilderSupplier的型別為:
public class PipelineDraweeControllerBuilderSupplier implements
Supplier

SimpleDraweeView的建構函式

SimpleDraweeView的建構函式會呼叫init()函式,該函式會初始化成員變數mSimpleDraweeControllerBuilder ,型別為:SimpleDraweeControllerBuilder。

private void init() {
  if (isInEditMode()) {
    return;
  }
  Preconditions.checkNotNull(
      sDraweeControllerBuilderSupplier,
      "SimpleDraweeView was not initialized!");
  mSimpleDraweeControllerBuilder = sDraweeControllerBuilderSupplier.get();
}

sDraweeControllerBuilderSupplier.get()返回PipelineDraweeControllerBuilder型別的資料,該類繼承AbstractDraweeControllerBuilder類,後者繼承了SimpleDraweeControllerBuilder介面。

@Override
public PipelineDraweeControllerBuilder get() {
  return new PipelineDraweeControllerBuilder(
      mContext,
      mPipelineDraweeControllerFactory,
      mImagePipeline,
      mBoundControllerListeners);
}

SimpleDraweeView::setImageURI()

我們通過setImageURI為呼叫SimpleDraweeView設定圖片,
該函式分兩部分,第一建立DraweeController ,第二設定Controller.

建立DraweeController

通過呼叫mSimpleDraweeControllerBuilder的build()函式實現,程式碼如下:

/**
 * Displays an image given by the uri.
 *
 * @param uri uri of the image
 * @param callerContext caller context
 */
public void setImageURI(Uri uri, @Nullable Object callerContext) {
  DraweeController controller = mSimpleDraweeControllerBuilder
      .setCallerContext(callerContext)
      .setUri(uri)
      .setOldController(getController())
      .build();
  setController(controller);
}

即呼叫drawee\src\main\java\com\facebook\drawee\controller\AbstractDraweeControllerBuilder.java 的build():

/** Builds the specified controller. */
@Override
public AbstractDraweeController build() {
  validate();

  // if only a low-res request is specified, treat it as a final request.
  if (mImageRequest == null && mMultiImageRequests == null && mLowResImageRequest != null) {
    mImageRequest = mLowResImageRequest;
    mLowResImageRequest = null;
  }

  return buildController();
}
/** Builds a regular controller. */
protected AbstractDraweeController buildController() {
  AbstractDraweeController controller = obtainController();
  controller.setRetainImageOnFailure(getRetainImageOnFailure());
  maybeBuildAndSetRetryManager(controller);
  maybeAttachListeners(controller);
  return controller;
}

obtainController()的實現在PipelineDraweeControllerBuilder.java中:

@Override
protected PipelineDraweeController obtainController() {
  DraweeController oldController = getOldController();
  PipelineDraweeController controller;
  if (oldController instanceof PipelineDraweeController) {
    controller = (PipelineDraweeController) oldController;
    controller.initialize(
        obtainDataSourceSupplier(),
        generateUniqueControllerId(),
        getCallerContext());
  } else {
    controller = mPipelineDraweeControllerFactory.newController(
        obtainDataSourceSupplier(),
        generateUniqueControllerId(),
        getCallerContext());
  }
  return controller;
}

呼叫AbstractDraweeControllerBuilder.java的obtainDataSourceSupplier():

/** Gets the top-level data source supplier to be used by a controller. */
protected Supplier<DataSource<IMAGE>> obtainDataSourceSupplier() {
  if (mDataSourceSupplier != null) {
    return mDataSourceSupplier;
  }

  Supplier<DataSource<IMAGE>> supplier = null;

  // final image supplier;
  if (mImageRequest != null) {
    supplier = getDataSourceSupplierForRequest(mImageRequest);
  } else if (mMultiImageRequests != null) {
    supplier = getFirstAvailableDataSourceSupplier(mMultiImageRequests, mTryCacheOnlyFirst);
  }

  // increasing-quality supplier; highest-quality supplier goes first
  if (supplier != null && mLowResImageRequest != null) {
    List<Supplier<DataSource<IMAGE>>> suppliers = new ArrayList<>(2);
    suppliers.add(supplier);
    suppliers.add(getDataSourceSupplierForRequest(mLowResImageRequest));
    supplier = IncreasingQualityDataSourceSupplier.create(suppliers);
  }

  // no image requests; use null data source supplier
  if (supplier == null) {
    supplier = DataSources.getFailedDataSourceSupplier(NO_REQUEST_EXCEPTION);
  }

  return supplier;
}
/** Creates a data source supplier for the given image request. */
protected Supplier<DataSource<IMAGE>> getDataSourceSupplierForRequest(
    final REQUEST imageRequest,
    final boolean bitmapCacheOnly) {
  final Object callerContext = getCallerContext();
  return new Supplier<DataSource<IMAGE>>() {
    @Override
    public DataSource<IMAGE> get() {
      return getDataSourceForRequest(imageRequest, callerContext, bitmapCacheOnly);
    }
    @Override
    public String toString() {
      return Objects.toStringHelper(this)
          .add("request", imageRequest.toString())
          .toString();
    }
  };
}

最後通過PipelineDraweeControllerBuilder.java的getDataSourceForRequest()返回DataSourceSupplier。該DataSourceSupplier的get()會呼叫getDataSourceForRequest()將獲取到的圖片資料返回,返回資料型別為:DataSource

@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSourceForRequest(
    ImageRequest imageRequest,
    Object callerContext,
    boolean bitmapCacheOnly) {
  if (bitmapCacheOnly) {
    return mImagePipeline.fetchImageFromBitmapCache(imageRequest, callerContext);
  } else {
    return mImagePipeline.fetchDecodedImage(imageRequest, callerContext);
  }
}

設定Controller

接著回到 setImageURI函式中的 setController(controller)的實現:

  /** Sets the controller. */
  public void setController(@Nullable DraweeController draweeController) {
    mDraweeHolder.setController(draweeController);
    super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
  }

呼叫DraweeHolder.java的setController()函式:

 /**
   * Sets a new controller.
   */
  public void setController(@Nullable DraweeController draweeController) {
    boolean wasAttached = mIsControllerAttached;
    if (wasAttached) {
      detachController();
    }

    // Clear the old controller
    if (mController != null) {
      mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);
      mController.setHierarchy(null);
    }
    mController = draweeController;
    if (mController != null) {
      mEventTracker.recordEvent(Event.ON_SET_CONTROLLER);
      mController.setHierarchy(mHierarchy);
    } else {
      mEventTracker.recordEvent(Event.ON_CLEAR_CONTROLLER);
    }

    if (wasAttached) {
      attachController();
    }
  }

該函式主要的功能是呼叫mController.setHierarchy(mHierarchy);關於mHierarchy,是在SimpleDraweeView的父類GenericDraweeView的建構函式中設定的:

   protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
    GenericDraweeHierarchyBuilder builder =
        GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);
    setAspectRatio(builder.getDesiredAspectRatio());
    setHierarchy(builder.build());
  }

根據我們在XML中宣告的SimpleDraweeView的一些屬性建立GenericDraweeHierarchyBuilder,然後呼叫它的build()函式生成GenericDraweeHierarchy:

  /**
   * Inflates a new hierarchy builder from XML.
   * The builder can then be modified in order to override XML attributes if necessary.
   */
  public static GenericDraweeHierarchyBuilder inflateBuilder(
      Context context,
      @Nullable AttributeSet attrs) {
    Resources resources = context.getResources();
    GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(resources);
    return updateBuilder(builder, context, attrs);
  }
  /**
   * Updates the existing hierarchy builder based on the XML attributes.
   *
   * This method is useful if a custom view uses different default values. In that case a
   * builder with adjusted default values can be passed to this method and only the properties
   * explicitly specified in XML will be overridden.
   * The builder can be modified afterwards in case some XML attributes needs to be overridden.
   *
   * @param builder a hierarchy builder to be updated
   * @return the modified instance of the same builder
   */
  public static GenericDraweeHierarchyBuilder updateBuilder(
      GenericDraweeHierarchyBuilder builder,
      Context context,
      @Nullable AttributeSet attrs) {
    // these paramters cannot be applied immediately so we store them first
    int progressBarAutoRotateInterval = 0;
    int roundedCornerRadius = 0;
    boolean roundTopLeft = true;
    boolean roundTopRight = true;
    boolean roundBottomLeft = true;
    boolean roundBottomRight = true;

    if (attrs != null) {
      TypedArray gdhAttrs = context.obtainStyledAttributes(
        attrs,
        R.styleable.GenericDraweeHierarchy);
      try {
        final int indexCount = gdhAttrs.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
          final int attr = gdhAttrs.getIndex(i);
          // most popular ones first
          if (attr == R.styleable.GenericDraweeHierarchy_actualImageScaleType) {
            builder.setActualImageScaleType(getScaleTypeFromXml(gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_placeholderImage) {
            builder.setPlaceholderImage(getDrawable(context, gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_pressedStateOverlayImage) {
            builder.setPressedStateOverlay(getDrawable(context, gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_progressBarImage) {
            builder.setProgressBarImage(getDrawable(context, gdhAttrs, attr));

          // the remaining ones without any particular order
          } else if (attr == R.styleable.GenericDraweeHierarchy_fadeDuration) {
            builder.setFadeDuration(gdhAttrs.getInt(attr, 0));

          } else if (attr == R.styleable.GenericDraweeHierarchy_viewAspectRatio) {
            builder.setDesiredAspectRatio(gdhAttrs.getFloat(attr, 0));

          } else if (attr == R.styleable.GenericDraweeHierarchy_placeholderImageScaleType) {
            builder.setPlaceholderImageScaleType(getScaleTypeFromXml(gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_retryImage) {
            builder.setRetryImage(getDrawable(context, gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_retryImageScaleType) {
            builder.setRetryImageScaleType(getScaleTypeFromXml(gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_failureImage) {
            builder.setFailureImage(getDrawable(context, gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_failureImageScaleType) {
            builder.setFailureImageScaleType(getScaleTypeFromXml(gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_progressBarImageScaleType) {
            builder.setProgressBarImageScaleType(getScaleTypeFromXml(gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_progressBarAutoRotateInterval) {
            progressBarAutoRotateInterval =
                gdhAttrs.getInteger(attr, progressBarAutoRotateInterval);

          } else if (attr == R.styleable.GenericDraweeHierarchy_backgroundImage) {
            builder.setBackground(getDrawable(context, gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_overlayImage) {
            builder.setOverlay(getDrawable(context, gdhAttrs, attr));

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundAsCircle) {
            getRoundingParams(builder).setRoundAsCircle(gdhAttrs.getBoolean(attr, false));

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundedCornerRadius) {
            roundedCornerRadius = gdhAttrs.getDimensionPixelSize(attr, roundedCornerRadius);

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundTopLeft) {
            roundTopLeft = gdhAttrs.getBoolean(attr, roundTopLeft);

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundTopRight) {
            roundTopRight = gdhAttrs.getBoolean(attr, roundTopRight);

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundBottomLeft) {
            roundBottomLeft = gdhAttrs.getBoolean(attr, roundBottomLeft);

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundBottomRight) {
            roundBottomRight = gdhAttrs.getBoolean(attr, roundBottomRight);

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundWithOverlayColor) {
            getRoundingParams(builder).setOverlayColor(gdhAttrs.getColor(attr, 0));

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundingBorderWidth) {
            getRoundingParams(builder).setBorderWidth(gdhAttrs.getDimensionPixelSize(attr, 0));

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundingBorderColor) {
            getRoundingParams(builder).setBorderColor(gdhAttrs.getColor(attr, 0));

          } else if (attr == R.styleable.GenericDraweeHierarchy_roundingBorderPadding) {
            getRoundingParams(builder).setPadding(gdhAttrs.getDimensionPixelSize(attr, 0));

          }
        }
      } finally {
        gdhAttrs.recycle();
      }
    }
 // wrap progress bar if auto-rotating requested
    if (builder.getProgressBarImage() != null && progressBarAutoRotateInterval > 0) {
      builder.setProgressBarImage(
        new AutoRotateDrawable(builder.getProgressBarImage(), progressBarAutoRotateInterval));
    }

    // set rounded corner radii if requested
    if (roundedCornerRadius > 0) {
      getRoundingParams(builder).setCornersRadii(
        roundTopLeft ? roundedCornerRadius : 0,
        roundTopRight ? roundedCornerRadius : 0,
        roundBottomRight ? roundedCornerRadius : 0,
        roundBottomLeft ? roundedCornerRadius : 0);
    }

    return builder;
  }    

再返回setController(controller)中,可以看到呼叫 super.setImageDrawable(mDraweeHolder.getTopLevelDrawable()),即ImageView的setImageDrawable函式,讓圖片在SimpleDraweeView中顯示。

GenericDraweeHierarchy

這是Fresco的特點,每個SimpleDraweeView設定的圖片都是RootDrawable型別的,對應GenericDraweeHierarchy的mTopLevelDrawable成員變數,而GenericDraweeHierarchy是類似下圖的hierachy:

   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)

在該類的建構函式中我們可以看到對上述各層圖片的建立過程:

GenericDraweeHierarchy(GenericDraweeHierarchyBuilder builder) {
    mResources = builder.getResources();
    mRoundingParams = builder.getRoundingParams();

    mActualImageWrapper = new ForwardingDrawable(mEmptyActualImageDrawable);

    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 count
    int numLayers = 0;
    int backgroundsIndex = numLayers;
    numLayers += numBackgrounds;
    mPlaceholderImageIndex = numLayers++;
    mActualImageIndex = numLayers++;
    mProgressBarImageIndex = numLayers++;
    mRetryImageIndex = numLayers++;
    mFailureImageIndex = numLayers++;
    int overlaysIndex = numLayers;
    numLayers += numOverlays;

    // array of layers
    Drawable[] layers = new Drawable[numLayers];
    if (numBackgrounds > 0) {
      int index = 0;
      for (Drawable background : builder.getBackgrounds()) {
        layers[backgroundsIndex + index++] = buildBranch(background, null);
      }
    }
    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);
        }
      }
      if (builder.getPressedStateOverlay() != null) {
        layers[overlaysIndex + 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();
  }

在獲取到圖片後,只需呼叫該類的setImage()函式將圖片設定給mActualImageWrapper即可:

  @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(mActualImageIndex);
    setProgress(progress);
    if (immediate) {
      mFadeDrawable.finishTransitionImmediately();
    }
    mFadeDrawable.endBatchMode();
  }

上述設定圖片的過程是在com/facebook/drawee/controller/AbstractDraweeController.java中定義的:

protected void submitRequest() {
    ...
    final DataSubscriber<T> dataSubscriber =
        new BaseDataSubscriber<T>() {
          @Override
          public void onNewResultImpl(DataSource<T> dataSource) {
            // isFinished must be obtained before image, otherwise we might set intermediate result
            // as final image.
            boolean isFinished = dataSource.isFinished();
            float progress = dataSource.getProgress();
            T image = dataSource.getResult();
            if (image != null) {
              onNewResultInternal(id, dataSource, image, progress, isFinished, wasImmediate);
            } else if (isFinished) {
              onFailureInternal(id, dataSource, new NullPointerException(), /* isFinished */ true);
            }
          }
         ...
  }

  private void onNewResultInternal(
      String id,
      DataSource<T> dataSource,
      @Nullable T image,
      float progress,
      boolean isFinished,
      boolean wasImmediate) {
   ...
    try {
      drawable = createDrawable(image);
    } catch (Exception exception) {
      logMessageAndImage("drawable_failed @ onNewResult", image);
      releaseImage(image);
      onFailureInternal(id, dataSource, exception, isFinished);
      return;
    }
    T previousImage = mFetchedImage;
    Drawable previousDrawable = mDrawable;
    mFetchedImage = image;
    mDrawable = drawable;
    try {
      // set the new image
      if (isFinished) {
        logMessageAndImage("set_final_result @ onNewResult", image);
        mDataSource = null;
        mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);
        getControllerListener().onFinalImageSet(id, getImageInfo(image), getAnimatable());
        // IMPORTANT: do not execute any instance-specific code after this point
...
  }

其中mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);將圖片設定到SimpleDraweeView對應的DraweeHierarchy中。