浅谈 Fresco 框架结构

在前面的文章 Fresco 源码分析 —— 图片加载流程 里面详细说明了图片加载的整个流程,但是除了理解源码之外,对于源码的框架层面的设计也是需要去了解的,不能只是简单的读源码,好的源码的框架设计也是值得我们去学习的。以后,我们自己在开发一个源码的时候,也就能将学到的好的经验运用到自己的代码上。

代码工程

从 module 层面来看,可以看到 Fresco 是有很多 module 的,并且这些 module 都是按照每个 module 的功能进行划分的,可以通过名字就可以知道 module 的作用。

虽然 module 很多,但是并不像我们平时的项目里面一样,一个 module 包含很多代码,有的可能只有几个类,只是 fresco 整体分的比较细。

下面介绍下一些关键的 module:

  • animated-base:主要都是动图的一些共有的基础操作,包阔每一帧的缓存,解码渲染,帧数,宽高等逻辑。

  • Drawee:就是 UI 层,例如 DraweeView, drawable 相关的类等;

  • imagepipeline:整个工程的核心,图片加载,内存、缓存管理,bitmap处理等等核心逻辑;

  • imagepipeline-base:是 imagepipeline 的一些基础类,包括接口(缓存,解码,图片信息)以及相关的基础类;

  • imagepipeline-backends :这里就是最后的请求逻辑,这里给出两个不同的示例,分别采用 volly 和 ok-http 来实现的,默认是 HttpUrlConnectionNetworkFetcher ,也就是说业务方有着更复杂的业务需求的话,需要自己去实现。

  • Drawee-backends:主要是在 Drawee 的基础上封装了请求和初始化逻辑,比如 Fresco,PipelineDraweeController 等相关类。

 

设计思想浅谈 

1、里面有很多类以 config 结尾,他们是怎么用的?

ImagePipelineConfig:内部采用 builder 模式来进行创建,主要是把 ImagePipeline 需要的参数都通过 config 来进行管理;

ImagePipelineFactory: 通过 ImagePipelineConfig 来生成 ImagePipeline ;

类似的还有:

  • DraweeConfig: 内部采用 builder 模式来进行创建,包含 Drawee 相关的配置;

  • DiskCacheConfig:内部采用 builder 模式来进行创建,包含 disk 的各种配置,包括目录,版本,缓存大小,错误日志等。最后也是使用 DiskStorageCacheFactory 来生成 disk;

  • PoolConfig:内部采用 builder 模式来进行创建,其实最终也是再 PoolFactory 里面使用;

可以发现,Fresco 里面的 config 类都是采用了 buidler 模式。那为啥需要采用 builder 模式呢?因为 config 从名字来说是配置类,里面会有很多参数,所以会采用 builder 模式,以后别人在配置的时候,就不会关心过的参数问题。 

其实对于内部 builder 我有个疑问就是为啥要使用内部 builder 。 

  • 一是内部属性太多,如果采用构造函数模式,那需要写很多构造函数

  • 二是采用builder 模式后,用户只需要设置他关心的属性,其他不关心的属性都可以采用默认值来进行处理,也就是减轻了使用者的压力。

  • 三是一旦构造完成,就不可以修改了,builder 里面都是设置属性,但是类本身只提供获取属性方法,不提供设置方法,隔绝了用户更改带来的不可控因素。

  • 至于内部builder 可能是不希望将他们独立出去,散落在各处不好管理。

 

2、提供了很多 producer,consumer,那这么多类是如何管理的,他们之间恶关系如何维护。

提供了 ProducerFactory 来管理所有的 producer。ProducerFactory 有静态方法,大多数是非静态方法。主要用来获取各种 producer 。

ProducerSequenceFactory: 这个其实就是把各个 Producer 连接在一起;或者说是按照一定的规则,将他们组装在一起。这样外界在调用的时候,只需要确定你的 Sequence 是什么样的,调用对应的方法 获取 Sequence。其中在 Sequence 里面又会通过 ProducerFactory 来获取指定的

每一个 producer 又会有一个对应的 consumer,可以发现大多数 consumer 都是 producer 里面的内部类。

 

3、DataSource 的作用

