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 85a2b93a2..545c3e50b 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 @@ -35,8 +35,12 @@ 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.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.annotation.Nullable; @@ -70,6 +74,7 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image private final DebugMemoryManager mMemoryManager; private final FlipperPerfLogger mPerfLogger; @Nullable private final FrescoFlipperDebugPrefHelper mDebugPrefHelper; + private final List mEvents = new ArrayList<>(); public FrescoFlipperPlugin( DebugImageTracker imageTracker, @@ -111,7 +116,62 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image @Override public void onConnect(FlipperConnection connection) { super.onConnect(connection); + connection.receive( + "getAllImageData", + new FlipperReceiver() { + @Override + public void onReceive(FlipperObject params, FlipperResponder responder) { + if (!ensureFrescoInitialized(responder)) { + return; + } + final ImagePipelineFactory imagePipelineFactory = Fresco.getImagePipelineFactory(); + final CountingMemoryCacheInspector.DumpInfo memoryCache = + new CountingMemoryCacheInspector<>( + imagePipelineFactory.getBitmapCountingMemoryCache()) + .dumpCacheContent(); + 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) { + final String imageID = memoryCacheSharedEntries.getString(i); + imageIDListBuilder.put(imageID); + } + for (int i = 0; i < memoryCacheLRUEntries.length(); ++i) { + final String imageID = memoryCacheLRUEntries.getString(i); + imageIDListBuilder.put(imageID); + } + final FlipperArray imageIDs = imageIDListBuilder.build(); + final FlipperObject levels = + getImageList(memoryCache, memoryCacheSharedEntries, memoryCacheLRUEntries); + final FlipperArray.Builder imageDataListBuilder = new FlipperArray.Builder(); + + for (int i = 0; i < imageIDs.length(); ++i) { + final String imageID = imageIDs.getString(i); + final CacheKey cacheKey = mFlipperImageTracker.getCacheKey(imageID); + if (cacheKey == null) { + continue; + } + final CloseableReference ref = + imagePipelineFactory.getBitmapCountingMemoryCache().get(cacheKey); + if (ref == null) { + continue; + } + final CloseableBitmap bitmap = (CloseableBitmap) ref.get(); + final String encodedBitmap = + bitmapToBase64Preview(bitmap.getUnderlyingBitmap(), mPlatformBitmapFactory); + imageDataListBuilder.put( + getImageData( + imageID, encodedBitmap, bitmap, mFlipperImageTracker.getUriString(cacheKey))); + } + responder.success( + new FlipperObject.Builder() + .put("levels", levels) + .put("imageDataList", imageDataListBuilder.build()) + .put("events", new FlipperArray(new JSONArray(mEvents))) + .build()); + } + }); connection.receive( "listImages", new FlipperReceiver() { @@ -127,29 +187,11 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image new CountingMemoryCacheInspector<>( imagePipelineFactory.getBitmapCountingMemoryCache()) .dumpCacheContent(); - responder.success( - 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", buildImageIdList(memoryCache.sharedEntries)) - .build()) - .put( - new FlipperObject.Builder() - .put("cacheType", "Bitmap memory cache") - .put("clearKey", "memory") - .put("sizeBytes", memoryCache.size) - .put("maxSizeBytes", memoryCache.maxSize) - .put("imageIds", buildImageIdList(memoryCache.lruEntries)) - .build()) - // TODO (t31947642): list images on disk - .build()) - .build()); + getImageList( + memoryCache, + buildImageIdList(memoryCache.sharedEntries), + buildImageIdList(memoryCache.lruEntries))); mPerfLogger.endMarker("Sonar.Fresco.listImages"); } }); @@ -185,14 +227,8 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image bitmapToBase64Preview(bitmap.getUnderlyingBitmap(), mPlatformBitmapFactory); responder.success( - new FlipperObject.Builder() - .put("imageId", imageId) - .put("uri", mFlipperImageTracker.getUriString(cacheKey)) - .put("width", bitmap.getWidth()) - .put("height", bitmap.getHeight()) - .put("sizeBytes", bitmap.getSizeInBytes()) - .put("data", encodedBitmap) - .build()); + getImageData( + imageId, encodedBitmap, bitmap, mFlipperImageTracker.getUriString(cacheKey))); mPerfLogger.endMarker("Sonar.Fresco.getImage"); } @@ -266,6 +302,45 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image } } + private FlipperObject getImageList( + 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(); + } + + private FlipperObject getImageData( + String imageID, String encodedBitmap, CloseableBitmap bitmap, String uriString) { + 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) + .build(); + } + private boolean ensureFrescoInitialized(FlipperResponder responder) { mPerfLogger.startMarker("Sonar.Fresco.ensureFrescoInitialized"); try { @@ -403,8 +478,9 @@ public class FrescoFlipperPlugin extends BufferingFlipperPlugin implements Image .put("height", imagePerfData.getOnScreenHeightPx()) .build()); } - - send(FRESCO_EVENT, response.build()); + FlipperObject responseObject = response.build(); + mEvents.add(responseObject); + send(FRESCO_EVENT, responseObject); } public void onImageVisibilityUpdated(ImagePerfData imagePerfData, int visibilityState) { diff --git a/build.gradle b/build.gradle index 1b10bce41..ae890a612 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ // This source code is licensed under the MIT license found in the LICENSE file // in the root directory of this source tree. -buildscript { +buildscript { repositories { google() jcenter() diff --git a/src/plugins/fresco/index.js b/src/plugins/fresco/index.js index c26500e59..1b63676e5 100644 --- a/src/plugins/fresco/index.js +++ b/src/plugins/fresco/index.js @@ -15,7 +15,7 @@ import type { CacheInfo, } from './api.js'; import type {ImagesMap} from './ImagePool.js'; -import type {MetricType} from 'flipper'; +import type {MetricType, MiddlewareAPI} from 'flipper'; import React from 'react'; import ImagesCacheOverview from './ImagesCacheOverview.js'; import { @@ -65,6 +65,12 @@ const debugLog = (...args) => { } }; +type ImagesMetaData = {| + levels: ImagesListResponse, + events: Array, + imageDataList: Array, +|}; + export default class extends FlipperPlugin { static defaultPersistedState: PersistedState = { images: [], @@ -73,6 +79,52 @@ export default class extends FlipperPlugin { surfaceList: new Set(), }; + static exportPersistedState = ( + callClient: (string, ?Object) => Promise, + persistedState: ?PersistedState, + store: ?MiddlewareAPI, + ): Promise => { + if (persistedState) { + return Promise.resolve(persistedState); + } + const defaultPromise = Promise.resolve(persistedState); + if (!store) { + return defaultPromise; + } + return callClient('getAllImageData').then((data: ImagesMetaData) => { + if (!data) { + return; + } + const {levels, events, imageDataList} = data; + let pluginData: PersistedState = { + images: [...levels.levels], + surfaceList: new Set(), + events: [], + imagesMap: {}, + }; + + events.forEach((event: ImageEventWithId, index) => { + const {attribution} = event; + if (attribution instanceof Array && attribution.length > 0) { + const surface = attribution[0].trim(); + if (surface.length > 0) { + pluginData.surfaceList.add(surface); + } + } + pluginData = { + ...pluginData, + events: [{eventId: index, ...event}, ...pluginData.events], + }; + }); + + imageDataList.forEach((imageData: ImageData) => { + const {imageId} = imageData; + pluginData.imagesMap[imageId] = imageData; + }); + return pluginData; + }); + }; + static metricsReducer = ( persistedState: PersistedState, ): Promise => { @@ -113,42 +165,6 @@ export default class extends FlipperPlugin { coldStartFilter: false, }; - init() { - debugLog('init()'); - this.updateCaches('init'); - this.client.subscribe('events', (event: ImageEvent) => { - const {surfaceList} = this.props.persistedState; - const {attribution} = event; - if (attribution instanceof Array && attribution.length > 0) { - const surface = attribution[0].trim(); - if (surface.length > 0) { - surfaceList.add(surface); - } - } - this.props.setPersistedState({ - events: [ - {eventId: this.nextEventId, ...event}, - ...this.props.persistedState.events, - ], - }); - this.nextEventId++; - }); - this.client.subscribe( - 'debug_overlay_event', - (event: FrescoDebugOverlayEvent) => { - this.setState({isDebugOverlayEnabled: event.enabled}); - }, - ); - - this.imagePool = new ImagePool(this.getImage, (images: ImagesMap) => - this.props.setPersistedState({imagesMap: images}), - ); - } - - teardown() { - this.imagePool.clear(); - } - filterImages = ( images: ImagesList, events: Array, @@ -183,6 +199,49 @@ export default class extends FlipperPlugin { return imageList; }; + init() { + debugLog('init()'); + this.updateCaches('init'); + this.client.subscribe('events', (event: ImageEvent) => { + const {surfaceList} = this.props.persistedState; + const {attribution} = event; + if (attribution instanceof Array && attribution.length > 0) { + const surface = attribution[0].trim(); + if (surface.length > 0) { + surfaceList.add(surface); + } + } + this.props.setPersistedState({ + events: [ + {eventId: this.nextEventId, ...event}, + ...this.props.persistedState.events, + ], + }); + this.nextEventId++; + }); + this.client.subscribe( + 'debug_overlay_event', + (event: FrescoDebugOverlayEvent) => { + this.setState({isDebugOverlayEnabled: event.enabled}); + }, + ); + this.imagePool = new ImagePool(this.getImage, (images: ImagesMap) => + this.props.setPersistedState({imagesMap: images}), + ); + + let images = this.filterImages( + this.props.persistedState.images, + this.props.persistedState.events, + this.state.selectedSurface, + this.state.coldStartFilter, + ); + this.setState({images}); + } + + teardown() { + this.imagePool.clear(); + } + updateImagesOnUI = ( images: ImagesList, surface: string, diff --git a/src/utils/exportData.js b/src/utils/exportData.js index 92a31be40..d85764931 100644 --- a/src/utils/exportData.js +++ b/src/utils/exportData.js @@ -319,6 +319,15 @@ export function importDataToStore(data: string, store: Store) { const {pluginStates} = json.store; const keys = Object.keys(pluginStates); + keys.forEach(key => { + store.dispatch({ + type: 'SET_PLUGIN_STATE', + payload: { + pluginKey: key, + state: pluginStates[key], + }, + }); + }); clients.forEach(client => { const clientPlugins = keys .filter(key => { @@ -340,15 +349,6 @@ export function importDataToStore(data: string, store: Store) { ), }); }); - keys.forEach(key => { - store.dispatch({ - type: 'SET_PLUGIN_STATE', - payload: { - pluginKey: key, - state: pluginStates[key], - }, - }); - }); } export const importFileToStore = (file: string, store: Store) => {