理解Fresco的设计原理

Facebook的Fresco图片框架性能优化最为出名,代码量也最大,拆出来的jar包也多,最基础的功能(gradle引入compile 'com.facebook.fresco:fresco:1.1.0')就包括drawee、fbcore、fresco、imagepipeline、imagepipeline-base5个jar包。
但我们看这5个jar包,其实功能界限很清晰:
drawee提供了功能丰富的图片控件;
fbcore和fresco两个提供了基础功能(各种通用基础工具类)和Fresco初始化功能;
imagepipeline的两个包提供了获取、加载、缓存和解码等功能。

所以如果不从jar包出发,而是从功能出发的话,我们就可以考虑从初始化、管理和控件这三个角度,来看待Fresco的整体结构:

一、整体结构

1.1 Fresco初始化

主要就是Fresco这个类的初始化操作,使用Fresco必须先执行初始化函数:

//Fresco源码
    if (imagePipelineConfig == null) {
      ImagePipelineFactory.initialize(context);
    } else {
      ImagePipelineFactory.initialize(imagePipelineConfig);
    }
    initializeDrawee(context, draweeConfig);

我们看到,其实就是ImagePipeline和Drawee的初始化,这两者都可以输入Config对象,对初始化过程进行配置。
由于可配置参数非常复杂,Fresco都是采用了工厂+建造者的模式实现的这部分功能,逻辑划分地比较清晰。
ImagePipelineConfig
ImagePipelineConfig主要用来配置如何获取图像对象。
Fresco使用ImagePipelineFactory生产ImagePipeline,而创建ImagePipelineFactory时可以使用自定义的ImagePipelineConfig对象,这个Config对象定义了图片下载、管理和获取的各种策略,如访问网络的NetworkFetcher,缓冲池PoolFactory等。
DraweeConfig
DraweeConfig主要用来配置如何生成DraweeController。
Fresco自己定义了一套功能强大的图片控件DraweeView,这个控件的主要实例是SimpleDraweeView,Drawee的初始化就是SimpleDraweeView的初始化。
但是Drawee是一个MVC格式,SimpleDraweeView是View,必须有对应的Controller,这个Controller需要使用PipelineDraweeControllerFactory工厂类来生产,DraweeConfig就定义了这个工厂。

通过这两个Config对象,Fresco完成图片管理模块和图片显示模块的初始化,为后续使用做好准备。

1.2 图片管理

图片对象在Android系统中始终是个重点问题,占用内存偏大,IO时间较长,解码耗费CPU等问题始终在困扰Android的App开发。
一般来说,解决思路就是缩小尺寸、多做缓存、少做解码等。

具体来说,Fresco用ImagePipeline就是负责图片下载、解码、缓存与回收等功能,实现了四级图片管理:
已解码图片内存--未解码图片内存--本地图片文件--在线图片。
其中,前三级都是缓存,而且有两级内存缓存,其中第一级已解码图片的内存缓存是比较特别的设计,采用已解码图片缓存,可以减少重复decode对CPU的销毁,避免因反复解码造成UI卡顿等现象。
通过缓存机制,可以缓解内存和IO问题,已解码图片的缓存可以缓解CPU问题。

1.3 图片控件

在App中,成熟的图片显示功能其实很复杂,比如,考虑到在线图片的不可靠性,需要显示错误图片和加载过程的中间图片;可能需要淡入淡出效果;可能需要剪裁画面为圆角图片等;可能需要显示动态图片等。
另外,图片控件也需要交互,需要响应onTouchEvent事件。

Fresco用DraweeView统一实现了这些功能,它提供6个图层,而且做了一定的优化,基本满足显示需要,同时也可以响应事件。
我们在初始化时操作的Drawee就是负责图片的绘制与显示。

二、初始化

Fresco的初始化其实就是ImagePipeline图片管理模块的初始化,以及Drawee图片控件的初始化:

  public static void initialize(
      Context context,
      @Nullable ImagePipelineConfig imagePipelineConfig,
      @Nullable DraweeConfig draweeConfig) {
    ...
    if (imagePipelineConfig == null) {
      ImagePipelineFactory.initialize(context);
    } else {
      ImagePipelineFactory.initialize(imagePipelineConfig);
    }
    initializeDrawee(context, draweeConfig);
   ...
 }

问题在于,如何平衡高内聚的功能,与低耦合的可扩展性,Fresco的做法类似于模板方法,在抽象类中定义主要的功能逻辑,同时利用Config去来设置或调整一些细节的实现。

2.1 ImagePipeline的初始化

Fresco中获取ImagePipeline是通过Factory获取的:

  //Fresco源码
  public static ImagePipeline getImagePipeline() {
    return getImagePipelineFactory().getImagePipeline();//通过Factory获取
  }