DataSource 是一个泛型接口。按照源码的描述,它和 future 原理差不多,但是有个不一样的地方,就是它可以获取当前的进度。

AbstractDataSource 继承自 DataSource;这块内部已经维护好了各种状态;然后会通过 listeners 进行通知。

AbstractProducerToDataSourceAdapter : 继承自 AbstractDataSource,从名字就可以看出来这是一个适配器,将 Producer 转为 DataSource。

 

4、ProducerContext 的作用

主要是用来将上下文信息传递给 Producer;可以具体看看代码,可以发现 context 内部包含很多逻辑。

public interface ProducerContext {

  @StringDef({
    ExtraKeys.ORIGIN,
    ExtraKeys.ORIGIN_SUBCATEGORY,
    ExtraKeys.NORMALIZED_URI,
    ExtraKeys.SOURCE_URI,
    ExtraKeys.ENCODED_WIDTH,
    ExtraKeys.ENCODED_HEIGHT,
    ExtraKeys.ENCODED_SIZE,
    ExtraKeys.MULTIPLEX_BITMAP_COUNT,
    ExtraKeys.MULTIPLEX_ENCODED_COUNT,
  })
  @interface ExtraKeys {
    String ORIGIN = "origin";
    String ORIGIN_SUBCATEGORY = "origin_sub";
    String SOURCE_URI = "uri_source";
    String NORMALIZED_URI = "uri_norm";
    String ENCODED_WIDTH = "encoded_width";
    String ENCODED_HEIGHT = "encoded_height";
    String ENCODED_SIZE = "encoded_size";
    /* number of deduped request in BitmapMemoryCacheKeyMultiplexProducer */
    String MULTIPLEX_BITMAP_COUNT = "multiplex_bmp_cnt";
    /* number of deduped request in EncodedCacheKeyMultiplexProducer */
    String MULTIPLEX_ENCODED_COUNT = "multiplex_enc_cnt";
  }

  /** @return image request that is being executed */
  ImageRequest getImageRequest();

  /** @return id of this request */
  String getId();

  /** @return optional id of the UI component requesting the image */
  @Nullable
  String getUiComponentId();

  /** @return ProducerListener2 for producer's events */
  ProducerListener2 getProducerListener();

  /** @return the {@link Object} that indicates the caller's context */
  Object getCallerContext();

  /** @return the lowest permitted {@link ImageRequest.RequestLevel} */
  ImageRequest.RequestLevel getLowestPermittedRequestLevel();

  /** @return true if the request is a prefetch, false otherwise. */
  boolean isPrefetch();

  /** @return priority of the request. */
  Priority getPriority();

  /** @return true if request's owner expects intermediate results */
  boolean isIntermediateResultExpected();

  /**
   * Adds callbacks to the set of callbacks that are executed at various points during the
   * processing of a request.
   *
   * @param callbacks callbacks to be executed
   */
  void addCallbacks(ProducerContextCallbacks callbacks);

  ImagePipelineConfig getImagePipelineConfig();

  EncodedImageOrigin getEncodedImageOrigin();

  void setEncodedImageOrigin(EncodedImageOrigin encodedImageOrigin);

  <E> void setExtra(@ExtraKeys String key, @Nullable E value);

  void putExtras(@Nullable Map<String, ?> extras);

  @Nullable
  <E> E getExtra(String key);

  @Nullable
  <E> E getExtra(String key, @Nullable E valueIfNotFound);

  Map<String, Object> getExtras();

  /** Helper to set {@link ExtraKeys#ORIGIN} and {@link ExtraKeys#ORIGIN_SUBCATEGORY} */
  void putOriginExtra(@Nullable String origin, @Nullable String subcategory);

  /** Helper to set {@link ExtraKeys#ORIGIN} */
  void putOriginExtra(@Nullable String origin);
}

 

这里说下 ProducerContext 使用,这里其实采用的是接口模式,然后相当于是面向接口编程,这样后期在扩展的时候,也会变得更加方便。

类似的上下文还有 FrescoContext。

其实对于上下文,我们在设计源码的时候,也可以考虑下,有了 context 的存在,可以减少很多类之间的依赖,使得代码逻辑更加清晰。

 

