Skip to content

Commit

Permalink
Added caching of postprocessor output
Browse files Browse the repository at this point in the history
  • Loading branch information
massimocarli authored and tyronen committed Jul 20, 2015
1 parent cfbc7f0 commit 96111f2
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
package com.facebook.imagepipeline.cache;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import java.util.Locale;

import android.net.Uri;

import com.facebook.cache.common.CacheKey;
import com.facebook.common.internal.Objects;
import com.facebook.common.internal.Preconditions;
Expand All @@ -25,41 +24,52 @@
/**
* Cache key for BitmapMemoryCache
*/
@Immutable
public class BitmapMemoryCacheKey implements CacheKey {

private final String mSourceString;
private final @Nullable ResizeOptions mResizeOptions;
private final boolean mAutoRotated;
private final ImageDecodeOptions mImageDecodeOptions;
private final @Nullable CacheKey mPostprocessorCacheKey;
private final @Nullable String mPostprocessorName;
private final int mHash;

public BitmapMemoryCacheKey(
String sourceString,
@Nullable ResizeOptions resizeOptions,
boolean autoRotated,
ImageDecodeOptions imageDecodeOptions) {
ImageDecodeOptions imageDecodeOptions,
@Nullable CacheKey postprocessorCacheKey,
@Nullable String postprocessorName) {
mSourceString = Preconditions.checkNotNull(sourceString);
mResizeOptions = resizeOptions;
mAutoRotated = autoRotated;
mImageDecodeOptions = imageDecodeOptions;
mPostprocessorCacheKey = postprocessorCacheKey;
mPostprocessorName = postprocessorName;
mHash = HashCodeUtil.hashCode(
sourceString.hashCode(),
(resizeOptions != null) ? resizeOptions.hashCode() : 0,
autoRotated ? Boolean.TRUE.hashCode() : Boolean.FALSE.hashCode(),
mImageDecodeOptions);
mImageDecodeOptions,
mPostprocessorCacheKey,
postprocessorName);
}

@Override
public boolean equals(Object o) {
if (!(o instanceof BitmapMemoryCacheKey)) {
return false;
}

BitmapMemoryCacheKey otherKey = (BitmapMemoryCacheKey) o;
return mHash == otherKey.mHash &&
mSourceString.equals(otherKey.mSourceString) &&
Objects.equal(this.mResizeOptions, otherKey.mResizeOptions) &&
mAutoRotated == otherKey.mAutoRotated &&
Objects.equal(mImageDecodeOptions, otherKey.mImageDecodeOptions);
Objects.equal(mImageDecodeOptions, otherKey.mImageDecodeOptions) &&
Objects.equal(mPostprocessorCacheKey, otherKey.mPostprocessorCacheKey) &&
Objects.equal(mPostprocessorName, otherKey.mPostprocessorName);
}

@Override
Expand All @@ -71,15 +81,22 @@ public String getSourceUriString() {
return mSourceString;
}

@Nullable
public String getPostprocessorName() {
return mPostprocessorName;
}

