Fresco源代码分析之二:SimpleDraweeView如何拉取图片并绘制在屏幕上?

Fresco源代码分析之二:SimpleDraweeView如何拉取图片并绘制在屏幕上?

引言

《Fresco源代码分析之1:Fresco的初始化 》文章中,仔细分析了Fresco.init(Context)方法中所做的工作。总结下来有一下几点:

  1. 初始化了 ImagePipelineFactory , 包括默认的ImagePipelineConfig(其中初始化了各种线程池、是否解码、旋转之类的设置、各种Cache的默认配置等等)。
  2. 初始化了默认的 SimpleDraweeView ,包括对应的ImagePipeline。

那么问题来了,当我们通过SimpleDraweeView的

setImageUri(Uri, Object)

方法调用的时候,Fresco到底做了哪些工作呢?图片是经过了哪些工作绘制在界面上的呢?这篇文章就来说说这些事情。

调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
 \* 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);
}

在想象中,图片库要将图片显示出来,至少要经过查询缓存、网络获取、图片的解码、写入缓存、图片的后处理、显示图片这几个过程。但查看Fresco的代码,发现仅仅这几行,两句话就完事了,那么这两句话后面,到底有哪些故事发生呢?

源代码分析

从上述代码出发,分析图片加载的全过程。

获取DraweeController

代码的第一行,典型的是一个获取DraweeController的方法,具体的工作是怎么样的呢?

DraweeController controller = mSimpleDraweeControllerBuilder
    .setCallerContext(callerContext)
    .setUri(uri)
    .setOldController(getController())
    .build();

其中mSimpleDraweeControllerBuilder是一个SimpleDraweeControllerBuilder的接口,该方法以此调用了四个方法,分别设置了 调用者的上下文Uri旧的Controller。从接口上看不出来Fresco具体做了什么工组的,因此还是要看看具体的实现。 在Fresco的初始化代码中,是这样初始化的

// 初始化Drawee
private static void initializeDrawee(Context context) {
  sDraweeControllerBuilderSupplier = new PipelineDraweeControllerBuilderSupplier(context);
  SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
}

因此可以看出来,SimpleDraweeView所对应的Controller实际上是源自PipelineDraweeControllerBuilder,进一步查看,可以发现继承关系如下:

SimpleDraweeControllerBuilder
|
|
AbstractDraweeControllerBuilder
|
|
PipelineDraweeControllerBuilder

既然在SimpleDraweeView.setUri()方法中只调用了setCallerContext\setUri\setOldController,那么我们就从这四个方法入手

setCallerContext(Object callerContext)

这个方法源自 AbstractDraweeControllerBuilder

/** Sets the caller context. */
@Override
public BUILDER setCallerContext(Object callerContext) {
  mCallerContext = callerContext;
  return getThis();
}

和字面意思一样,该方法就是简单的设置了上下文,而且需要注意的是,上下文的类型为Object

setUri(Uri uri)

该方法首先调用 PipelineDraweeControllerBuilder 的对应的方法

@Override
public PipelineDraweeControllerBuilder setUri(Uri uri) {
  return super.setImageRequest(ImageRequest.fromUri(uri));
}

其实本质上还是调用父类的这个方法,但需要注意的是,在这里面使用ImageRequest.fromUri(Uri)又将方法包装了一下,将Uri包装为ImageRequest的类型。ImageRequest的类的注释如下:

Immutable object encapsulating everything pipeline has to know about requested image to proceed.

可以直接看出,在Fresco中,ImageRequest为一个不可变类,其中包含了一个请求所需要的全部信息。 而调用至 AbstractDraweeControllerBuilder 中对应的方法也很简单,就是设置了一下对应的成员变量

/** Sets the image request. */
public BUILDER setImageRequest(REQUEST imageRequest) {
  mImageRequest = imageRequest;
  return getThis();
}

setOldController(DraweeController)

此处也是调用的 AbstractDraweeControllerBuilder中的方法,具体代码如下:

/** Sets the old controller to be reused if possible. */
@Override
public BUILDER setOldController(@Nullable DraweeController oldController) {
  mOldController = oldController;
  return getThis();
}

工作也很简单,就是同样是设置了成员变量,那么这样看来,所有的工作应该都是放在了build()方法里面,现在去看看。

build()

build方法也是直接调用的 AbstractDraweeControllerBuilder中的方法,