5、ImagePipeline

ImagePipelineConfig: 这个可以说是把 ImagePipeline 用到的东西一网打尽;这个是用于用户在使用 Fresco 的时候,可以进行对应的配置。

ImagePipeline: 发起请求的类(包括网络,本地缓存,内存,回调)以及解码和非解码的图片,还有包括预取;

ImagePipelineFactory: 是一个单例。也就是说明所有的请求配置是一样的。但是这样的话,怎么区分不同的请求,这块还需要仔细看看的。url 是通过 imageRequest 来管理的,ImagePipeline 主要是负责管理其他方面的东西。包括缓存等所有请求应该都是一样的。然后在获取 getDataSourceSupplier 的时候发起图片请求。

imageRequest:imageRequest 包含 url 等相关信息。会在 ImagePipeline 中构造一个 producerSequence。最终,producerSequence 和 settableProducerContext 会在 AbstractProducerToDataSourceAdapter 转变为 DataSource;

 

6、ImagePipeline 和 producer 之间的关系

这个其实在 ImagePipeline 中解释过了,他们相当于是一环套一环。

 

7、builder 模式的使用

ImageRequestBuilder: 用于构建 ImageRequest。

AbstractDraweeControllerBuilder: 使用得是泛型,将通用逻辑封装在其内部;

PipelineDraweeControllerBuilder:controller 的逻辑在里面;

这个其实主要是在前面的 config 里面讲过了,这里就不再细说。

 

8、factory 模式的应用

DefaultDrawableFactory:生成动态静态图片;

PipelineDraweeControllerFactory:一个是创建controller,一个是创建内部controller;

ImagePipelineFactory:这个有点感觉是个容器,所有和 ImagePipeline 相关的都可以从里面获取到;

其他 factory 的类也有很多。就我个人理解,之所以用到 factory ,主要是为了屏蔽产品的具体实现,调用者只关心产品的接口。

 

9、其他

Fresco 是怎么控制请求暂停的

Fresco.getImagePipeline().resume()  // 恢复请求
Fresco.getImagePipeline().isPaused() // 是否处于暂停态
Fresco.getImagePipeline().pause(); // 暂停请求

public class ThreadHandoffProducerQueueImpl implements ThreadHandoffProducerQueue {
  private boolean mQueueing = false;
  private final Deque<Runnable> mRunnableList;  // 双向队列
  private final Executor mExecutor;  // 是轻量级的,只有一个线程

  public ThreadHandoffProducerQueueImpl(Executor executor) {
    mExecutor = Preconditions.checkNotNull(executor);
    mRunnableList = new ArrayDeque<>();
  }

  @Override
  public synchronized void addToQueueOrExecute(Runnable runnable) {
    if (mQueueing) {  // 一旦暂停,就将请求加入队列
      mRunnableList.add(runnable);
    } else {
      mExecutor.execute(runnable);
    }
  }

  @Override
  public synchronized void startQueueing() {
    mQueueing = true;
  }

  @Override
  public synchronized void stopQueuing() {
    mQueueing = false;
    execInQueue();
  }

 

通过 mThreadHandoffProducerQueue.startQueueing() 来控制暂停;相当于请求可以加到队列中,但是不会发起请求;

ThreadHandoffProducer  是用于请求的时候进行线程切换的。其中有个 produceResults 会将相关逻辑封装成一个StatefulProducerRunnable 里面进行处理,最终会调用下面的方法进行执行:

