From 57c573d97302e50375a6583b177331addd3801d3 Mon Sep 17 00:00:00 2001 From: Mathias Fleig Mortensen Date: Fri, 27 Mar 2020 06:29:20 -0700 Subject: [PATCH] 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 --- android/build.gradle | 3 + .../plugins/fresco/FrescoFlipperPlugin.java | 160 +++++++++++++----- build.gradle | 6 +- 3 files changed, 126 insertions(+), 43 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 59905916b..6972c0aa1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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' } } diff --git a/android/plugins/fresco/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java b/android/plugins/fresco/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java index 66758b284..ac156b6ac 100644 --- a/android/plugins/fresco/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java +++ b/android/plugins/fresco/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java @@ -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 ref = imagePipelineFactory.getBitmapCountingMemoryCache().get(cacheKey); try { if (ref != null) { loadFromBitmapCache(ref, imageId, cacheKey, responder); - } else { - // try to load from encoded cache - CloseableReference 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"); - return; - } - } finally { - CloseableReference.closeSafely(encodedRef); - } + mPerfLogger.endMarker("Sonar.Fresco.getImage"); + return; } } finally { CloseableReference.closeSafely(ref); } - mPerfLogger.endMarker("Sonar.Fresco.getImage"); + // try to load from encoded cache + CloseableReference encodedRef = + imagePipelineFactory.getEncodedCountingMemoryCache().get(cacheKey); + try { + if (encodedRef != null) { + loadFromEncodedCache(encodedRef, imageId, cacheKey, responder); + mPerfLogger.endMarker("Sonar.Fresco.getImage"); + return; + } + } finally { + CloseableReference.closeSafely(encodedRef); + } + + // 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 t = + Fresco.getImagePipelineFactory() + .getMainBufferedDiskCache() + .get(cacheKey, new AtomicBoolean(false)); + + t.continueWith( + new Continuation() { + public Void then(Task 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 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 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> images) { - FlipperArray.Builder builder = new FlipperArray.Builder(); for (DumpInfoEntry entry : images) { final FlipperImageTracker.ImageDebugData imageDebugData = @@ -429,6 +493,22 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin return builder.build(); } + private FlipperArray buildImageIdListDisk(List 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) { diff --git a/build.gradle b/build.gradle index 7b4678ee6..ab8408e09 100644 --- a/build.gradle +++ b/build.gradle @@ -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' ]