/* * 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.Predicate; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.references.CloseableReference; import com.facebook.imagepipeline.cache.BitmapMemoryCacheKey; 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>> { @VisibleForTesting 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(); // No point continuing if there's no postprocessor attached to this request. final Postprocessor postprocessor = imageRequest.getPostprocessor(); if (postprocessor == null) { mInputProducer.produceResults(consumer, producerContext); return; } listener.onProducerStart(requestId, getProducerName()); final CacheKey postprocessorCacheKey = postprocessor.getPostprocessorCacheKey(); final CacheKey cacheKey; CloseableReference<CloseableImage> cachedReference = null; if (postprocessorCacheKey != null) { cacheKey = mCacheKeyFactory.getPostprocessedBitmapCacheKey(imageRequest); cachedReference = mMemoryCache.get(cacheKey); } else { cacheKey = null; } if (cachedReference != null) { listener.onProducerFinishWithSuccess( requestId, getProducerName(), listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "true") : null); consumer.onProgressUpdate(1.0f); consumer.onNewResult(cachedReference, true); cachedReference.close(); } else { final boolean isRepeatedProcessor = postprocessor instanceof RepeatedPostprocessor; final String processorName = postprocessor.getClass().getName(); Consumer<CloseableReference<CloseableImage>> cachedConsumer = new CachedPostprocessorConsumer( consumer, cacheKey, isRepeatedProcessor, processorName, 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 String mProcessorName; private final MemoryCache<CacheKey, CloseableImage> mMemoryCache; public CachedPostprocessorConsumer(final Consumer<CloseableReference<CloseableImage>> consumer, final CacheKey cacheKey, final boolean isRepeatedProcessor, final String processorName, final MemoryCache<CacheKey, CloseableImage> memoryCache) { super(consumer); this.mCacheKey = cacheKey; this.mIsRepeatedProcessor = isRepeatedProcessor; this.mProcessorName = processorName; this.mMemoryCache = memoryCache; } @Override protected void onNewResultImpl(CloseableReference<CloseableImage> newResult, boolean isLast) { if (!isLast && !mIsRepeatedProcessor) { return; } // Given a null result, we just pass it on. if (newResult == null) { getConsumer().onNewResult(null, isLast); return; } // cache and forward the new result final CloseableReference<CloseableImage> newCachedResult; if (mCacheKey != null) { mMemoryCache.removeAll( new Predicate<CacheKey>() { @Override public boolean apply(CacheKey cacheKey) { if (cacheKey instanceof BitmapMemoryCacheKey) { return mProcessorName.equals( ((BitmapMemoryCacheKey) cacheKey).getPostprocessorName()); } return false; } }); newCachedResult = mMemoryCache.cache(mCacheKey, newResult); } else { newCachedResult = newResult; } try { getConsumer().onProgressUpdate(1f); getConsumer().onNewResult( (newCachedResult != null) ? newCachedResult : newResult, isLast); } finally { CloseableReference.closeSafely(newCachedResult); } } } protected String getProducerName() { return PRODUCER_NAME; } }