/** 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();
}

这里面有一个判断 仅仅在ImageRequest以及MultiImageRequest都为空,且LowResImagaeRequest(低质量请求)存在的时候,才将这个低质量的请求作为最终的请求来处理。buildController() 方法做了什么呢?代码如下:

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

可以看出来,分了4个步骤:

  1. 获取对应的Controller
  2. 配置Controller是否显示失败重新获取的图片
  3. 设置重试管理器(RetryManager)
  4. 设置Attach事件监听者

要想搞明白了,还是一件一件事情依次看看做了什么。 AbstractDraweeController controller = obtainController(); AbstractDraweeControllerBuilder中并没有obtainController()的默认实现,并要求子类实现,在PipelineDraweeControllerBuilder中,其实现如下:

  /**
   * 该方法会尝试复用以前的Controller,
   * 复用的办法是:
   * 1\首先判断Controller的类型,如果是PipelineDraweeController,则初始化即可
   * 2\如果不是如果是PipelineDraweeController的类型,则新建一个.
   * @return
   */

@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;
}

其实代码也很简单,就是检查之前通过setOldController设置的Controller是否为自身的类型,如果是,则通过设置重新使用,如果不是,则通过对应的工厂方法重新建立一个。 当然,我们不能看到这边就可以了,既然Controller可以复用,我们就看看重用Controller的初始化代码中都做了什么工作?

/**
 * 通过新配置的Id和调用者上下文,重新初始化AbstractDraweeController
 * 这种设计允许在不需要重新实例化controller的时候复用之前的Controller.
 *
 * Initializes this controller with the new id and caller context.
 * This allows for reusing of the existing controller instead of instantiating a new one.
 * This method should be called when the controller is in detached state.
 * @param id unique id for this controller
 * @param callerContext tag and context for this controller
 */
protected void initialize(String id, Object callerContext) {
  init(id, callerContext);
}

private void init(String id, Object callerContext) {
  // 记录事件:开始初始化Controller
  mEventTracker.recordEvent(Event.ON_INIT_CONTROLLER);
  // 如果之前有DeferredReleaser,则释放
  // cancel deferred release
  if (mDeferredReleaser != null) {
    mDeferredReleaser.cancelDeferredRelease(this);
  }
  // 重新初始化各种状态
  // reinitialize mutable state (fetch state)
  mIsAttached = false;
  releaseFetch();
  mRetainImageOnFailure = false;
  // 重新初始化RetryManager
  // reinitialize optional components
  if (mRetryManager != null) {
    mRetryManager.init();
  }
  // 重新初始化GestureDetector
  if (mGestureDetector != null) {
    mGestureDetector.init();
    mGestureDetector.setClickListener(this);
  }
  // 清空ControllerListener
  if (mControllerListener instanceof InternalForwardingListener) {
    ((InternalForwardingListener) mControllerListener).clearListeners();
  } else {
    mControllerListener = null;
  }
  // 清空Drawee视图
  // clear hierarchy and controller overlay
  if (mSettableDraweeHierarchy != null) {
    mSettableDraweeHierarchy.reset();
    mSettableDraweeHierarchy.setControllerOverlay(null);
    mSettableDraweeHierarchy = null;
  }
  mControllerOverlay = null;
  // reinitialize constant state
  if (FLog.isLoggable(FLog.VERBOSE)) {
    FLog.v(TAG, "controller %x %s -> %s: initialize", System.identityHashCode(this), mId, id);
  }
  mId = id;
  mCallerContext = callerContext;
}

上面的代码就是Controller初始化或者说重新初始化过程,所调用的代码,具体都有哪些工作呢?依次来看:

  1. 记录事件:初始化Controller
  2. 如果之前调用了DeferredReleaser,则取消。
  3. 设置变量 mISAttached = false; 释放之前的网络获取。
  4. 重新配置RetryManager.
  5. 重新设置触摸事件检测
  6. 配置MControllerListener,若为内置转发Listener,则保留,反之清空。
  7. 清空对应的Drawee视图。
  8. 设置Id和上下文。

setController(DraweeController)

setController(DraweeController)的代码如下:

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

这一块代码也很简单,两句话:

  1. 为mDraweeHoler设置了Controller
  2. 调用了ImageView的setImageDrawable(Drawable)方法,设置了一张图片。

其实第2步代码的作用非常容易理解:就是在网络数据拉取成功之前,先设置一张默认的图片,这样在交互的时候,界面更加友好。 那么这样看来,网络请求发出等工作都是在第1步代码当中了。

/**
 * 配置新的Controller
 * Sets a new controller.
 */
