码迷,mamicode.com
首页 > 其他好文 > 详细

Fresco图片库研读分析

时间:2016-04-29 16:15:35      阅读:638      评论:0      收藏:0      [点我收藏+]

标签:

Fresco是Facebook推出的一款用于Android应用的强大图片库,相对于其他主流的图片库而言,Fresco的主要优秀特性的在于缓存和内存控制上,当然有利就有弊,Fresco的代码量大,方法数较多,阅读难度比较大,下面我们就从三个方面入手来分析Fresco

一、初始化配置

二、图片的加载与缓存

三、图片的绘制显示

关于Fresco中的一些概念和使用方法,见Fresco的官方文档http://fresco-cn.org/docs/index.html


一、初始化配置

使用Fresco的第一步,就是进行初始化,Fresco提供了两种初始化方式,一种是使用默认配置初始化,一种是使用用户自定义配置初始化。就使用而言,默认配置能完全满足我们的需要,但如果想要做更多的控制,那么可以使用自定义的配置。当然,使用自定义配置的前提,是我们要了解Fresco都提供了哪些配置,哪些配置需要我们自定义,哪些可以使用默认的配置。

默认配置初始化:

/** 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参数,此类是主配置类,主要提供了内存缓存配置,未解码的内存缓存配置,磁盘缓存配置,网络请求配置,线程池配置,缓存Key工厂配置,Pool工厂配置,渐进式Jpeg配置等等。也就是说我们在自定义配置的时候,这些都是可以自定义的,只要实现相应的接口及其接口方法即可,具体可参考默认的实现类,这里不对这些配置一一分析。

我们看默认初始化实现:

/** Initializes {@link ImagePipelineFactory} with default config. */
public static void initialize(Context context) {
  initialize(ImagePipelineConfig.newBuilder(context).build());
}

使用builder模式构建出ImagePipelineConfig,然后进行初始化。在ImagePipelineConfig的构造方法中,那些必要的配置,如果是空实现,那么都赋值为默认的实现。因此我们在自定义配置时,不用每个配置都去自定义设置。在自定义配置时,通过ImagePipelineConfig.newBuilder(context)构建出ImagePipelineConfig.Builder类,根据其提供的方法设置自己对应的实现即可。

ImagePipelineConfig初始化完成后,接着调用了initializeDrawee(context); 我们看下这一步是做什么的。

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

调用了SimpleDraweeView的静态初始化方法,其接受一个Supplier<? extends SimpleDraweeControllerBuilder>的参数,这个Supplier是一个接口,仅有一个get()方法,用来提供一个实例对象。在SimpleDraweeView构造方法被调用时,会调用Supplierget()方法,将其返回的实例对象(这里是PipelineDraweeControllerBuilder)保存到SimpleDraweeView的成员变量中供后续使用。

其实initializeDrawee(context)方法提供了一种默认的实现,即PipelineDraweeControllerBuilderFresco同时给我们提供了另一个实现VolleyDraweeControllerBuilder,我们也可以更换为这个,当然更换后的实现细节,也都不太一样了。甚至如果我们愿意,可以自己写一个实现,只要继承AbstractDraweeControllerBuilder抽象类即可,前提是要对后续逻辑流程有足够的了解。

回头来看,初始化过程并不复杂,主要是ImagePipelineConfig的初始化构造和提供SimpleDraweeControllerBuilder的实现类。Fresco类其实就是一个入口封装,方便外部统一调用,我们完全可以不用这个类或者自己写一个封装类去实现自定义的初始化操作。

从初始化的流程,我们可以看到Fresco严格遵循面向对象的设计思想,模块分离,面向接口编程,各种设计模式的使用等都非常值得我们学习借鉴。当我们在做一个库供别人使用时,也应该像这样,定义好接口,提供一些默认的实现,方便使用者自定义自己的实现。


二、图片的加载与缓存