 mThreadHandoffProducerQueue.addToQueueOrExecute(FrescoInstrumenter.decorateRunnable(statefulRunnable, getInstrumentationTag(context))); 

这里有个问题是我看着是只有一个线程,他是如何利用这一个线程来操作的?

虽然只有一个线程,但是其实就跟主线程发起请求也只有一个线程一样,所以其实整体还好,也便于控制。

public abstract class StatefulProducerRunnable<T> extends StatefulRunnable<T> {}

public abstract class StatefulRunnable<T> implements Runnable {
  @Override
  public final void run() {
    if (!mState.compareAndSet(STATE_CREATED, STATE_STARTED)) {
      return;
    }
    T result;
    try {
      result = getResult();
    } catch (Exception e) {
      mState.set(STATE_FAILED);
      onFailure(e);
      return;
    }

    mState.set(STATE_FINISHED);
    try {
      onSuccess(result);
    } finally {
      disposeResult(result);
    }
  }
}
可以发现,这里并没有做很复杂,就是为了改变下运行状态。 DecodeProducer 中也有个线程池,最终就会切换到另一个线程。

// DecodeProducer.java 
 @Override
  public void produceResults(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final ProducerContext producerContext) {
    try {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.beginSection("DecodeProducer#produceResults");
      }
      final ImageRequest imageRequest = producerContext.getImageRequest();
      ProgressiveDecoder progressiveDecoder;
      if (!UriUtil.isNetworkUri(imageRequest.getSourceUri())) {
        progressiveDecoder =
            new LocalImagesProgressiveDecoder(
                consumer, producerContext, mDecodeCancellationEnabled, mMaxBitmapSize);
      } else {
        ProgressiveJpegParser jpegParser = new ProgressiveJpegParser(mByteArrayPool);
        progressiveDecoder =
            new NetworkImagesProgressiveDecoder(
                consumer,
                producerContext,
                jpegParser,
                mProgressiveJpegConfig,
                mDecodeCancellationEnabled,
                mMaxBitmapSize);
      }
      mInputProducer.produceResults(progressiveDecoder, producerContext);
    } finally {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.endSection();
      }
    }

 

这里会分成两路,先看看是不是本地图片,如果是本地,那么就会走解码逻辑,也就会用到线程池然后切换线程;

简单来说就是通过 produceResults 来实现一层层的封装。比如 DecodeProducer 就是在 produceResults 封装了一个 progressiveDecoder,他同时也是一个 consumer,会在结果回来的时候,回调 consumer 的  onNewResultImpl 方法。最终就会开始运行相关逻辑。

Consumer 和 Producer 之间的关系

每一个 producer 又会有一个对应的 consumer,可以发现大多数 consumer 都是 consumer 里面的内部类。

当调用一个producer 的 produceResults 方法的时候,同时会将自己内部类consumer实例传给下一个producer,一旦有结果了,就可以使用 consumer 进行优化。

怎么切回主线程

首先在请求的时候,会通过 datasource 来进行监听结果的回调:

// AbstractDraweeController.java
public void onAttach() {
    ...
    mIsAttached = true;
    if (!mIsRequestSubmitted) {
      submitRequest();
    }
}

protected void submitRequest() {
    mDataSource = getDataSource(); 
    final DataSubscriber<T> dataSubscriber = new BaseDataSubscriber<T>() { //可以简单的把它理解为一个监听者
        @Override
        public void onNewResultImpl(DataSource<T> dataSource) { //图片加载成功
            ...
        }
        ...
    };
    ...
    mDataSource.subscribe(dataSubscriber, mUiThreadImmediateExecutor); //mUiThreadImmediateExecutor是指 dataSubscriber 回调方法运行的线程,这里是主线程
} 

可以看到上面是在主线程进行监听的。mUiThreadImmediateExecutor 是一个单例,是 UiThreadImmediateExecutorService 的一个实例,会直接调用 handler 执行:

  // UiThreadImmediateExecutorService.java
  @Override
  public void execute(Runnable command) {
    if (isHandlerThread()) {
      command.run();
    } else {
      super.execute(command);  // 内部使用handler.post 来实现
    }
  } 

在通知结果的时候,会封装成一个 runnable 对象,最终在 excutor 里面运行。

// AbstractDataSource.java
protected void notifyDataSubscriber(
      final DataSubscriber<T> dataSubscriber,
      final Executor executor,
      final boolean isFailure,
      final boolean isCancellation) {
    Runnable runnable =
        new Runnable() {
          @Override
          public void run() {
            if (isFailure) {
              dataSubscriber.onFailure(AbstractDataSource.this);
            } else if (isCancellation) {
              dataSubscriber.onCancellation(AbstractDataSource.this);
            } else {
              dataSubscriber.onNewResult(AbstractDataSource.this);
            }
          }
        };
    final DataSourceInstrumenter instrumenter = getDataSourceInstrumenter();
    if (instrumenter != null) {
      runnable = instrumenter.decorateRunnable(runnable, "AbstractDataSource_notifyDataSubscriber");
    }
    executor.execute(runnable);
  }