public void setController(@Nullable DraweeController draweeController) {
  // 首先判断该DraweeHolder是否已经与某个Controller绑定.(按照Controller方法的调用行为,此处更应该表述的是,该图像已经需要Controller加载过)
  boolean wasAttached = mIsControllerAttached;
  // 如果之前绑定过,则首先解除绑定.
  if (wasAttached) {
    detachController();
  }

  // Clear the old controller, 重置之前的Controller
  if (mController != null) {
    mEventTracker.recordEvent(Event.ON_CLEAR_OLD_CONTROLLER);
    mController.setHierarchy(null);
  }
  // 将新的Controller赋值给成员变量.
  mController = draweeController;
  // 记录事件,配置视图
  if (mController != null) {
    mEventTracker.recordEvent(Event.ON_SET_CONTROLLER);
    mController.setHierarchy(mHierarchy);
  } else {
    mEventTracker.recordEvent(Event.ON_CLEAR_CONTROLLER);
  }

  // 如果之前的Controller曾经绑定,则进一步调用attachController
  if (wasAttached) {
    attachController();
  }
}

setController 方法着实也非常简单,可以看出其实主要就是几个工作:

  1. 首先检查该DraweeHolder是否之前绑定过Controller,如果有,则调用detachController()方法释放之前的Controller。
  2. 清空之前的Controller,并将新的controller对象赋给mController。
  3. 记录事件,为新的controller配置视图。
  4. 如果该DraweeHolder之前绑定过Controller,则调用attachController()方法直接拉起下一步进程。

需要注意的是,我们在代码分析中可以发现,其实我们在setController的时候,Fresco并不一定会直接发出网络请求,那么Fresco是什么时候进行的呢?分为两种情况:

  1. 如果DraweeHoler之前有通过Controller加载过图片,则直接通过新的controller重新加载。
  2. 如果没有,则等待时机执行。那什么时候呢?接着分析一下。

图片的加载

其实很简单,Controller中有个方法叫onAttach(),这个名字是不是很熟悉?没错,和View的onAttach()方法签名一样,同样的,则个方法也是监听DraweeHolder的onAttach()回调函数,来看看。 ViewHolder

/**
 * Gets the controller ready to display the image.
 *
 * <p>The containing view must call this method from both {@link View#onFinishTemporaryDetach()}
 * and {@link View#onAttachedToWindow()}.
 */
public void onAttach() {
  mEventTracker.recordEvent(Event.ON_HOLDER_ATTACH);
  mIsHolderAttached = true;
  attachOrDetachController();
}

代码很简单,就是记录事件,然后设置变量,然后调用下面一个方法,接着看。

private void attachOrDetachController() {
  if (mIsHolderAttached && mIsVisible && mIsActivityStarted) {
    attachController();
  } else {
    detachController();
  }
}

这个也不用多说,就是检查DraweeHolder是否已经已经在屏幕上了,是否是显示的,Activity是否已经启动,如果是,则直接attachController()。 AbastarctDraweeController 接下来就看看Controller的onAttach()方法了

@Override
public void onAttach() {
  if (FLog.isLoggable(FLog.VERBOSE)) {
    FLog.v(
        TAG,
        "controller %x %s: onAttach: %s",
        System.identityHashCode(this),
        mId,
        mIsRequestSubmitted ? "request already submitted" : "request needs submit");
  }
  // 记录事件
  mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);
  // 检查mSettingableDraweeHierarchy是否为null
  Preconditions.checkNotNull(mSettableDraweeHierarchy);
  // 释放DeferredReleaser
  mDeferredReleaser.cancelDeferredRelease(this);
  // 设置mISAttached
  mIsAttached = true;
  // 如果任务没有提交,则提交
  if (!mIsRequestSubmitted) {
    submitRequest();
  }
}

代码也很简单,无需多说,任务就是在这里被提交的。

// 提交请求
protected void submitRequest() {
  // 记录事件
  mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT);
  // 向Controller Listener 提交对应事件
  getControllerListener().onSubmit(mId, mCallerContext);
  // 设置进度,设置不显示进度
  mSettableDraweeHierarchy.setProgress(0, true);
  // 设置相关变量
  mIsRequestSubmitted = true;
  mHasFetchFailed = false;
  mDataSource = getDataSource();
  if (FLog.isLoggable(FLog.VERBOSE)) {
    FLog.v(
        TAG,
        "controller %x %s: submitRequest: dataSource: %x",
        System.identityHashCode(this),
        mId,
        System.identityHashCode(mDataSource));
  }
  final String id = mId;
  final boolean wasImmediate = mDataSource.hasResult();
  // 添加DataSubscriber
  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);
          }
        }
        @Override
        public void onFailureImpl(DataSource<T> dataSource) {
          onFailureInternal(id, dataSource, dataSource.getFailureCause(), /* isFinished */ true);
        }
        @Override
        public void onProgressUpdate(DataSource<T> dataSource) {
          boolean isFinished = dataSource.isFinished();
          float progress = dataSource.getProgress();
          onProgressUpdateInternal(id, dataSource, progress, isFinished);
        }
      };
  mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor);
}

提交请求的代码我也加了注释,但这个地方涉及到整个数据流的分析,后面接着讲,暂时就看到这里。