初始化工作做好之后,接下来就是加载显示图片了,这块是重头戏。Fresco与其他图片库最大的不同之处就是它是控件级的,自定义了一个用于显示静态图片或动画图片的视图DraweeView,我们只需要在布局中使用SimpleDraweeView替换ImageView即可,其中SimpleDraweeView在创建的时,其父类GenericDraweeView的构造方法中会构建GenericHierarchy并设置给DraweeHolder,GenericHierarchy是做什么的,我们后面会说明。

加载显示图片非常简单,调用SimpleDraweeView的以下两个方法即可

setImageURI(Uri uri);
setImageURI(Uri uri, @Nullable Object callerContext);
后续就不用我们关心了,Fresco会加载图片并缓存和显示。
我们看这两个方法具体是怎么实现的
/**
 * Displays an image given by the uri.
 *
 * @param uri uri of the image
 * @undeprecate
 */
@Override
public void setImageURI(Uri uri) {
  setImageURI(uri, null);
}

/**
 * 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);
}
setImageURI(Uri uri)ImageView的方法,DraweeView继承自ImageView,据Fresco开发者称后续会直接继承View,所以谨慎使用继承自ImageView的相关方法。setImageURI(Uri uri)调用了两个参数的setImageURI(Uri uri, @Nullable Object callerContext)方法,第二个参数没太大作用,对于统计和日志记录可能会有用。方法中的mSimpleDraweeControllerBuilder,就是第一步初始化时传入的Supplier接口提供的实例。
一图胜千言,看图
技术分享
此图描述了DraweeView、DraweeHierarchy、DraweeController等类之间的关系,一个简单的MVC框架设计。DraweeHolder主要用于分离DraweeHierarchy、DraweeController与DraweeView之间的强耦合。
回到setImageURI(Uri uri, @Nullable Object callerContext)方法,短短几行代码,背后做了很多的事情,我们一点点分析,首先看DraweeController是如何被build出来的。
1. setCallerContext()我们暂不关心,先看setUri(uri)方法,其内部构建了一个不可变的ImageRequest实例对象,并将其赋值给AbstractDraweeControllerBuilder的成员变量,用于后续的数据请求使用;
2. 再查看build()方法,此方法在AbstractDraweeControllerBuilder类中,其最终结果是返回一个DraweeController实例,看代码中的过程:
/** 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类,我们看这个方法的实现:
@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;
}
第一次执行时由于oldController不存在,因此会走else分支,由工场方法newController去创建,会构建出PipelineDraweeController类实例。我们看newController方法接收的三个参数,第一个参数为提供DataSource的Supplier,第二个参数Controller的唯一Id,第三个参数为前文提到的CallerContext,这三个参数将作为的PipelineDraweeController实例对象的成员变量,重点是第一个参数,跟踪obtainDataSourceSupplier()方法,其最终创建如下:
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();
  }
}
此匿名Supplier类的get()方法会返回一个DataSource类的对象实例,我们先记录,看后面在什么时机调用其get()方法。
至此,build()方法已走完,构建出了具体的PipelineDraweeController实例对象。
再次回到SimpleDraweeView的setImageURI(Uri uri, @Nullable Object callerContext)方法,往下该执行setController(controller)方法了,我们看这个方法
/** Sets the controller. */
public void setController(@Nullable DraweeController draweeController) {
  mDraweeHolder.setController(draweeController);
  super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}
