Fresco的緩存機制

Fresco的圖片獲取及緩存由ImagePipeline模塊實現,具體見下圖:


7T.png

三級緩存

1.Bitmap緩存

Bitmap緩存存儲Bitmap對象,這些Bitmap對象可以立刻用來顯示或者用于后處理

在5.0以下系統,Bitmap緩存位于ashmem,這樣Bitmap對象的創建和釋放將不會引發GC,更少的GC會使你的APP運行得更加流暢。

5.0及其以上系統,相比之下,內存管理有了很大改進,所以Bitmap緩存直接位于Java的heap上。

當應用在后臺運行時,該內存會被清空。

2.未解碼圖片的內存緩存

這個緩存存儲的是原始壓縮格式的圖片。從該緩存取到的圖片在使用之前,需要先進行解碼。

如果有調整大小,旋轉,或者WebP編碼轉換工作需要完成,這些工作會在解碼之前進行。

APP在后臺時,這個緩存同樣會被清空。

3.文件緩存

和未解碼的內存緩存相似,文件緩存存儲的是未解碼的原始壓縮格式的圖片,在使用之前同樣需要經過解碼等處理。

圖片獲取順序

和內存緩存不一樣,APP在后臺時,內容是不會被清空的。即使關機也不會。用戶可以隨時用系統的設置菜單中進行清空緩存操作。
Fresco介紹:Android的一個新圖片庫中,我們已經知道Fresco的緩存是由Producer/Consumer的框架來實現的。
圖片獲取是由各級Producer實現的,而將獲取到的圖片添加到緩存中是由各級Cusumer來實現的。
關于如何使用各級Producer獲取圖片的順序見:\imagepipeline\src\main\java\com\facebook\imagepipeline\core\ProducerSequenceFactory.java
從如下函數可以看出整個的處理過程:

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 {
    String uriString = uri.toString();
    if (uriString.length() > 30) {
      uriString = uriString.substring(0, 30) + "...";
    }
    throw new RuntimeException("Unsupported uri scheme! Uri is: " + uriString);
  }
}

我們先看getNetworkFetchSequence(),即從網絡中加載圖片需要經過怎樣的處理:

  /**
   * swallow result if prefetch -> bitmap cache get ->
   * background thread hand-off -> multiplex -> bitmap cache -> decode -> multiplex ->
   * encoded cache -> disk cache -> (webp transcode) -> network fetch.
   */
  private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() {
    if (mNetworkFetchSequence == null) {
      mNetworkFetchSequence =
          newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());
    }
    return mNetworkFetchSequence;
  }

該函數主要是返回newBitmapCacheGetToDecodeSequence()創建的Producer序列。分為兩部分,一部分是從Bitmap緩存獲取數據到未解碼圖片的內存緩存的Producer序列,另一部分是從網絡獲取數據到未解碼圖片的內存緩存的Producer序列。

從Bitmap緩存到未解碼圖片的內存緩存的Producer序列

具體實現如下:

  /**
   * Same as {@code newBitmapCacheGetToBitmapCacheSequence} but with an extra DecodeProducer.
   * @param inputProducer producer providing the input to the decode
   * @return bitmap cache get to decode sequence
   */
  private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToDecodeSequence(
      Producer<EncodedImage> inputProducer) {
    DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(inputProducer);
    return newBitmapCacheGetToBitmapCacheSequence(decodeProducer);
  }
    /**
   * Bitmap cache get -> thread hand off -> multiplex -> bitmap cache
   * @param inputProducer producer providing the input to the bitmap cache
   * @return bitmap cache get to bitmap cache sequence
   */
  private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToBitmapCacheSequence(
      Producer<CloseableReference<CloseableImage>> inputProducer) {
    BitmapMemoryCacheProducer bitmapMemoryCacheProducer =
        mProducerFactory.newBitmapMemoryCacheProducer(inputProducer);
    BitmapMemoryCacheKeyMultiplexProducer bitmapKeyMultiplexProducer =
        mProducerFactory.newBitmapMemoryCacheKeyMultiplexProducer(bitmapMemoryCacheProducer);
    ThreadHandoffProducer<CloseableReference<CloseableImage>> threadHandoffProducer =
        mProducerFactory.newBackgroundThreadHandoffProducer(
            bitmapKeyMultiplexProducer,
            mThreadHandoffProducerQueue);
    return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);
  }
BitmapMemoryCacheGetProducer

對應圖1中的”Memory Cache Read”繼承了 BitmapMemoryCacheProducer類,只從Bitmap緩存中讀取數據,只有該Producer是在UI線程中執行的。

public class BitmapMemoryCacheGetProducer extends BitmapMemoryCacheProducer {

