diff --git a/android/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java b/android/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java index 545c3e50b..6e7d593b2 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java +++ b/android/src/main/java/com/facebook/flipper/plugins/fresco/FrescoFlipperPlugin.java @@ -13,6 +13,7 @@ import com.facebook.common.internal.Predicate; import com.facebook.common.memory.manager.DebugMemoryManager; import com.facebook.common.memory.manager.NoOpDebugMemoryManager; import com.facebook.common.references.CloseableReference; +import com.facebook.common.references.SharedReference; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.info.ImageLoadStatus; import com.facebook.drawee.backends.pipeline.info.ImageOriginUtils; @@ -31,28 +32,31 @@ import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; import com.facebook.imagepipeline.cache.CountingMemoryCacheInspector; import com.facebook.imagepipeline.cache.CountingMemoryCacheInspector.DumpInfoEntry; import com.facebook.imagepipeline.core.ImagePipelineFactory; +import com.facebook.imagepipeline.debug.CloseableReferenceLeakTracker; 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 org.json.JSONArray; - import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.annotation.Nullable; +import org.json.JSONArray; /** * Allows Sonar to display the contents of Fresco's caches. This is useful for developers to debug * what images are being held in cache as they navigate through their app. */ -public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements ImagePerfDataListener { +public class FrescoFlipperPlugin extends BufferingFlipperPlugin + implements ImagePerfDataListener, CloseableReferenceLeakTracker.Listener { private static final String FRESCO_EVENT = "events"; private static final String FRESCO_DEBUGOVERLAY_EVENT = "debug_overlay_event"; + private static final String FRESCO_CLOSEABLE_REFERENCE_LEAK_EVENT = + "closeable_reference_leak_event"; private static final int BITMAP_PREVIEW_WIDTH = 150; private static final int BITMAP_PREVIEW_HEIGHT = 150; @@ -82,7 +86,8 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image @Nullable FlipperObjectHelper flipperObjectHelper, DebugMemoryManager memoryManager, FlipperPerfLogger perfLogger, - @Nullable FrescoFlipperDebugPrefHelper debugPrefHelper) { + @Nullable FrescoFlipperDebugPrefHelper debugPrefHelper, + @Nullable CloseableReferenceLeakTracker closeableReferenceLeakTracker) { mFlipperImageTracker = imageTracker instanceof FlipperImageTracker ? (FlipperImageTracker) imageTracker @@ -92,6 +97,10 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image mMemoryManager = memoryManager; mPerfLogger = perfLogger; mDebugPrefHelper = debugPrefHelper; + + if (closeableReferenceLeakTracker != null) { + closeableReferenceLeakTracker.setListener(this); + } } public FrescoFlipperPlugin() { @@ -101,6 +110,7 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image null, new NoOpDebugMemoryManager(), new NoOpFlipperPerfLogger(), + null, null); } @@ -130,7 +140,8 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image imagePipelineFactory.getBitmapCountingMemoryCache()) .dumpCacheContent(); - final FlipperArray memoryCacheSharedEntries = buildImageIdList(memoryCache.sharedEntries); + final FlipperArray memoryCacheSharedEntries = + buildImageIdList(memoryCache.sharedEntries); final FlipperArray memoryCacheLRUEntries = buildImageIdList(memoryCache.lruEntries); final FlipperArray.Builder imageIDListBuilder = new FlipperArray.Builder(); for (int i = 0; i < memoryCacheSharedEntries.length(); ++i) { @@ -306,27 +317,27 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image CountingMemoryCacheInspector.DumpInfo memoryCache, FlipperArray memoryCacheSharedEntries, FlipperArray memoryCacheLRUEntries) { - return new FlipperObject.Builder() - .put( - "levels", - new FlipperArray.Builder() - .put( - new FlipperObject.Builder() - .put("cacheType", "On screen bitmaps") - .put("sizeBytes", memoryCache.size - memoryCache.lruSize) - .put("imageIds", memoryCacheSharedEntries) - .build()) - .put( - new FlipperObject.Builder() - .put("cacheType", "Bitmap memory cache") - .put("clearKey", "memory") - .put("sizeBytes", memoryCache.size) - .put("maxSizeBytes", memoryCache.maxSize) - .put("imageIds", memoryCacheLRUEntries) - .build()) - // TODO (t31947642): list images on disk - .build()) - .build(); + return new FlipperObject.Builder() + .put( + "levels", + new FlipperArray.Builder() + .put( + new FlipperObject.Builder() + .put("cacheType", "On screen bitmaps") + .put("sizeBytes", memoryCache.size - memoryCache.lruSize) + .put("imageIds", memoryCacheSharedEntries) + .build()) + .put( + new FlipperObject.Builder() + .put("cacheType", "Bitmap memory cache") + .put("clearKey", "memory") + .put("sizeBytes", memoryCache.size) + .put("maxSizeBytes", memoryCache.maxSize) + .put("imageIds", memoryCacheLRUEntries) + .build()) + // TODO (t31947642): list images on disk + .build()) + .build(); } private FlipperObject getImageData( @@ -433,7 +444,8 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image return; } - FlipperImageTracker.ImageDebugData data = mFlipperImageTracker.getDebugDataForRequestId(requestId); + FlipperImageTracker.ImageDebugData data = + mFlipperImageTracker.getDebugDataForRequestId(requestId); if (data == null) { return; } @@ -495,4 +507,13 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image private static void respondError(FlipperResponder responder, String errorReason) { responder.error(new FlipperObject.Builder().put("reason", errorReason).build()); } + + @Override + public void onCloseableReferenceLeak(SharedReference reference) { + final FlipperObject.Builder builder = + new FlipperObject.Builder() + .put("identityHashCode", System.identityHashCode(reference)) + .put("className", reference.get().getClass().getName()); + send(FRESCO_CLOSEABLE_REFERENCE_LEAK_EVENT, builder.build()); + } } diff --git a/src/plugins/fresco/api.js b/src/plugins/fresco/api.js index 73fc6f934..7c3d79a0d 100644 --- a/src/plugins/fresco/api.js +++ b/src/plugins/fresco/api.js @@ -67,3 +67,8 @@ export type ImageEvent = { export type FrescoDebugOverlayEvent = {| enabled: boolean, |}; + +export type AndroidCloseableReferenceLeakEvent = {| + identityHashCode: string, + className: string, +|}; diff --git a/src/plugins/fresco/index.js b/src/plugins/fresco/index.js index 1b63676e5..0971b7cb1 100644 --- a/src/plugins/fresco/index.js +++ b/src/plugins/fresco/index.js @@ -12,6 +12,7 @@ import type { ImagesListResponse, ImageEvent, FrescoDebugOverlayEvent, + AndroidCloseableReferenceLeakEvent, CacheInfo, } from './api.js'; import type {ImagesMap} from './ImagePool.js'; @@ -225,6 +226,13 @@ export default class extends FlipperPlugin { this.setState({isDebugOverlayEnabled: event.enabled}); }, ); + this.client.subscribe( + 'closeable_reference_leak_event', + (event: AndroidCloseableReferenceLeakEvent) => { + // TODO(T45065440): Temporary log, to be turned into counter. + console.warn('CloseableReference leak detected:', event); + }, + ); this.imagePool = new ImagePool(this.getImage, (images: ImagesMap) => this.props.setPersistedState({imagesMap: images}), );