内部两行代码,第一行将controller设置给DraweeHolder,第二行设置显示的drawable,这块我们在第三部分图片的绘制显示中进行分析,先分析第一行
/**
 * 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();
  }
}

将draweeController赋值给DraweeHolder的成员变量mController,然后根据是否被Attached,来执行相应的操作。

这里补充一点,DraweeView会根据其当前是否位于屏幕的可视区域来决定图片的加载与否,比如view在滑出屏幕可视区域会停止加载,如果图片正在下载,则会取消。

@Override
protected void onAttachedToWindow() {
  super.onAttachedToWindow();
  mDraweeHolder.onAttach();
}

@Override
protected void onDetachedFromWindow() {
  super.onDetachedFromWindow();
  mDraweeHolder.onDetach();
}

@Override
public void onStartTemporaryDetach() {
  super.onStartTemporaryDetach();
  mDraweeHolder.onDetach();
}

@Override
public void onFinishTemporaryDetach() {
  super.onFinishTemporaryDetach();
  mDraweeHolder.onAttach();
}
当DraweeView位于屏幕可视区域内时,会执行DraweeHolder的onAttach()方法,此方法内部最终调用了DraweeController的onAttach()方法,图片的加载由此正式开始,我们看AbstractDraweeController中的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);
  Preconditions.checkNotNull(mSettableDraweeHierarchy);
  mDeferredReleaser.cancelDeferredRelease(this);
  mIsAttached = true;
  if (!mIsRequestSubmitted) {
    submitRequest();
  }
}

protected void submitRequest() {
  mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT);
  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();
  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);
}
看submitRequest()方法中的第六行mDataSource = getDatasource(),即获取DataSource,还记得上面我们在创建PipelineDraweeController时传入的第一个参数吗,由obtainDataSourceSupplier()方法创建的匿名类Supplier的实例,之前说我们看它的get()方法什么时候调用,OK,就是此刻,调用getDataSource()时调用。
@Override
protected DataSource<CloseableReference<CloseableImage>> getDataSource() {
  return mDataSourceSupplier.get();
}
我们回过头看其get()方法的调用,AbstractDraweeControllerBuilder类中
/** 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中的实现
@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);
  }
}
终于看到ImagePipeline了,ImagePipeline是发起请求以及缓存的清理与查询的入口在ImagePipeline中创建了DataSource,我们看else分支,不限定只从cache中读取,ImagePipeline的fetchDecodedImage方法
/**
* Submits a request for execution and returns a DataSource representing the pending decoded
* image(s).
* <p>The returned DataSource must be closed once the client has finished with it.
* @param imageRequest the request to submit
* @return a DataSource representing the pending decoded image(s)
*/
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
    ImageRequest imageRequest,
    Object callerContext) {
  try {
    Producer<CloseableReference<CloseableImage>> producerSequence =
        mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
    return submitFetchRequest(
        producerSequence,
        imageRequest,
        ImageRequest.RequestLevel.FULL_FETCH,
        callerContext);
  } catch (Exception exception) {
    return DataSources.immediateFailedDataSource(exception);
  }
}

private <T> DataSource<CloseableReference<T>> submitFetchRequest(
    Producer<CloseableReference<T>> producerSequence,
    ImageRequest imageRequest,
    ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
    Object callerContext) {
  try {
    ImageRequest.RequestLevel lowestPermittedRequestLevel =
        ImageRequest.RequestLevel.getMax(
            imageRequest.getLowestPermittedRequestLevel(),
            lowestPermittedRequestLevelOnSubmit);
    SettableProducerContext settableProducerContext = new SettableProducerContext(
        imageRequest,
        generateUniqueFutureId(),
        mRequestListener,
        callerContext,
        lowestPermittedRequestLevel,
      /* isPrefetch */ false,
        imageRequest.getProgressiveRenderingEnabled() ||
            !UriUtil.isNetworkUri(imageRequest.getSourceUri()),
        imageRequest.getPriority());
    return CloseableProducerToDataSourceAdapter.create(
        producerSequence,
        settableProducerContext,
        mRequestListener);
  } catch (Exception exception) {
    return DataSources.immediateFailedDataSource(exception);
  }
}
最后的return通过CloseableProducerToDataSourceAdapter类的静态方法创建DataSource的具体实例类,其中一个重要的参数,producerSequence,在前一个方法中创建并传递过来。为什么说这个参数重要,我们稍后再说。
代码回到AbstractDraweeController类的submitRequest()方法中,getDataSource()已完成,再往下看,创建了一个DataSubscriber的类实例对象,然后DataSource通过subscribe方法将DataSubscriber实例对象传入,即数据的订阅,当数据发生改变时会通知给订阅者,好了,以上的所有流程我们可以用图来表示下
技术分享