  @VisibleForTesting static final String PRODUCER_NAME = "BitmapMemoryCacheGetProducer";

  public BitmapMemoryCacheGetProducer(
      MemoryCache<CacheKey, CloseableImage> memoryCache,
      CacheKeyFactory cacheKeyFactory,
      Producer<CloseableReference<CloseableImage>> inputProducer) {
    super(memoryCache, cacheKeyFactory, inputProducer);
  }

  @Override
  protected Consumer<CloseableReference<CloseableImage>> wrapConsumer(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final CacheKey cacheKey) {
    // since this cache is read-only, we can pass our consumer directly to the next producer
    return consumer;
  }
}

上述Producer流中的ThreadHandoffProducer之后的圖片獲取都在非UI線程中獲取,即圖1中綠色的部分。

BitmapMemoryCacheProducer

該類與BitmapMemoryCacheGetProducer的不同之處是,它在緩存中不存在數據時,會創建相應的Consumer,使用mMemoryCache.cache(cacheKey, newResult)將解壓后的圖片數據緩存到內存中去。

public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {
...
  @Override
  public void produceResults(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final ProducerContext producerContext) {
      ...
    final CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext);

    CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey);

    if (cachedReference != null) {
      boolean isFinal = cachedReference.get().getQualityInfo().isOfFullQuality();
      if (isFinal) {
        listener.onProducerFinishWithSuccess(
            requestId,
            getProducerName(),
            listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "true") : null);
        consumer.onProgressUpdate(1f);
      }
      consumer.onNewResult(cachedReference, isFinal);
      cachedReference.close();
      if (isFinal) {
        return;
      }
    }

    if (producerContext.getLowestPermittedRequestLevel().getValue() >=
        ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE.getValue()) {
      listener.onProducerFinishWithSuccess(
          requestId,
          getProducerName(),
          listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
      consumer.onNewResult(null, true);
      return;
    }

    Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer, cacheKey);
    listener.onProducerFinishWithSuccess(
        requestId,
        getProducerName(),
        listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
    mInputProducer.produceResults(wrappedConsumer, producerContext);
  }

  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) {
        // ignore invalid intermediate results and forward the null result if last
        if (newResult == null) {
          if (isLast) {
            getConsumer().onNewResult(null, true);
          }
          return;
        }
        // stateful results cannot be cached and are just forwarded
        if (newResult.get().isStateful()) {
          getConsumer().onNewResult(newResult, isLast);
          return;
        }
        // if the intermediate result is not of a better quality than the cached result,
        // forward the already cached result and don't cache the new result.
        if (!isLast) {
          CloseableReference<CloseableImage> currentCachedResult = mMemoryCache.get(cacheKey);
          if (currentCachedResult != null) {
            try {
              QualityInfo newInfo = newResult.get().getQualityInfo();
              QualityInfo cachedInfo = currentCachedResult.get().getQualityInfo();
              if (cachedInfo.isOfFullQuality() || cachedInfo.getQuality() >= newInfo.getQuality()) {
                getConsumer().onNewResult(currentCachedResult, false);
                return;
              }
            } finally {
              CloseableReference.closeSafely(currentCachedResult);
            }
          }
        }
        // cache and forward the new result
        CloseableReference<CloseableImage> newCachedResult =
            mMemoryCache.cache(cacheKey, newResult);
        try {
          if (isLast) {
            getConsumer().onProgressUpdate(1f);
          }
          getConsumer().onNewResult(
              (newCachedResult != null) ? newCachedResult : newResult, isLast);
        } finally {
          CloseableReference.closeSafely(newCachedResult);
        }
      }
    };
  }

}
DecodeProducer

從未解碼圖片的內存緩存區獲取數據并做解壓處理,對應圖1中的”Decode”。

至此,從Bitmap獲取圖片需要使用到的Producer的順序及如何處理已經整理完畢。

從網絡獲取數據到未解碼圖片的內存緩存的Producer序列

/**
   * multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch.
   */
  private synchronized Producer<EncodedImage> getCommonNetworkFetchToEncodedMemorySequence() {
    if (mCommonNetworkFetchToEncodedMemorySequence == null) {
      Producer<EncodedImage> inputProducer =
          newEncodedCacheMultiplexToTranscodeSequence(
              mProducerFactory.newNetworkFetchProducer(mNetworkFetcher));
      mCommonNetworkFetchToEncodedMemorySequence =
          ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);

      if (mResizeAndRotateEnabledForNetwork && !mDownsampleEnabled) {
        mCommonNetworkFetchToEncodedMemorySequence =
            mProducerFactory.newResizeAndRotateProducer(
                mCommonNetworkFetchToEncodedMemorySequence);
      }
    }
    return mCommonNetworkFetchToEncodedMemorySequence;
  }