所以ImagePipeline的初始化就是Factory的初始化:

  //ImagePipelineFactory源码
  public static void initialize(ImagePipelineConfig imagePipelineConfig) {
    sInstance = new ImagePipelineFactory(imagePipelineConfig);
  }

而Factory生产功能对象时,都需要Config的协助,例如在生产文件缓存时:

  //ImagePipelineFactory源码
  public FileCache getMainFileCache() {
    if (mMainFileCache == null) {
      DiskCacheConfig diskCacheConfig = mConfig.getMainDiskCacheConfig();
      mMainFileCache = mConfig.getFileCacheFactory().get(diskCacheConfig);
    }
    return mMainFileCache;
  }

所以,ImagePipeline的生产,核心在于开发者自己配置的ImagePipelineConfig,其中定义了可以影响生产的各类参数对象:

public class ImagePipelineConfig {
  // There are a lot of parameters in this class. Please follow strict alphabetical order.
  // 有趣的是,注释要求按字母顺序排列参数,然鹅,源码并没有做到 : )
  @Nullable private final AnimatedImageFactory mAnimatedImageFactory;
  private final Bitmap.Config mBitmapConfig;
  //未解码缓存的参数
  private final Supplier<MemoryCacheParams> mBitmapMemoryCacheParamsSupplier;
  //未解码缓存工厂
  private final CacheKeyFactory mCacheKeyFactory;
  private final Context mContext;
  private final boolean mDownsampleEnabled;
  //文件缓存工厂
  private final FileCacheFactory mFileCacheFactory;
  //已解码缓存的参数
  private final Supplier<MemoryCacheParams> mEncodedMemoryCacheParamsSupplier;
  private final ExecutorSupplier mExecutorSupplier;
  private final ImageCacheStatsTracker mImageCacheStatsTracker;
  //解码器
  @Nullable private final ImageDecoder mImageDecoder;
  private final Supplier<Boolean> mIsPrefetchEnabledSupplier;
  //磁盘缓存配置
  private final DiskCacheConfig mMainDiskCacheConfig;
  private final MemoryTrimmableRegistry mMemoryTrimmableRegistry;
  //在线数据获取器
  private final NetworkFetcher mNetworkFetcher;
  @Nullable private final PlatformBitmapFactory mPlatformBitmapFactory;
  private final PoolFactory mPoolFactory;
  //渐进加载配置
  private final ProgressiveJpegConfig mProgressiveJpegConfig;
  //请求监听者
  private final Set<RequestListener> mRequestListeners;
  private final boolean mResizeAndRotateEnabledForNetwork;
  //缩略图磁盘缓存配置
  private final DiskCacheConfig mSmallImageDiskCacheConfig;
  //解码器配置
  @Nullable private final ImageDecoderConfig mImageDecoderConfig;
  private final ImagePipelineExperiments mImagePipelineExperiments;

这些配置参数,为ImagePipelineFactory里的众多工厂提供参数。
整体来说,ImagePipeline的生产过程如下:


ImagePipelineFactory的生产

这样设计下,ImagePipeline所需的各种缓存、网络、磁盘等都集合在Factory中生产,同时通过Config保留了一定的可扩展性。

2.2 DraweeView的初始化

Fresco在初始化时,会对Drawee进行初始化:

  //Fresco源码
  private static void initializeDrawee(
      Context context,
      @Nullable DraweeConfig draweeConfig) {
    sDraweeControllerBuilderSupplier =
        new PipelineDraweeControllerBuilderSupplier(context, draweeConfig);
    SimpleDraweeView.initialize(sDraweeControllerBuilderSupplier);
  }

可见,分成了两步,先初始化ControllerBuilderSupplier,再根据这个Supplier去初始化DraweeView控件。
其实,第二步相对简单,因为Fresco的Drawee控件采用了MVC模式,初始化控件时,必须有Controller,需要依次调用Supplier-->Builder-->Controller,这样,在为控件设置url时,才能开始工作:

  //SimpleDraweeView源码
  public void setImageURI(Uri uri, @Nullable Object callerContext) {
    DraweeController controller = mSimpleDraweeControllerBuilder
        .setCallerContext(callerContext)
        .setUri(uri)
        .setOldController(getController())
        .build();
    setController(controller);//在setController函数中,会获取Drawable对象,绘制到控件上。
  }

所以有Supplier后,初始化View比较容易。

我们回来看第一步,怎样初始化Supplier,这个相对复杂,为了保留扩展性,Fresco并没有使用限定的Controller,而是定义了Supplier、Builder、Factory等一批辅助类,以便扩展,这些接口/类的关系是这样的:
如何实现DraweeController

可见,Builder是利用Config的参数,生产出Controller对象的,需要注意的是,提供Builder的Supplier,通过ImagePipelineFactory生产并引用了ImagePipeline的实例对象:
//PipelineDraweeControllerBuilderSupplier源码
  public PipelineDraweeControllerBuilderSupplier(
      Context context,
      ImagePipelineFactory imagePipelineFactory,
      Set<ControllerListener> boundControllerListeners,
      @Nullable DraweeConfig draweeConfig) {
    mContext = context;
    mImagePipeline = imagePipelineFactory.getImagePipeline();
    ...

这样,图像控件DraweeView就可以通过Controller与图像管理ImagePipeline关联起来了。

三、图片管理

图片管理其实包括图片的下载、缓存、解码等功能,OkHttp使用了Intercepter分层的设计来解决复杂问题,Fresco也采用了分层思想,不过层次不像OkHttp那样明显,它使用的是分阶段的设计,每个阶段返回一个Producer。
所以Fresco中的核心设计就是Producer和Consumer,用Producer来层层处理,每层的处理结果通过Consumer向上传递。
另外,Fresco还是用Cache和Pool来缓存数据,用Decoder解码器来优化各Android版本的解码功能。

3.1 分层设计

Fresco的分层设计,从功能上,包括这样几步:
1.从已编码缓存中获取(Bitmap类型),如果是从已编码缓存中获取数据,Fresco直接在UI线程中完成处理。
2.从未编码缓存中获取(EncodedImage类型)
3.从磁盘缓存中获取
4.从在线网络中获取

如果要做好分层处理,每层返回数据的逻辑和格式应该是类似的,这样才能实现递归处理,Fresco源码中,从网络获取数据的过程就是建立了一个执行链的过程,每层函数返回的都是Producer<T>,所以这些Producer函数的逻辑就是链式调用的逻辑:

  //ProducerSequenceFactory源码
  private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() {
    if (mNetworkFetchSequence == null) {
      mNetworkFetchSequence =
       newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());//缓存图片解码,其实前后有两个分支
    }
    return mNetworkFetchSequence;
  }
  //分支1-缓存图片解码
  private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToDecodeSequence(
      Producer<EncodedImage> inputProducer) {
    DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(inputProducer);//图片解码工具
    return newBitmapCacheGetToBitmapCacheSequence(decodeProducer);
  }
  //分支1-获取缓存图片
  private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToBitmapCacheSequence(
      Producer<CloseableReference<CloseableImage>> inputProducer) {
    BitmapMemoryCacheProducer bitmapMemoryCacheProducer =
        mProducerFactory.newBitmapMemoryCacheProducer(inputProducer);//从Bitmap复制缓存
    BitmapMemoryCacheKeyMultiplexProducer bitmapKeyMultiplexProducer =
        mProducerFactory.newBitmapMemoryCacheKeyMultiplexProducer(bitmapMemoryCacheProducer);//合并请求,避免产生多张图片
    ThreadHandoffProducer<CloseableReference<CloseableImage>> threadHandoffProducer =
        mProducerFactory.newBackgroundThreadHandoffProducer(//线程执行
            bitmapKeyMultiplexProducer,
            mThreadHandoffProducerQueue);
    return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);//已解码图片缓存
  }
 //分支2-在线获取未解码图片
 private synchronized Producer<EncodedImage> getCommonNetworkFetchToEncodedMemorySequence() {
    if (mCommonNetworkFetchToEncodedMemorySequence == null) {
      Producer<EncodedImage> inputProducer =
          newEncodedCacheMultiplexToTranscodeSequence(//在线请求数据
              mProducerFactory.newNetworkFetchProducer(mNetworkFetcher));
      mCommonNetworkFetchToEncodedMemorySequence =
          ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);//合并数据

      mCommonNetworkFetchToEncodedMemorySequence =
          mProducerFactory.newResizeAndRotateProducer(//大小/旋转控制
              mCommonNetworkFetchToEncodedMemorySequence,
              mResizeAndRotateEnabledForNetwork,
              mUseDownsamplingRatio);
    }
    return mCommonNetworkFetchToEncodedMemorySequence;
  }
  //分支2-访问网络
    private Producer<EncodedImage> newEncodedCacheMultiplexToTranscodeSequence(
      Producer<EncodedImage> inputProducer) {
    if (WebpSupportStatus.sIsWebpSupportRequired &&
        (!mWebpSupportEnabled || WebpSupportStatus.sWebpBitmapFactory == null)) {
      inputProducer = mProducerFactory.newWebpTranscodeProducer(inputProducer);//图片转码
    }
    inputProducer = newDiskCacheSequence(inputProducer);//磁盘缓存
    EncodedMemoryCacheProducer encodedMemoryCacheProducer =
        mProducerFactory.newEncodedMemoryCacheProducer(inputProducer);//未解码图片缓存
    return mProducerFactory.newEncodedCacheKeyMultiplexProducer(encodedMemoryCacheProducer);//合并请求
  }

因为层层return函数的设计,最先执行的其实是最内层的return,所以这个调用链的调用顺序和书写顺序正好是反的,就分支1而言,最先执行的是最里面的:

  return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);//已解码图片缓存