总结下这个流程:

1. 当调用SimpleDraweeView的setImageUri(uri)方法时,PipelineDraweeControllerBuilder通过setUri(uri)生成不可变的ImageReqeust成员对象,通过build()方法构建PipelineDraweeController实例对象(创建的Supplier<DataSource<IMAGE>>作为其成员变量),然后将controller设置给DraweeHolder;

2. 当View处于屏幕的可视区域时,会执行controller的onAttach()方法,获取DataSource并绑定订阅者DataSubscriber。


整理下思路回来,我们接着分析,到目前为止还没看到时怎么去加载图片的呀,现在我们去看如何去加载图片,记得我们前面提到的在创建DataSource时传入的一个重要参数producerSequence吗,加载图片的关键流程要登场了。Fresco使用了producer(生产者) — consumer(消费者)模式加载和缓存图片的,producerSequence看名字就知道它应该持有了一系列生产者的序列,其内部代码我们在这不做分析,它内部采用了包装模式,一个producer包装另一个producer,类似于Java中的InputStream,调用关系也是一级一级的调用,consumer是在外面创建,并传递给producer的produceResults方法,在某个producer中如果consumer需要包装,那么包装一层再传递给下一个producer的produceResults方法。

Producer的包装过程,主要涉及两个类,ProducerSequenceFactory,ProducerFactory,前者用来获取经过包装的producer,后者获取具体的单个producer。

producer--生产者,负责生产获取结果,从内存,未解码内存,磁盘,网络等获取数据

consumer–消费者,负责消费使用结果,解码,变换,返回。存到各个缓存中。

前面在创建DataSource时使用了CloseableProducerToDataSourceAdapter.create()的静态方法,创建了一个CloseableProducerToDataSourceAdapter的实例对象(实现了DataSource接口),我们看其父类AbstractProducerToDataSourceAdapter的构造方法

protected AbstractProducerToDataSourceAdapter(
    Producer<T> producer,
    SettableProducerContext settableProducerContext,
    RequestListener requestListener) {
  mSettableProducerContext = settableProducerContext;
  mRequestListener = requestListener;
  mRequestListener.onRequestStart(
      settableProducerContext.getImageRequest(),
      mSettableProducerContext.getCallerContext(),
      mSettableProducerContext.getId(),
      mSettableProducerContext.isPrefetch());
  producer.produceResults(createConsumer(), settableProducerContext);
}
在构造方法内部,producer已经开始工作了,一层一层的执行;consumer为内部创建的一个匿名类,作为根的consumer,在得到最终结果后,通知数据订阅者,将数据交给订阅者处理(UI线程执行),一个完整图片获取过程所经历的producer顺序如下:

1. BitmapMemoryCacheGetProducer    只读内存缓存的producer

2. ThreadHandoffProducer    启动线程的producer,后续的producer都在线程中执行

3. BitmapMemoryCacheKeyMultiplexProducer    使用memory cache key合并请求的producer

4. BitmapMemoryCacheProducer    读取内存缓存的producer

5. DecodeProducer    解码图片的producer,渐进式JPEG图片,gifwebp等动画图片的解码

6. ResizeAndRotateProducer    JPEG图片resizesrotates处理

7. AddImageTransformMetaDataProducer    主要包装解码的consumer,并传递到下一个producer

8. EncodedCacheKeyMultiplexProducer    使用encoded cache key合并请求的producer

9. EncodedMemoryCacheProducer    读取未解码的内存缓存的producer

10. DiskCacheProducer    读取磁盘缓存的producer

11. WebpTranscodeProducer    包装转码WebPJPEG/PNGconsumer,并传递到下一个producer

12. NetworkFetchProducer    网络请求的producer