ResizeAndRotateProducer

該類創建了TransformingConsumer 對象,對圖片做大小和角度的轉換(對應圖1中的Transform)。

public class ResizeAndRotateProducer implements Producer<EncodedImage> {
...
@Override
  public void produceResults(
      final Consumer<EncodedImage> consumer,
      final ProducerContext context) {
    mInputProducer.produceResults(new TransformingConsumer(consumer, context), context);
  }

  private class TransformingConsumer extends DelegatingConsumer<EncodedImage, EncodedImage> {
...
@Override
    protected void onNewResultImpl(@Nullable EncodedImage newResult, boolean isLast) {
      if (mIsCancelled) {
        return;
      }
      if (newResult == null) {
        if (isLast) {
          getConsumer().onNewResult(null, true);
        }
        return;
      }
      TriState shouldTransform =
          shouldTransform(mProducerContext.getImageRequest(), newResult);
      // ignore the intermediate result if we don't know what to do with it
      if (!isLast && shouldTransform == TriState.UNSET) {
        return;
      }
      // just forward the result if we know that it shouldn't be transformed
      if (shouldTransform != TriState.YES) {
        getConsumer().onNewResult(newResult, isLast);
        return;
      }
      // we know that the result should be transformed, hence schedule it
      if (!mJobScheduler.updateJob(newResult, isLast)) {
        return;
      }
      if (isLast || mProducerContext.isIntermediateResultExpected()) {
        mJobScheduler.scheduleJob();
      }
    }