只有当不存在已解码图片缓存时,才会去执行参数threadHandoffProducer所定义的子线程获取图片数据(所以说,Fresco获取已解码图片缓存是在UI线程操作的)。

以上就是Producer链的基本结构和原理,源代码比较多,但是设计其实很精炼。

3.2 Producer-Consumer功能结构

Producer链的设计虽然能实现链式处理,但只是实现了链式的调用,缺少一个反馈数据的机制,Fresco为此设计了Consumer,Producer负责链式传递调用,Consumer负责在执行成功或失败后,予以反馈,这就是Fresco中的Producer-Consumer结构。
例如,在获取未解码图片缓存的BitmapMemoryCacheProducer中,定义了这样的Consumer:

protected Consumer<CloseableReference<CloseableImage>> wrapConsumer(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final CacheKey cacheKey) {
    return new DelegatingConsumer<
        CloseableReference<CloseableImage>,
        CloseableReference<CloseableImage>>(consumer) {
      @Override
      public void onNewResultImpl(CloseableReference<CloseableImage> newResult, boolean isLast) {
        ...
        // cache and forward the new result
        CloseableReference<CloseableImage> newCachedResult =
            mMemoryCache.cache(cacheKey, newResult);
        ...
  }

这个Consumer就可以在Producer执行过程中记录缓存数据。
有些时候,还会添加一些Listener,例如在AbstractProducerToDataSourceAdapter中定义的:

//AbstractProducerToDataSourceAdapter源码
  private Consumer<T> createConsumer() {
    return new BaseConsumer<T>() {
      @Override
      protected void onNewResultImpl(@Nullable T newResult, boolean isLast) {
        AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, isLast);
      }

      @Override
      protected void onFailureImpl(Throwable throwable) {
        AbstractProducerToDataSourceAdapter.this.onFailureImpl(throwable);
      }

      @Override
      protected void onCancellationImpl() {
        AbstractProducerToDataSourceAdapter.this.onCancellationImpl();
      }

      @Override
      protected void onProgressUpdateImpl(float progress) {
        AbstractProducerToDataSourceAdapter.this.setProgress(progress);
      }
    };
  }
  //调用Listener处理
  protected void onNewResultImpl(@Nullable T result, boolean isLast) {
    if (super.setResult(result, isLast)) {
      if (isLast) {
        //Listener是在ImagePipeline中定义并传入Adapter的
        mRequestListener.onRequestSuccess(
            mSettableProducerContext.getImageRequest(),
            mSettableProducerContext.getId(),
            mSettableProducerContext.isPrefetch());
      }
    }
  }

在具体实现中,Fresco还增加了Listener这样的角色,在Producer--Consumer体系之外,还可以通知订阅者相关的事件,实际上是一个Producer--Consumer+Listener的结构,这样扩展起来更加灵活。

3.3 Producer的生产

Producer是在ImagePipeline中发起生产的:

  public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
      ImageRequest imageRequest,
      Object callerContext,
      ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
      ...
      Producer<CloseableReference<CloseableImage>> producerSequence =
          mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
      ...
  }

在ProducerSequenceFactory中,可以看到,Factory会分情况创建多中FetchSequence:

  //ProducerSequenceFactory源码
  private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(
      ImageRequest imageRequest) {
    Preconditions.checkNotNull(imageRequest);

    Uri uri = imageRequest.getSourceUri();
    Preconditions.checkNotNull(uri, "Uri is null.");
    if (UriUtil.isNetworkUri(uri)) {
      return getNetworkFetchSequence();
    } else if (UriUtil.isLocalFileUri(uri)) {
      if (MediaUtils.isVideo(MediaUtils.extractMime(uri.getPath()))) {
        return getLocalVideoFileFetchSequence();
      } else {
        return getLocalImageFileFetchSequence();
      }
    } else if (UriUtil.isLocalContentUri(uri)) {
      return getLocalContentUriFetchSequence();
    } else if (UriUtil.isLocalAssetUri(uri)) {
      return getLocalAssetFetchSequence();
    } else if (UriUtil.isLocalResourceUri(uri)) {
      return getLocalResourceFetchSequence();
    } else if (UriUtil.isDataUri(uri)) {
      return getDataFetchSequence();
    } else {
      throw new IllegalArgumentException(
          "Unsupported uri scheme! Uri is: " + getShortenedUriString(uri));
    }
  }

3.4 Consumer的生产