Fresco 中使用到的线程池

DefaultExecutorSupplier 中包含各种不同类型的线程池; 用的是固定线程池;

 */
public class DefaultExecutorSupplier implements ExecutorSupplier {
  // Allows for simultaneous reads and writes.
  private static final int NUM_IO_BOUND_THREADS = 2;
  private static final int NUM_LIGHTWEIGHT_BACKGROUND_THREADS = 1;  // 只有一个吗?
  // 各种不同类型的线程
  private final Executor mIoBoundExecutor;  
  private final Executor mDecodeExecutor;
  private final Executor mBackgroundExecutor;
  private final Executor mLightWeightBackgroundExecutor;

  public DefaultExecutorSupplier(int numCpuBoundThreads) {
    mIoBoundExecutor =
        Executors.newFixedThreadPool(
            NUM_IO_BOUND_THREADS,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoIoBoundExecutor", true));
    mDecodeExecutor =
        Executors.newFixedThreadPool(
            numCpuBoundThreads,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoDecodeExecutor", true));
    mBackgroundExecutor =
        Executors.newFixedThreadPool(
            numCpuBoundThreads,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoBackgroundExecutor", true));
    // 用的是固定线程池
    mLightWeightBackgroundExecutor =
        Executors.newFixedThreadPool(
            NUM_LIGHTWEIGHT_BACKGROUND_THREADS,
            new PriorityThreadFactory(
                Process.THREAD_PRIORITY_BACKGROUND, "FrescoLightWeightBackgroundExecutor", true));
  }

 

Fresco 磁盘的大小

// DiskCacheConfig.java
private int mVersion = 1;
    private String mBaseDirectoryName = "image_cache";
    private Supplier<File> mBaseDirectoryPathSupplier;
    private long mMaxCacheSize = 40 * ByteConstants.MB;
    private long mMaxCacheSizeOnLowDiskSpace = 10 * ByteConstants.MB;
    private long mMaxCacheSizeOnVeryLowDiskSpace = 2 * ByteConstants.MB;
    private EntryEvictionComparatorSupplier mEntryEvictionComparatorSupplier =
        new DefaultEntryEvictionComparatorSupplier();
    private CacheErrorLogger mCacheErrorLogger;
    private CacheEventListener mCacheEventListener;
    private DiskTrimmableRegistry mDiskTrimmableRegistry;
    private boolean mIndexPopulateAtStartupEnabled;

 

 磁盘大小控制

// DiskStorageCache.java
  @GuardedBy("mLock")
  private void evictAboveSize(long desiredSize, CacheEventListener.EvictionReason reason)
      throws IOException {}


  /**
   * Test if the cache size has exceeded its limits, and if so, evict some files. It also calls
   * maybeUpdateFileCacheSize
   *
   * <p>This method uses mLock for synchronization purposes.
   */
  private void maybeEvictFilesInCacheDir() throws IOException {
    synchronized (mLock) {
      boolean calculatedRightNow = maybeUpdateFileCacheSize();

      // Update the size limit (mCacheSizeLimit)  这里会根据当前磁盘的大小来判断是否需要更新磁盘文件的大小。不足就会采用小的容量。
      updateFileCacheSizeLimit();

      long cacheSize = mCacheStats.getSize();
      // If we are going to evict force a recalculation of the size
      // (except if it was already calculated!)
      if (cacheSize > mCacheSizeLimit && !calculatedRightNow) {
        mCacheStats.reset();
        maybeUpdateFileCacheSize();
      }

      // If size has exceeded the size limit, evict some files
      if (cacheSize > mCacheSizeLimit) {
        evictAboveSize(
            mCacheSizeLimit * 9 / 10, CacheEventListener.EvictionReason.CACHE_FULL); // 目标值 90%
      }
    }
  }

 

Android 4.4 以下,Bitmap 内存放到 Ashmem 区(Fresco)

posted @ 2021-04-18 21:31  huansky  阅读(694)  评论(0编辑  收藏  举报