    private void doTransform(EncodedImage encodedImage, boolean isLast) {
      mProducerContext.getListener().onProducerStart(mProducerContext.getId(), PRODUCER_NAME);
      ImageRequest imageRequest = mProducerContext.getImageRequest();
      PooledByteBufferOutputStream outputStream = mPooledByteBufferFactory.newOutputStream();
      Map<String, String> extraMap = null;
      EncodedImage ret = null;
      InputStream is = null;
      try {
        int numerator = getScaleNumerator(imageRequest, encodedImage);
        extraMap = getExtraMap(encodedImage, imageRequest, numerator);
        is = encodedImage.getInputStream();
        JpegTranscoder.transcodeJpeg(
            is,
            outputStream,
            getRotationAngle(imageRequest, encodedImage),
            numerator,
            DEFAULT_JPEG_QUALITY);
        CloseableReference<PooledByteBuffer> ref =
            CloseableReference.of(outputStream.toByteBuffer());
        try {
          ret = new EncodedImage(ref);
          ret.setImageFormat(ImageFormat.JPEG);
          try {
            ret.parseMetaData();
            mProducerContext.getListener().
                onProducerFinishWithSuccess(mProducerContext.getId(), PRODUCER_NAME, extraMap);
            getConsumer().onNewResult(ret, isLast);
          } finally {
            EncodedImage.closeSafely(ret);
          }
        } finally {
          CloseableReference.closeSafely(ref);
        }
      } catch (Exception e) {
        mProducerContext.getListener().
            onProducerFinishWithFailure(mProducerContext.getId(), PRODUCER_NAME, e, extraMap);
        getConsumer().onFailure(e);
        return;
      } finally {
        Closeables.closeQuietly(is);
        outputStream.close();
      }
    }
...
AddImageTransformMetaDataProducer

添加圖片的MetaData信息

EncodedMemoryCacheProducer

與BitmapMemoryCacheProducer類似,在緩存中不存在數據時,會創建相應的Consumer,使用 cachedResult = mMemoryCache.cache(cacheKey, ref);將圖片數據緩存到未解碼圖片的內存緩存區中.對應代碼如下:

/**
 * Memory cache producer for the encoded memory cache.
 */
public class EncodedMemoryCacheProducer implements Producer<EncodedImage> {
 ...
      Consumer<EncodedImage> consumerOfInputProducer = new DelegatingConsumer<
          EncodedImage,
          EncodedImage>(consumer) {
        @Override
        public void onNewResultImpl(EncodedImage newResult, boolean isLast) {
          // intermediate or null results are not cached, so we just forward them
          if (!isLast || newResult == null) {
            getConsumer().onNewResult(newResult, isLast);
            return;
          }
          // cache and forward the last result
          CloseableReference<PooledByteBuffer> ref = newResult.getByteBufferRef();
          if (ref != null) {
            CloseableReference<PooledByteBuffer> cachedResult;
            try {
              cachedResult = mMemoryCache.cache(cacheKey, ref);
            } finally {
              CloseableReference.closeSafely(ref);
            }
...
}
DiskCacheProducer

從disk緩存中獲取數據,如果沒有找到的話,使用NetworkFetchProducer獲取數據并創建DiskCacheConsumer對象,將數據緩存到disk中。DiskCacheConsumer的代碼如下:

private class DiskCacheConsumer extends DelegatingConsumer<EncodedImage, EncodedImage> {

  private final BufferedDiskCache mCache;
  private final CacheKey mCacheKey;

  private DiskCacheConsumer(
      final Consumer<EncodedImage> consumer,
      final BufferedDiskCache cache,
      final CacheKey cacheKey) {
    super(consumer);
    mCache = cache;
    mCacheKey = cacheKey;
  }

  @Override
  public void onNewResultImpl(EncodedImage newResult, boolean isLast) {
    if (newResult != null && isLast) {
      if (mChooseCacheByImageSize) {
        int size = newResult.getSize();
        if (size > 0 && size < mForceSmallCacheThresholdBytes) {
          mSmallImageBufferedDiskCache.put(mCacheKey, newResult);
        } else {
          mDefaultBufferedDiskCache.put(mCacheKey, newResult);
        }
       } else {
        mCache.put(mCacheKey, newResult);
       }
    }
    getConsumer().onNewResult(newResult, isLast);
  }
}
NetworkFetchProducer

從網絡中獲取圖片數據,ImagePipeline 默認使用HttpURLConnection。應用可以根據自己需求使用不同的網絡庫。

public class NetworkFetchProducer implements Producer<EncodedImage> {

  @Override
  public void produceResults(Consumer<EncodedImage> consumer, ProducerContext context) {
    context.getListener()
        .onProducerStart(context.getId(), PRODUCER_NAME);
    final FetchState fetchState = mNetworkFetcher.createFetchState(consumer, context);
    mNetworkFetcher.fetch(
        fetchState, new NetworkFetcher.Callback() {
          @Override
          public void onResponse(InputStream response, int responseLength) throws IOException {
            NetworkFetchProducer.this.onResponse(fetchState, response, responseLength);
          }
         ...

  private void onResponse(
      FetchState fetchState,
      InputStream responseData,
      int responseContentLength)
      throws IOException {
    final PooledByteBufferOutputStream pooledOutputStream;
    if (responseContentLength > 0) {
      pooledOutputStream = mPooledByteBufferFactory.newOutputStream(responseContentLength);
    } else {
      pooledOutputStream = mPooledByteBufferFactory.newOutputStream();
    }
    final byte[] ioArray = mByteArrayPool.get(READ_SIZE);
    try {
      int length;
      while ((length = responseData.read(ioArray)) >= 0) {
        if (length > 0) {
          pooledOutputStream.write(ioArray, 0, length);
          maybeHandleIntermediateResult(pooledOutputStream, fetchState);
          float progress = calculateProgress(pooledOutputStream.size(), responseContentLength);
          fetchState.getConsumer().onProgressUpdate(progress);
        }
      }
      mNetworkFetcher.onFetchCompletion(fetchState, pooledOutputStream.size());
      handleFinalResult(pooledOutputStream, fetchState);
    } finally {
      mByteArrayPool.release(ioArray);
      pooledOutputStream.close();
    }
  }

該類的構造函數的參數NetworkFetcher用來建立http鏈接,默認的HttpURLConnection的相應實現可以作為一個參考. 見:\imagepipeline\src\main\java\com\facebook\imagepipeline\producers\HttpUrlConnectionNetworkFetcher.java

注:關于網絡請求

Fresco給出了OkHttp的實現。如果需要使用OkHttp, 使用下面的依賴配置

dependencies {
  // your project's other dependencies
  compile "com.facebook.fresco:fresco:0.9.0+"
  compile 'com.facebook.fresco:imagepipeline-okhttp:0.9.0+'}

配置Image pipeline這時也有一些不同,不再使用ImagePipelineConfig.newBuilder,而是使用OkHttpImagePipelineConfigFactory:

Context context;OkHttpClient okHttpClient; // build on your own
ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
    .newBuilder(context, okHttpClient)
    . // other setters
    . // setNetworkFetchProducer is already called for you
    .build();
Fresco.initialize(context, config);

另外也可以通過繼承NetworkFetchProducer來使用自定義的網絡層,此時在配置Image pipeline時,把producer傳遞給Image pipeline。

ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
   .setNetworkFetchProducer(myNetworkFetchProducer);
   . // other setters
  .build();Fresco.initialize(context, config);

總結

從上面圖片獲取及緩存的整個過程可以看到Producer/Consumer的框架的強大之處。以上是個人的一些簡單總結,有什么不對的地方麻煩指正。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容