Consumer不是在ImagePipeline中发起生产的,而是经过ImagePipeline-->CloseableProducerToDataSourceAdapter-->AbstractProducerToDataSourceAdapter的调用链,最终在抽象类中实现的:

  //AbstractProducerToDataSourceAdapter源码
  producer.produceResults(createConsumer(), settableProducerContext);//处理生产者
  ...
  //定义消费者
  private Consumer<T> createConsumer() {
    return new BaseConsumer<T>() {
      @Override
      protected void onNewResultImpl(@Nullable T newResult, boolean isLast) {
        AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, isLast);
      }
  ...
  //在消费者的回调中,调用Listener。
  protected void onNewResultImpl(@Nullable T result, boolean isLast) {
    if (super.setResult(result, isLast)) {
      if (isLast) {
        mRequestListener.onRequestSuccess(
            mSettableProducerContext.getImageRequest(),
            mSettableProducerContext.getId(),
            mSettableProducerContext.isPrefetch());
      }
    }
  }

可见,虽然Adapter适配器的主要作用是从Producer转为DataSource,但是Consumer可以做到从中间抓取Producer的执行结果,插入部分操作。

3.5 Listener的生产

ImagePipeline在请求图片时,会生产Listener。

//ImagePipeline源码
  private RequestListener getRequestListenerForRequest(ImageRequest imageRequest) {
    ...
    return new ForwardingRequestListener(mRequestListener, imageRequest.getRequestListener());
  }

真正的生产Listener是在ForwardingRequestListener中:

//ForwardingRequestListener源码
public class ForwardingRequestListener implements RequestListener {
  private final List<RequestListener> mRequestListeners;//列表
  ...
  public ForwardingRequestListener(RequestListener... requestListeners) {
    mRequestListeners = Arrays.asList(requestListeners);
  }

ForwardingRequestListener本身是一个RequestLister,而且还持有一个RequestListener的列表,当ForwardingRequestListener被回调时,会遍历回调内部所有的Listener:

//ForwardingRequestListener源码
  @Override
  public void onRequestSuccess(ImageRequest request, String requestId, boolean isPrefetch) {
    final int numberOfListeners = mRequestListeners.size();
    for (int i = 0; i < numberOfListeners; ++i) {//遍历回调
      RequestListener listener = mRequestListeners.get(i);
      ...
        listener.onRequestSuccess(request, requestId, isPrefetch);
      ...
    }
  }

所以,每个Listener被回调时,都可以是一串回调。

3.6 Producer、Consumer、Listener如何协同工作

首先,准备好Producer后,提交请求。

  //ImagePipeline源码
  public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
      ImageRequest imageRequest,
      Object callerContext,
      ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit) {
    try {
      Producer<CloseableReference<CloseableImage>> producerSequence =
          mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
      return submitFetchRequest(
          producerSequence,
          imageRequest,
          lowestPermittedRequestLevelOnSubmit,
          callerContext);
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

然后,准备好RequestListener。

  //ImagePipeline源码
  private <T> DataSource<CloseableReference<T>> submitFetchRequest(
      Producer<CloseableReference<T>> producerSequence,
      ImageRequest imageRequest,
      ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
      Object callerContext) {
    final RequestListener requestListener = getRequestListenerForRequest(imageRequest);
    ...
      return CloseableProducerToDataSourceAdapter.create(
          producerSequence,
          settableProducerContext,
          requestListener);
  }

最后,定义Consumer,并把Producer、Consumer和Listener的业务逻辑关联起来。

  //AbstractProducerToDataSourceAdapter源码
  producer.produceResults(createConsumer(), settableProducerContext);//处理生产者
  ...
  //定义消费者
  private Consumer<T> createConsumer() {
    return new BaseConsumer<T>() {
      @Override
      protected void onNewResultImpl(@Nullable T newResult, boolean isLast) {
        AbstractProducerToDataSourceAdapter.this.onNewResultImpl(newResult, isLast);
      }
  ...
  //在消费者的回调中,调用Listener。
  protected void onNewResultImpl(@Nullable T result, boolean isLast) {
    if (super.setResult(result, isLast)) {
      if (isLast) {
        mRequestListener.onRequestSuccess(
            mSettableProducerContext.getImageRequest(),
            mSettableProducerContext.getId(),
            mSettableProducerContext.isPrefetch());
      }
    }
  }

3.7 Cache

为了提高图片加载性能,Fresco也采用了图片缓存的方式。
Cache代码具有普遍性,被分在imagepipeline-base里。

Fresco虽然有三级缓存,但从性质上就是内存缓存MemoryCache和磁盘缓存BufferedDiskCache两种,后者是一个全功能的缓存操作类。
内存缓存就比较复杂,为了兼顾不同类型的内存缓存,也为了优化内存使用,MemoryCache只是一个接口,真正为ImagePipeline生产的内存缓存,虽然Factory不同,但大都是个InstrumentedMemoryCache对象,该对象持有CountingMemoryCache,内存缓存的核心,就是这个CountingMemoryCache。

其实,管理内存缓存,重点就在于匹配、写入,和清理,CountingMemoryCache是这样实现的:
写入
为了方便查找和去重,使用一个CountingLruMap管理所有的内存缓存;
同时,需要检查是否超出空间上限,一个是数量上限,一个是内存空间上限,这都在MemoryCacheParams中配置,在ImagePipelineConfig时传入。
匹配
使用一个CountingLruMap(底层是一个LinkedHashMap),以k-v键值对的形式保存缓存,匹配时根据key即可匹配;
清理
清理需要兼顾空间效率和缓存命中,CountingMemoryCache的思路是专门管理未加载的图片。
具体来说,CountingMemoryCache另外做了一个CountingLruMap,专门存储未被加载过的图片,当空间不足时,CountingMemoryCache提供了一个trimExclusivelyOwnedEntries函数,根据清理数量或目标空间大小,清理部分数据。

在管理内存缓存时,使用两个CountingLruMap队列,其中一个队列中维持了

另外,缓存管理需要响应读写/命中等事件,所以Fresco定义了MemoryCacheTracker和ImageCacheStatsTracker两种跟踪器,可以跟踪处理缓存变化。

3.8 Pool

MemoryCache是通过缓存复用数据,这是数据层面的复用;在此基础上,Fresco做了更进一步的优化,就是在内存层面也做复用,因为图片内存空间一般较大,分配和回收内存空间也很耗资源,为此,Fresco使用内存池Pool来实现内存的复用。
Pool代码具有普遍性,被分在imagepipeline-base里。

图片内存复用时,一个很重要的变量就是图片的大小,为此,Fresco把Pool中的内存对象按照大小分别管理:

  //BasePool源码
  /**
   * The buckets - representing different 'sizes'
   */
  @VisibleForTesting
  final SparseArray<Bucket<V>> mBuckets;

每个具体的业务类如BitmapPool,在get时都是执行了抽象父类BasePool的get方法(多态),从而实现从Pool中复用内存。

Pool和Cache虽然都是解决复用问题,但是应用场景不同,Cache是为业务层提供数据用的,Pool主要是为Decoder提供解码时的内存空间,如ArtDecoder就使用了BitmapPool。

3.9 Decoder

Decoder代码在imagepipeline包里。

因为Android系统变动,不同版本的Android系统下,各自有效率最优的Decoder方式,为此,Fresco使用PlatformDecoder接口定义了解码的抽象行为,然后针对不同平台,提供了不同的解码器:
GingerbreadPurgeableDecoder(继承DalvikPurgeableDecoder):通过MemoryFile来获取一个Ashmem内存,并在Ashmem内存中解码,因为Ashmem并不纳入Android虚拟机的Java内存管理,所以有占内存极低的”黑科技“的感觉。
KitKatPurgeableDecoder(继承DalvikPurgeableDecoder):因为Android系统不再允许通过Ashmem做复杂运算,解码需要在Java层开辟内存,这里使用了FlexByteArrayPool提供内存。
ArtDecoder:直接把未解码图片封装为流来解码,使用BitmapPool提供内存。

四、图片控件

Fresco与其他图片加载框架最大的不同就在于控件,Fresco提供了专门的图片控件DraweeView,包揽了动图播放、多级图层、渐进显示、画面裁剪等功能,而且性能控制地相当不错。

4.1 控件结构设计

先看DraweeView需要实现的功能:
1.需要显示图片;
2.需要管理所有需要显示的图片,并从中选择应当显示哪些;
3.需要更新这些图片;
4.控件是需要交互的,还需要处理onTouchEvent事件。
如果把这些功能看成一个APP,这其实是很典型的MVC结构,1是V,2是M,3+4是C。

实际上,Fresco就是把他设计成了一个MVC结构:
DraweeView的MVC

在这个MVC结构中,View层显示数据时,需要从M层获取Drawable对象,也就是通过Hierarchy获取Drawable:
//DraweeView源码
super.setImageDrawable(mDraweeHolder.getTopLevelDrawable());//super指的是ImageView

其中,Holder的函数是由Hierarchy处理的:

  //DraweeHolder源码
  public Drawable getTopLevelDrawable() {
    return mHierarchy == null ? null : mHierarchy.getTopLevelDrawable();
  }

此外,每次为DraweeView设置Hierarchy或Controller时,也会执行View的setImageDrawable更新显示内容,在Fresco里,实际上正是通过setImageURI/setHierarchy/setController这三个函数,实现的图片加载。

我们看到,Fresco的设计的V里有个DraweeHolder,这个Holder持有数据层Hierarchy和控制层Controller的实例,所以DraweeView里与C和M相关的操作,都是在这个Holder里操作的,这很像ContextWrapper里把所有跟Context相关的工作都交给ContextImpl的装饰模式,不过Fresco里这个设计属于合成复用原则,即尽量用聚合引用等关系,代替继承关系,这样解耦比较彻底,也方便用户。

利用这个Holder,我们实际上可以自己扩展DraweeView,只要引用一个DraweeHolder实例,就能继续套用Fresco设计的这套MVC结构

4.2 MVC——M

Fresco在DraweeView中的Model层,主要管理和提供Drawable,所以,其实就是需要实现这样两个功能:
1.定义和管理6层图片的Drawable
2.提供合成后的图片Drawable
注意这里的Model层并不同于图像管理中的Model概念,并不试图获取图片数据,那是ImagePipeline的功能,这里的Model只是为了管理图片的分层。

具体来说,Fresco对于Model层的实现,核心接口就是DraweeHierarchy,定义了getTopLevelDrawbale函数,来提供Drawable数据:

public interface DraweeHierarchy {
  Drawable getTopLevelDrawable();
}

为了实现多级图片的层次设计,扩展了一个SettableDraweeHierarchy:

public interface SettableDraweeHierarchy extends DraweeHierarchy {
  void reset();
  void setImage(Drawable drawable, float progress, boolean immediate);
  void setProgress(float progress, boolean immediate);
  void setFailure(Throwable throwable);
  void setRetry(Throwable throwable);
  void setControllerOverlay(Drawable drawable);
}

在这两个接口的基础上,才是我们真正在开发时使用的GenericDraweeHierarchy:

 * <p>
 * Example hierarchy with a placeholder, retry, failure and the actual image:
 *  <pre>
 *  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)
 *  </pre>
 *
 * <p>
public class GenericDraweeHierarchy implements SettableDraweeHierarchy {
  ...
  private static final int BACKGROUND_IMAGE_INDEX = 0; //背景图
  private static final int PLACEHOLDER_IMAGE_INDEX = 1; //占位图
  private static final int ACTUAL_IMAGE_INDEX = 2; //真实图
  private static final int PROGRESS_BAR_IMAGE_INDEX = 3; //进度图
  private static final int RETRY_IMAGE_INDEX = 4; //重试图
  private static final int FAILURE_IMAGE_INDEX = 5; //失败图
  private static final int OVERLAY_IMAGES_INDEX = 6; //覆盖图

我们说的6个层,就是在这个类中实现的,它使用一个Drawable数组管理这六层数据,并合并为一个RootDrawable类型的mTopLevelDrawble:

//GenericDraweeHierarchy源码
Drawable[] layers = new Drawable[numLayers];
...
//在数组中定义6层Drawable
...
mFadeDrawable = new FadeDrawable(layers);//组合6个图层
...
Drawable maybeRoundedDrawable =
        WrappingUtils.maybeWrapWithRoundedOverlayColor(mFadeDrawable, mRoundingParams);//图像裁剪,如圆角
mTopLevelDrawable = new RootDrawable(maybeRoundedDrawable);//

Hierarchy最终向View提供一个RootDrawable类型的mTopLevelDrawable对象,作为V的数据源。
出于职责分工的设计,Hierarchy中的图层管理和V中的事件响应,都是在Controller中实现的。

4.3 MVC——C

对于控制层Controller来说,需要做这样几件事:
1.与View建立关联;
2.与Model建立关联;
3.生产DraweeController;
4.实现业务功能;

1.与View建立关联
为View设置Controller是开发者自己写代码的:

PipelineDraweeController controller =
                (PipelineDraweeController) Fresco.newDraweeControllerBuilder()
                        .setUri(uri)
                        .build();
imageView.setController(controller);

这个很简单
2.与Model建立关联
我们知道,DraweeView的DraweeHolder持有数据层Hierarchy和控制层Controller的实例:

//DraweeHolder源码
public class DraweeHolder<DH extends DraweeHierarchy>
    implements VisibilityCallback {
  ...
  private DH mHierarchy;
  private DraweeController mController = null;

在Holder设置Controller和Hierarchy时,这两者会被关联起来:

  //DraweeHolder源码
  public void setController(@Nullable DraweeController draweeController) {
      ...
      mController.setHierarchy(mHierarchy);
  }
  ...
  public void setHierarchy(DH hierarchy) {
      ...
      mController.setHierarchy(mHierarchy);
  }

借此,Controller可以操作Model层。
3.生产DraweeController
为了保留扩展性,Fresco并没有使用限定的Controller,而是定义了Supplier、Builder、Factory等一批辅助类,以便扩展,前面说过,这些接口/类的关系是这样的:

如何实现DraweeController

我们看到,Fresco提供了PipelineDraweeController,为了方便扩展,可以向Fresco中传入自定义的Supplier,从Supplier中取出Builder,利用Builder的Factory生产Controller。

在这套体系之下,我们可以沿用Fresco的结构,自己扩展Builder、Factory和Controller,只要继承AbstractDraweeControllerBuilder就能很方便地实现Builder,只要继承AbstractDraweeController就能很方便地实现Controller,核心业务功能这两个抽象类都有实现过,开发者只要根据自身需要去扩展即可。
4.实现业务功能
Controller的业务功能都在DraweeController中定义好了:

public interface DraweeController {
  DraweeHierarchy getHierarchy();
  void setHierarchy(@Nullable DraweeHierarchy hierarchy);
  void onAttach();
  void onDetach();
  void onViewportVisibilityHint(boolean isVisibleInViewportHint);
  boolean onTouchEvent(MotionEvent event);
  Animatable getAnimatable();
  void setContentDescription(String contentDescription);
  String getContentDescription();
}

具体来说,Controller的很多核心功能在抽象类AbstractDraweeController里已经写好了,onAttach、onDetach、事件处理等都有实现,这样可以把框架内部交互的工作都提前定义好;也有些核心功能如createDrawable是空的,需要子类如PipelineDraweeController自己实现。

例如,onAttach和onClick时默认需要刷新图像数据,这时会调用submitRequest函数,需要通过DataSource获取数据,这里的分工是这样的:
抽象类:submitResuest函数,具体业务逻辑已写好,但缺少DataSource实例。
实现类:提供DataSource,通过Builder的getDataSourceForRequest函数中获取。
因为DataSource实际调用的是ImagePipeline的fetchDecodedImage函数,所以Controller是通过ImagePipeline获取图片数据。
这个过程是这样的:


Controller中的数据源

4.4 MVC——V

V其实非常简单,受益于MVC的设计,输入和输出都不在V中实现,onTouchEvent事件交给了Controller,setImageDrawable图像则是由Hierarchy数据层来负责提供Drawable,V只作为ImageView,把Drawable绘制到控件上。
至于Fresco对于6层图像的合并,前面在M层Hierarchy中已经看过,是Hierarchy负责合并Drawable,并调用Drawable的invalidateSelf函数重绘界面。
另外,DraweeView实际上还起到了外观类的作用,例如SimpleDraweeView的setImageURI函数,实际上是通过Holder交给Controller去做的,这是典型的外观模式。
除此之外,V的主要作用就是输入和输入,输入onTouchEvent事件,输出setImageDrawable图像。

五、Fresco的一些优化措施

为了缓解图片占用内存高,IO慢,渲染耗费CPU等问题,Fresco采用了大量优化手段。

5.1 解码器的优化

解码器最令人担心的问题,在于频繁解码会耗费大量的内存,这些内存频繁分配和回收,会造成GC卡顿。
所以,Fresco做了很多针对解码器的优化,如通过Pool内存池复用内寸空间。
而且,因为Android系统各平台的变化,不同平台的最优解码方式不一样,为此,Fresco是在ImagePipeline中生产解码器,生产解码器的代码如下:

  //ImagePipelineFactory源码
  public static PlatformDecoder buildPlatformDecoder(
      PoolFactory poolFactory,
      boolean directWebpDirectDecodingEnabled) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
      return new ArtDecoder(
          poolFactory.getBitmapPool(),
          maxNumThreads,
          new Pools.SynchronizedPool<>(maxNumThreads));
    } else {
      if (directWebpDirectDecodingEnabled
          && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
        return new GingerbreadPurgeableDecoder();
      } else {
        return new KitKatPurgeableDecoder(poolFactory.getFlexByteArrayPool());
      }
    }
  }

Fresco早期的解码器非常亮眼,它通过深入挖掘Ashmem的潜能,做到了解码、缓存和Java层极低的内存占用,但是后来的Android平台做了变更(4.4以后,dumpsys meminfo增加了Ashmem,就是一并计入App内存,以免滥用Android系统内存),后期的平台是在Java堆上管理内存了。
后期的解码,是BitmapFactory.decodeByteArray;
在Art平台上,是BitmapFactory.decodeStream。

5.2 CloseableReference

因为图片内存空间大,容易造成内存紧张,所以图片内存需要尽量主动回收,为此,Fresco使用CloseableReference包装了图片对象,例如,ImagePipeline获取已解码图片时,就使用CloseableReference:

//ImagePipeline源码
public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(

CloseableReference持有一个SharedReference,一方面存放对象,一方面引用计数,如果没有对象yin

参考

Fresco官网
Fresco图片库研读分析
Fresco图片框架内部实现原理探索
Fresco原理分析
Fresco源码赏析 之 基本流程
Fresco源码解析 - Hierarchy / View / Controller
浅析fresco
Fresco源码赏析之后台获取流程
谈谈fresco的bitmap内存分配

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 姓名:周小蓬 16019110037 转载自:http://blog.csdn.net/YChenFeng/art...
    aeytifiw阅读 34,522评论 13 425
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • 本文转载自http://dataunion.org/?p=9307 背景介绍Kafka简介Kafka是一种分布式的...
    Bottle丶Fish阅读 5,353评论 0 34
  • 一. “探长,我们最后一次见到伊森就是在这里了。” 眼前的男人身材消瘦,他长着胡茬的脸上满是绝望,双眼肿的不像样子...
    景泰蓝呦阅读 336评论 0 0