整个图片的加载流程如下:
1. 检查内存缓存,如有,返回
2. 后台线程开始后续工作
3. 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
4. 检查是否在文件缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
5. 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。

每个过程的具体细节不在这里分析了,有兴趣的话仔细研读。

回到AbstractDraweeController类的submitRequest()方法,数据订阅者DataSubscriber在收到正常的数据结果提醒时,如下处理:

private void onNewResultInternal(
    String id,
    DataSource<T> dataSource,
    @Nullable T image,
    float progress,
    boolean isFinished,
    boolean wasImmediate) {
  // ignore late callbacks (data source that returned the new result is not the one we expected)
  if (!isExpectedDataSource(id, dataSource)) {
    logMessageAndImage("ignore_old_datasource @ onNewResult", image);
    releaseImage(image);
    dataSource.close();
    return;
  }
  mEventTracker.recordEvent(
      isFinished ? Event.ON_DATASOURCE_RESULT : Event.ON_DATASOURCE_RESULT_INT);
  // create drawable
  Drawable drawable;
  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
    } else {
      logMessageAndImage("set_intermediate_result @ onNewResult", image);
      mSettableDraweeHierarchy.setImage(drawable, progress, wasImmediate);
      getControllerListener().onIntermediateImageSet(id, getImageInfo(image));
      // IMPORTANT: do not execute any instance-specific code after this point
    }
  } finally {
    if (previousDrawable != null && previousDrawable != drawable) {
      releaseDrawable(previousDrawable);
    }
    if (previousImage != null && previousImage != image) {
      logMessageAndImage("release_previous_result @ onNewResult", previousImage);
      releaseImage(previousImage);
    }
  }
}
创建Drawable,并将其设置给DraweeHierarchy,我们看Drawable时如何创建的,类PipelineDraweeController中

@Override
protected Drawable createDrawable(CloseableReference<CloseableImage> image) {
  Preconditions.checkState(CloseableReference.isValid(image));
  CloseableImage closeableImage = image.get();
  if (closeableImage instanceof CloseableStaticBitmap) {
    CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage;
    BitmapDrawable bitmapDrawable = new BitmapDrawable(
        mResources,
        closeableStaticBitmap.getUnderlyingBitmap());
    if (closeableStaticBitmap.getRotationAngle() == 0 ||
        closeableStaticBitmap.getRotationAngle() == EncodedImage.UNKNOWN_ROTATION_ANGLE) {
      return bitmapDrawable;
    } else {
      return new OrientedDrawable(bitmapDrawable, closeableStaticBitmap.getRotationAngle());
    }
  } else if (closeableImage instanceof CloseableAnimatedImage) {
    return mAnimatedDrawableFactory.create(
        ((CloseableAnimatedImage) closeableImage).getImageResult());
  } else {
    throw new UnsupportedOperationException("Unrecognized image class: " + closeableImage);
  }
}

如果是静态图片则创建BitmapDrawable,否则如果是动画图片,则创建包含多帧AnimatedDrawable。

最后将Drawable设置给DraweeHierarchy,由DraweeHierarchy负责Drawable的绘制工作。


三、图片的绘制显示

终于到图片的绘制了,接着上一节,AbstractDraweeController将Drawable设置给SettableDraweeHierarchy,其唯一实现类为GenericDraweeHierarchy,我们看它的setImage方法

@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();
}
直接看这个方法会比较迷茫,根本不知道drawable究竟是怎么被绘制的,我们先分析下GenericDraweeHierarchy的构造方法
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();
}
看着挺多,不过并不复杂,mActualImageWrapper看名字应该是最终实际展示的drawable,事实确实如此,然后创建了一个Drawable数组,添加了包括mActualImageWrapper在内的各种Drawable,有加载时占位用的drawable、加载失败显示的drawable、点击重试的drawable等,然后mFadeDrawable创建时传入了一个Drawable数组,mTopLevelDrawable在创建时又间接(圆角Drawable的包装)或直接引用了mFadeDrawable。

