Add disk cache support for Images plugin

Summary:
Tracks images in disk cache. It seems performant even with 500+ images in disk cache. Sidebar displays the local path for an image when that image is selected. Shows total size of images in disk cache. 'Clear Cache' clears the disk cache.

For now we unpack the async cache request in the plugin, should implement a `getSync()` method on `bufferedDiskCache` in the future. For some reason Flipper doesn't work with a blocking call (https://fburl.com/smj0s4li).

Reviewed By: defHLT

Differential Revision: D20001062

fbshipit-source-id: 1e7a7900e9f42d05e3bf30472e57cd643caa5aca
This commit is contained in:
Mathias Fleig Mortensen
2020-03-27 06:29:20 -07:00
committed by Facebook GitHub Bot
parent 5473ba6144
commit 57c573d973
3 changed files with 126 additions and 43 deletions

View File

@@ -71,6 +71,9 @@ android {
testImplementation deps.testRules
testImplementation deps.hamcrest
testImplementation deps.junit
api 'com.parse.bolts:bolts-tasks:1.4.0'
api 'com.parse.bolts:bolts-applinks:1.4.0'
}
}

View File

@@ -10,7 +10,11 @@ package com.facebook.flipper.plugins.fresco;
import android.graphics.Bitmap;
import android.util.Base64;
import android.util.Pair;
import bolts.Continuation;
import bolts.Task;
import com.facebook.cache.common.CacheKey;
import com.facebook.cache.common.SimpleCacheKey;
import com.facebook.cache.disk.DiskStorage;
import com.facebook.common.internal.ByteStreams;
import com.facebook.common.internal.Predicate;
import com.facebook.common.memory.PooledByteBuffer;
@@ -42,6 +46,7 @@ import com.facebook.imagepipeline.debug.DebugImageTracker;
import com.facebook.imagepipeline.debug.FlipperImageTracker;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imageutils.BitmapUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -50,6 +55,7 @@ import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
/**
@@ -175,7 +181,11 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
.dumpCacheContent();
try {
responder.success(getImageList(bitmapMemoryCache, encodedMemoryCache));
responder.success(
getImageList(
bitmapMemoryCache,
encodedMemoryCache,
imagePipelineFactory.getMainFileCache().getDumpInfo().entries));
mPerfLogger.endMarker("Sonar.Fresco.listImages");
} finally {
bitmapMemoryCache.release();
@@ -195,8 +205,8 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
}
mPerfLogger.startMarker("Sonar.Fresco.getImage");
String imageId = params.getString("imageId");
CacheKey cacheKey = mFlipperImageTracker.getCacheKey(imageId);
final String imageId = params.getString("imageId");
final CacheKey cacheKey = mFlipperImageTracker.getCacheKey(imageId);
if (cacheKey == null) {
respondError(responder, "ImageId " + imageId + " was evicted from cache");
mPerfLogger.cancelMarker("Sonar.Fresco.getImage");
@@ -204,33 +214,35 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
}
final ImagePipelineFactory imagePipelineFactory = Fresco.getImagePipelineFactory();
// load from bitmap cache
// try to load from bitmap cache
CloseableReference<CloseableImage> ref =
imagePipelineFactory.getBitmapCountingMemoryCache().get(cacheKey);
try {
if (ref != null) {
loadFromBitmapCache(ref, imageId, cacheKey, responder);
} else {
mPerfLogger.endMarker("Sonar.Fresco.getImage");
return;
}
} finally {
CloseableReference.closeSafely(ref);
}
// try to load from encoded cache
CloseableReference<PooledByteBuffer> encodedRef =
imagePipelineFactory.getEncodedCountingMemoryCache().get(cacheKey);
try {
if (encodedRef != null) {
loadFromEncodedCache(encodedRef, imageId, cacheKey, responder);
} else {
respondError(responder, "no bitmap withId=" + imageId);
mPerfLogger.cancelMarker("Sonar.Fresco.getImage");
mPerfLogger.endMarker("Sonar.Fresco.getImage");
return;
}
} finally {
CloseableReference.closeSafely(encodedRef);
}
}
} finally {
CloseableReference.closeSafely(ref);
}
mPerfLogger.endMarker("Sonar.Fresco.getImage");
// try to load from disk
loadFromDisk(imageId, cacheKey, responder);
}
private void loadFromBitmapCache(
@@ -245,7 +257,12 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
responder.success(
getImageData(
imageId, encodedBitmap, bitmap, mFlipperImageTracker.getUriString(cacheKey)));
imageId,
mFlipperImageTracker.getUriString(cacheKey),
bitmap.getWidth(),
bitmap.getHeight(),
bitmap.getSizeInBytes(),
encodedBitmap));
} else {
// TODO: T48376327, it might happened that ref.get() may not be casted to
// CloseableBitmap, this issue is tracked in the before mentioned task
@@ -268,17 +285,49 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
}
responder.success(
new FlipperObject.Builder()
.put("imageId", imageId)
.put("uri", mFlipperImageTracker.getUriString(cacheKey))
.put("width", dimensions.first)
.put("height", dimensions.second)
.put("sizeBytes", encodedArray.length)
.put(
"data",
"data:image/jpeg;base64,"
+ Base64.encodeToString(encodedArray, Base64.DEFAULT))
.build());
getImageData(
imageId,
mFlipperImageTracker.getUriString(cacheKey),
dimensions.first,
dimensions.second,
encodedArray.length,
dataFromEncodedArray(encodedArray)));
}
private void loadFromDisk(
final String imageId, final CacheKey cacheKey, final FlipperResponder responder) {
Task<EncodedImage> t =
Fresco.getImagePipelineFactory()
.getMainBufferedDiskCache()
.get(cacheKey, new AtomicBoolean(false));
t.continueWith(
new Continuation<EncodedImage, Void>() {
public Void then(Task<EncodedImage> task) throws Exception {
if (task.isCancelled() || task.isFaulted()) {
respondError(responder, "no bitmap withId=" + imageId);
mPerfLogger.cancelMarker("Sonar.Fresco.getImage");
return null;
}
final EncodedImage image = task.getResult();
try {
byte[] encodedArray = ByteStreams.toByteArray(image.getInputStream());
responder.success(
getImageData(
imageId,
mFlipperImageTracker.getLocalPath(cacheKey),
image.getWidth(),
image.getHeight(),
encodedArray.length,
dataFromEncodedArray(encodedArray)));
} finally {
EncodedImage.closeSafely(image);
}
mPerfLogger.endMarker("Sonar.Fresco.getImage");
return null;
}
});
}
});
@@ -350,9 +399,14 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
}
}
private static String dataFromEncodedArray(byte[] encodedArray) {
return "data:image/jpeg;base64," + Base64.encodeToString(encodedArray, Base64.DEFAULT);
}
private FlipperObject getImageList(
final CountingMemoryCacheInspector.DumpInfo bitmapMemoryCache,
final CountingMemoryCacheInspector.DumpInfo encodedMemoryCache) {
final CountingMemoryCacheInspector.DumpInfo encodedMemoryCache,
final List<DiskStorage.DiskDumpInfoEntry> diskEntries) {
return new FlipperObject.Builder()
.put(
"levels",
@@ -363,7 +417,8 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
// encoded
.put(getUsedStats("Used encoded images", encodedMemoryCache))
.put(getCachedStats("Cached encoded images", encodedMemoryCache))
// TODO (t31947642): list images on disk
// disk
.put(getDiskStats("Disk images", diskEntries))
.build())
.build();
}
@@ -388,15 +443,25 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
.build();
}
private FlipperObject getDiskStats(
final String cacheType, List<DiskStorage.DiskDumpInfoEntry> diskEntries) {
return new FlipperObject.Builder()
.put("cacheType", cacheType)
.put("clearKey", "disk")
.put("sizeBytes", Fresco.getImagePipelineFactory().getMainFileCache().getSize())
.put("imageIds", buildImageIdListDisk(diskEntries))
.build();
}
private static FlipperObject getImageData(
String imageID, String encodedBitmap, CloseableBitmap bitmap, String uriString) {
String imageID, String uriString, int width, int height, int sizeBytes, String data) {
return new FlipperObject.Builder()
.put("imageId", imageID)
.put("uri", uriString)
.put("width", bitmap.getWidth())
.put("height", bitmap.getHeight())
.put("sizeBytes", bitmap.getSizeInBytes())
.put("data", encodedBitmap)
.put("width", width)
.put("height", height)
.put("sizeBytes", sizeBytes)
.put("data", data)
.build();
}
@@ -414,7 +479,6 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
}
private FlipperArray buildImageIdList(List<DumpInfoEntry<CacheKey, CloseableImage>> images) {
FlipperArray.Builder builder = new FlipperArray.Builder();
for (DumpInfoEntry<CacheKey, CloseableImage> entry : images) {
final FlipperImageTracker.ImageDebugData imageDebugData =
@@ -429,6 +493,22 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin
return builder.build();
}
private FlipperArray buildImageIdListDisk(List<DiskStorage.DiskDumpInfoEntry> diskEntries) {
FlipperArray.Builder builder = new FlipperArray.Builder();
for (DiskStorage.DiskDumpInfoEntry entry : diskEntries) {
final CacheKey entryCacheKey = new SimpleCacheKey(entry.id, true);
final FlipperImageTracker.ImageDebugData imageDebugData =
mFlipperImageTracker.getImageDebugData(entryCacheKey);
if (imageDebugData == null) {
builder.put(mFlipperImageTracker.trackImage(entry.path, entryCacheKey).getUniqueId());
} else {
builder.put(imageDebugData.getUniqueId());
}
}
return builder.build();
}
private String bitmapToBase64Preview(Bitmap bitmap, PlatformBitmapFactory bitmapFactory) {
if (bitmap.getWidth() < BITMAP_SCALING_THRESHOLD_WIDTH
&& bitmap.getHeight() < BITMAP_SCALING_THRESHOLD_HEIGHT) {

View File

@@ -91,7 +91,7 @@ ext.deps = [
testCore : 'androidx.test:core:1.1.0',
testRules : 'androidx.test:rules:1.1.0',
// Plugin dependencies
frescoFlipper : 'com.facebook.fresco:flipper:2.0.0',
frescoStetho : 'com.facebook.fresco:stetho:2.0.0',
fresco : 'com.facebook.fresco:fresco:2.0.0'
frescoFlipper : 'com.facebook.fresco:flipper:2.2.0',
frescoStetho : 'com.facebook.fresco:stetho:2.2.0',
fresco : 'com.facebook.fresco:fresco:2.2.0'
]