@Override
public String toString() {
return String.format(
(Locale) null,
"%s_%s_%s_%s_%d",
"%s_%s_%s_%s_%s_%s_%d",
mSourceString,
mResizeOptions,
Boolean.toString(mAutoRotated),
mImageDecodeOptions,
mPostprocessorCacheKey,
mPostprocessorName,
mHash);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public interface CacheKeyFactory {
*/
public CacheKey getBitmapCacheKey(ImageRequest request);

/**
* @return {@link CacheKey} for doing post-processed bitmap cache lookups in the pipeline.
*/
public CacheKey getPostprocessedBitmapCacheKey(ImageRequest request);

/**
* @return {@link CacheKey} for doing encoded image lookups in the pipeline.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
import com.facebook.cache.common.CacheKey;
import com.facebook.cache.common.SimpleCacheKey;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.Postprocessor;

/**
* Default implementation of {@link CacheKeyFactory}.
*/
public class DefaultCacheKeyFactory implements CacheKeyFactory {

private static DefaultCacheKeyFactory sInstance = null;

protected DefaultCacheKeyFactory() {
Expand All @@ -37,7 +39,30 @@ public CacheKey getBitmapCacheKey(ImageRequest request) {
getCacheKeySourceUri(request.getSourceUri()).toString(),
request.getResizeOptions(),
request.getAutoRotateEnabled(),
request.getImageDecodeOptions());
request.getImageDecodeOptions(),
null,
null);
}

@Override
public CacheKey getPostprocessedBitmapCacheKey(ImageRequest request) {
final Postprocessor postprocessor = request.getPostprocessor();
final CacheKey postprocessorCacheKey;
final String postprocessorName;
if (postprocessor != null) {
postprocessorCacheKey = postprocessor.getPostprocessorCacheKey();
postprocessorName = postprocessor.getClass().getName();
} else {
postprocessorCacheKey = null;
postprocessorName = null;
}
return new BitmapMemoryCacheKey(
getCacheKeySourceUri(request.getSourceUri()).toString(),
request.getResizeOptions(),
request.getAutoRotateEnabled(),
request.getImageDecodeOptions(),
postprocessorCacheKey,
postprocessorName);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.facebook.imagepipeline.producers.NetworkFetchProducer;
import com.facebook.imagepipeline.producers.NetworkFetcher;
import com.facebook.imagepipeline.producers.NullProducer;
import com.facebook.imagepipeline.producers.PostprocessedBitmapMemoryCacheProducer;
import com.facebook.imagepipeline.producers.PostprocessorProducer;
import com.facebook.imagepipeline.producers.Producer;
import com.facebook.imagepipeline.producers.ResizeAndRotateProducer;
Expand Down Expand Up @@ -219,6 +220,12 @@ public static <T> NullProducer<T> newNullProducer() {
return new NullProducer<T>();
}

public PostprocessedBitmapMemoryCacheProducer newPostprocessorBitmapMemoryCacheProducer(
Producer<CloseableReference<CloseableImage>> nextProducer) {
return new PostprocessedBitmapMemoryCacheProducer(
mBitmapMemoryCache, mCacheKeyFactory, nextProducer);
}

public PostprocessorProducer newPostprocessorProducer(
Producer<CloseableReference<CloseableImage>> nextProducer) {
return new PostprocessorProducer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.facebook.imagepipeline.producers.LocalResourceFetchProducer;
import com.facebook.imagepipeline.producers.LocalVideoThumbnailProducer;
import com.facebook.imagepipeline.producers.NetworkFetcher;
import com.facebook.imagepipeline.producers.PostprocessedBitmapMemoryCacheProducer;
import com.facebook.imagepipeline.producers.PostprocessorProducer;
import com.facebook.imagepipeline.producers.Producer;
import com.facebook.imagepipeline.producers.RemoveImageTransformMetaDataProducer;
Expand Down Expand Up @@ -442,7 +443,9 @@ private synchronized Producer<CloseableReference<CloseableImage>> getPostprocess
if (!mPostprocessorSequences.containsKey(nextProducer)) {
PostprocessorProducer postprocessorProducer =
mProducerFactory.newPostprocessorProducer(nextProducer);
mPostprocessorSequences.put(nextProducer, postprocessorProducer);
PostprocessedBitmapMemoryCacheProducer postprocessedBitmapMemoryCacheProducer =
mProducerFactory.newPostprocessorBitmapMemoryCacheProducer(postprocessorProducer);
mPostprocessorSequences.put(nextProducer, postprocessedBitmapMemoryCacheProducer);
}
return mPostprocessorSequences.get(nextProducer);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* 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.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.android.internal.util.Predicate;

/**
* 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>> mNextProducer;

public PostprocessedBitmapMemoryCacheProducer(
MemoryCache<CacheKey, CloseableImage> memoryCache,
CacheKeyFactory cacheKeyFactory,
Producer<CloseableReference<CloseableImage>> nextProducer) {
mMemoryCache = memoryCache;
mCacheKeyFactory = cacheKeyFactory;
mNextProducer = nextProducer;
}

@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) {
mNextProducer.produceResults(consumer, producerContext);
return;
}
listener.onProducerStart(requestId, getProducerName());

final CacheKey postprocessorCacheKey = postprocessor.getPostprocessorCacheKey();
final CacheKey cacheKey;
CloseableReference<CloseableImage> cachedReference = null;
final String postprocessorName;
if (postprocessorCacheKey != null) {
cacheKey = mCacheKeyFactory.getPostprocessedBitmapCacheKey(imageRequest);
cachedReference = mMemoryCache.get(cacheKey);
postprocessorName = postprocessor.getClass().getName();
} else {
cacheKey = null;
postprocessorName = null;
}

if (cachedReference != null) {
listener.onProducerFinishWithSuccess(
requestId,
getProducerName(),
listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "true") : null);
consumer.onProgressUpdate(1f);
consumer.onNewResult(cachedReference, true);
cachedReference.close();
} else {
Consumer<CloseableReference<CloseableImage>> cachedConsumer = new CachedPostprocessorConsumer(
consumer,
cacheKey,
postprocessorName,
mMemoryCache);
listener.onProducerFinishWithSuccess(
requestId,
getProducerName(),
listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
mNextProducer.produceResults(cachedConsumer, producerContext);
}
}

public static class CachedPostprocessorConsumer extends DelegatingConsumer<
CloseableReference<CloseableImage>,
CloseableReference<CloseableImage>> {

private final CacheKey mCacheKey;
private final String mPostprocessorName;
private final MemoryCache<CacheKey, CloseableImage> mMemoryCache;

public CachedPostprocessorConsumer(final Consumer<CloseableReference<CloseableImage>> consumer,
final CacheKey cacheKey,
final String postprocessorName,
final MemoryCache<CacheKey, CloseableImage> memoryCache) {
super(consumer);
this.mCacheKey = cacheKey;
this.mPostprocessorName = postprocessorName;
this.mMemoryCache = memoryCache;
}

@Override
protected void onNewResultImpl(CloseableReference<CloseableImage> newResult, boolean isLast) {
// We should only receive final results from the postprocessor, but let's be defensive just
// in case.
if (!isLast) {
return;
}

// Given a null result, we just pass it on.
if (newResult == null) {
getConsumer().onNewResult(null, true);
return;
}

// cache and forward the new result
final CloseableReference<CloseableImage> newCachedResult;
if (mCacheKey != null) {
if (mPostprocessorName != null) {
mMemoryCache.removeAll(
new Predicate<CacheKey>() {
@Override
public boolean apply(CacheKey cacheKey) {
if (cacheKey instanceof BitmapMemoryCacheKey) {
return mPostprocessorName.equals(
((BitmapMemoryCacheKey) cacheKey).getPostprocessorName());
}
return false;
}
});
}
newCachedResult = mMemoryCache.cache(mCacheKey, newResult);
} else {
newCachedResult = newResult;
}
try {
getConsumer().onProgressUpdate(1f);
getConsumer().onNewResult(
(newCachedResult != null) ? newCachedResult : newResult, true);
} finally {
CloseableReference.closeSafely(newCachedResult);
}
}
}

protected String getProducerName() {
return PRODUCER_NAME;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@

package com.facebook.imagepipeline.request;

import javax.annotation.Nullable;

import android.graphics.Bitmap;

import com.facebook.cache.common.CacheKey;
import com.facebook.common.references.CloseableReference;
import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory;
import com.facebook.imagepipeline.nativecode.Bitmaps;
Expand Down Expand Up @@ -84,4 +87,14 @@ public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
*/
public void process(Bitmap bitmap) {
}

/**
* The default implementation of the CacheKey for a Postprocessor is null
* @return The CacheKey to use for caching. Not used if null
*/
@Override
@Nullable
public CacheKey getPostprocessorCacheKey() {
return null;
}
}
Loading

0 comments on commit 96111f2

Please sign in to comment.