还记得GenericDraweeHierarchy是在GenericDraweeView中被build出来并设置给DraweeHolder的吗,我们看下其setHierarchy(hierarchy)方法

/** Sets the hierarchy. */
public void setHierarchy(DH hierarchy) {
  mDraweeHolder.setHierarchy(hierarchy);
  super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());
}
看到方法里有个getTopLevelDrawable()方法,没错,这个方法返回的就是GenericDraweeHierarchy中的mTopLevelDrawable,将此drawable设置给了ImageView,此时,mTopLevelDrawable与ImageView建立了联系,mTopLevelDrawable的callback设置给了ImageView,我们看下Android的ImageView源码
/**
 * Sets a drawable as the content of this ImageView.
 * 
 * @param drawable the Drawable to set, or {@code null} to clear the
 *                 content
 */
public void setImageDrawable(@Nullable Drawable drawable) {
    if (mDrawable != drawable) {
        mResource = 0;
        mUri = null;

        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;

        updateDrawable(drawable);

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
}
 
private void updateDrawable(Drawable d) {
    if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
        mRecycleableBitmapDrawable.setBitmap(null);
    }

    if (mDrawable != null) {
        mDrawable.setCallback(null);
        unscheduleDrawable(mDrawable);
    }

    mDrawable = d;

    if (d != null) {
        d.setCallback(this);
        d.setLayoutDirection(getLayoutDirection());
        if (d.isStateful()) {
            d.setState(getDrawableState());
        }
        d.setVisible(getVisibility() == VISIBLE, true);
        d.setLevel(mLevel);
        mDrawableWidth = d.getIntrinsicWidth();
        mDrawableHeight = d.getIntrinsicHeight();
        applyImageTint();
        applyColorMod();

        configureBounds();
    } else {
        mDrawableWidth = mDrawableHeight = -1;
    }
}
在updateDrawable(d)方法中将drawable的callback设置为了this,而mTopLevelDrawable在创建时,其父类ForwardingDrawable在构造方法中,将传递过来的参数Drawable(可能为mFadeDrawable也可能为maybeRoundedDrawable)赋值给其成员变量,并将其callback设置为了this

public ForwardingDrawable(Drawable drawable) {
  mCurrentDelegate = drawable;
  DrawableUtils.setCallbacks(mCurrentDelegate, this, this);
}

mFadeDrawable创建时传入了drawable的数组,其父类ArrayDrawable的构造方法中,对数组中的每个drawable的callback设置为this

public ArrayDrawable(Drawable[] layers) {
  Preconditions.checkNotNull(layers);
  mLayers = layers;
  for (int i = 0; i < mLayers.length; i++) {
    DrawableUtils.setCallbacks(mLayers[i], this, this);
  }
  mDrawableParents = new DrawableParent[mLayers.length];
}

看下图来了解类之间关系和callback链

技术分享

也就是说ArrayDrawable中mLayers的Drawable数组中的每个drawable都间接与ImageView建立了联系,其callback的调用关系为的责任链模式,一级一级往上层调用。

看到这里我们在回到本节的开始出,GenericDraweeHierarchy的setImage方法,mActualImageWrapper为实际要绘制的图片,其位于mFadeDrawable的mLayers数组中,fadeInLayer(mActualImageIndex)设置要绘制的Drawable的索引,fendBatchMode()方法中调用了invalidateSelf(),此方法触发调用callback,然后callback链一级一级网上调用直到ImageView,ImageView最终调用onDraw方法,交由Drawable进行绘制,最终会调用到FadeDrawable的draw(canvas)方法,在其方法内部绘制mLayers数组中被标记的Drawable,至此drawable将被绘制到canvas上在屏幕上呈现。如果最终要绘制的Drawable为AnimatedDrawable(gif,webP等动画图片),那么调用其draw(canvas)方法将不断地绘制每一帧。


一些补充:

1、Pool模型

Fresco中有个Pool和Bucket的概念,可理解为缓存池,类似与线程池,即当需要分配一块内存的时候,先从Pool中取,如果有相应大小Bucket,则返回,没有的话看是否满足条件可分配,如果满足则分配对应大小的内存并返回,否则抛出异常。当得到对应的Bucket后获取其FreeList中块大小的值,如果没有,则新分配内存并使用,使用完之后返回到Bucket的FreeList中。

Pool是在初始化阶段完成配置,包括有几个Pool,每个Pool大小的软限制,硬限制,和包含多少个Bucket,每个Bucket的itemSize大小以及item的个数等,看下图来理解

技术分享

2、Image Decoder

Fresco中图片的解码操作根据不同的系统版本而有所不同,在4.4以下的版本中(sdk<19)解码时先将数据copy到Ashmem区域,然后进行解码;4.4版本由于使用MemoryFile不能work,而使用java内存来存储未解码image,使用BitmapFactory.decodeByteArray方法解码数据,不能从stream解码,因为不支持purgeable;5.0及以上版本使用BitmapFactory.decodeStream方法解码。

技术分享

3、引用计数CloseableReference

Fresco中使用了一种引用计数的方式来管理内存,主要为了解决Ashmem区域的Bitmap内存的分配管理

技术分享

以下内容摘自网上:

Ashmem:

Android系统里面,Ashmem这个区域的内存并不属于Java Heap,也不属于Native Heap。而Ashmem的使用,又有一点像Java的垃圾回收。

Ashmem中的某个内存空间像要被释放时候,会通过系统调用unpin来告知,但实际上这块内存空间的数据并没有被真正的擦除。

如果Android系统发现内存吃紧时,就会把unpin的内存空间利用起来去存储所需的数据。

而被unpin的内存空间,是可以被重新pin的,如果此时的该内存空间还没有被其他人使用的话,就节省了重新往Ashmem重新写入数据的过程了。

所以Ashmem这个工作原理是一种延迟释放。

 

BitmapAshmem中的使用

Ashmem内存区域是不能被Java应用直接使用的,但这其中有一些例外,而Bitmap是其中一个。

BitmapFactory.Options = new BitmapFactory.Options();

options.inPurgeable = true;

Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

Purgeable被设置成true以后,这个Bigmap就是保存在Ashmem内存区域中的,Java的垃圾回收是不能回收这片区域的内存的。

Android系统需要渲染这个Bitmap的时候,会调用pin,渲染完成后会调用unpin。而unpin后的内存空间表示能被其他人所使用。

如果被unpinBitmap需要重新渲染,系统会再次Decode这个Bitmap,而这个Decode的过程是在UI线程上完成的,Bitmap decode是非常消耗CPU资源的,当消耗过大时会引起UI阻塞,所以Google后来废弃了这个purgeable的参数。

后来Google提供了另外一个Flag,叫inBitmap。很遗憾的是,直到Android4.4后,这个新的Flag才得到完善。而Fresco致力于实现一个包括Android2.3以及以上的Android系统都能完美工作的图片加载管理开源库,因此Fresco放弃了使用inBitmap的解决方案。

 

Fresco是如何利用Ashmem去给Bitmap分配和管理内存?

上面说到的pinunpin两个操作,对应的NDK调用是AndroidBitmap_lockPixelsunlockPixels。按照我们一惯认知,为了避免内存泄漏,这两者必须成对出现。而Fresco为了避免Bitmap再次渲染而导致的在UI线程Decode的过程,偏偏不在渲染完成后调用unlockPixels

这就需要Fresco自己去管理这块内存区域,保证当这个Bitmap不再使用时,Ashmem的内存空间能被unpin

Fresco选择在Bitmap离开屏幕可视范围时候(onDetachWindow等时候),去做unpin




Fresco图片库研读分析

标签:

原文地址:http://blog.csdn.net/lpw14/article/details/51260153

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!