/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imagepipeline.producers; import com.facebook.cache.common.CacheKey; import com.facebook.common.internal.ImmutableMap; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.references.CloseableReference; import com.facebook.imagepipeline.cache.CacheKeyFactory; import com.facebook.imagepipeline.cache.MemoryCache; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.Postprocessor; import com.facebook.imagepipeline.request.RepeatedPostprocessor; /** * Memory cache producer for the bitmap memory cache. */ public class PostprocessedBitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> { public static final String PRODUCER_NAME = "PostprocessedBitmapMemoryCacheProducer"; @VisibleForTesting static final String VALUE_FOUND = "cached_value_found"; private final MemoryCache<CacheKey, CloseableImage> mMemoryCache; private final CacheKeyFactory mCacheKeyFactory; private final Producer<CloseableReference<CloseableImage>> mInputProducer; public PostprocessedBitmapMemoryCacheProducer( MemoryCache<CacheKey, CloseableImage> memoryCache, CacheKeyFactory cacheKeyFactory, Producer<CloseableReference<CloseableImage>> inputProducer) { mMemoryCache = memoryCache; mCacheKeyFactory = cacheKeyFactory; mInputProducer = inputProducer; } @Override public void produceResults( final Consumer<CloseableReference<CloseableImage>> consumer, final ProducerContext producerContext) { final ProducerListener listener = producerContext.getListener(); final String requestId = producerContext.getId(); final ImageRequest imageRequest = producerContext.getImageRequest(); final Object callerContext = producerContext.getCallerContext(); // If there's no postprocessor or the postprocessor doesn't require caching, forward results. final Postprocessor postprocessor = imageRequest.getPostprocessor(); if (postprocessor == null || postprocessor.getPostprocessorCacheKey() == null) { mInputProducer.produceResults(consumer, producerContext); return; } listener.onProducerStart(requestId, getProducerName()); final CacheKey cacheKey = mCacheKeyFactory.getPostprocessedBitmapCacheKey(imageRequest, callerContext); CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey); if (cachedReference != null) { listener.onProducerFinishWithSuccess( requestId, getProducerName(), listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "true") : null); listener.onUltimateProducerReached(requestId, PRODUCER_NAME, true); consumer.onProgressUpdate(1.0f); consumer.onNewResult(cachedReference, Consumer.IS_LAST); cachedReference.close(); } else { final boolean isRepeatedProcessor = postprocessor instanceof RepeatedPostprocessor; Consumer<CloseableReference<CloseableImage>> cachedConsumer = new CachedPostprocessorConsumer( consumer, cacheKey, isRepeatedProcessor, mMemoryCache); listener.onProducerFinishWithSuccess( requestId, getProducerName(), listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null); mInputProducer.produceResults(cachedConsumer, producerContext); } } public static class CachedPostprocessorConsumer extends DelegatingConsumer< CloseableReference<CloseableImage>, CloseableReference<CloseableImage>> { private final CacheKey mCacheKey; private final boolean mIsRepeatedProcessor; private final MemoryCache<CacheKey, CloseableImage> mMemoryCache; public CachedPostprocessorConsumer(final Consumer<CloseableReference<CloseableImage>> consumer, final CacheKey cacheKey, final boolean isRepeatedProcessor, final MemoryCache<CacheKey, CloseableImage> memoryCache) { super(consumer); this.mCacheKey = cacheKey; this.mIsRepeatedProcessor = isRepeatedProcessor; this.mMemoryCache = memoryCache; } @Override protected void onNewResultImpl( CloseableReference<CloseableImage> newResult, @Status int status) { // ignore invalid intermediate results and forward the null result if last if (newResult == null) { if (isLast(status)) { getConsumer().onNewResult(null, status); } return; } // ignore intermediate results for non-repeated postprocessors if (isNotLast(status) && !mIsRepeatedProcessor) { return; } // cache and forward the new result CloseableReference<CloseableImage> newCachedResult = mMemoryCache.cache(mCacheKey, newResult); try { getConsumer().onProgressUpdate(1f); getConsumer().onNewResult( (newCachedResult != null) ? newCachedResult : newResult, status); } finally { CloseableReference.closeSafely(newCachedResult); } } } protected String getProducerName() { return PRODUCER_NAME; } }