diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt index f8d5aa592..f5e98183a 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt @@ -45,6 +45,7 @@ class UIDebuggerFlipperPlugin( override fun onConnect(connection: FlipperConnection) { Log.i(LogTag, "Connected") this.context.connectionRef.connection = connection + this.context.bitmapPool.makeReady() connection.send( InitEvent.name, @@ -59,6 +60,7 @@ class UIDebuggerFlipperPlugin( Log.i(LogTag, "Disconnected") context.treeObserverManager.stop() + context.bitmapPool.recycleAll() } override fun runInBackground(): Boolean { diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt index 3e44989c7..fa7ec04eb 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/BitmapPool.kt @@ -11,42 +11,58 @@ import android.graphics.Bitmap import android.os.Handler import android.os.Looper -class BitmapPool( - private val width: Int, - private val height: Int, - private val config: Bitmap.Config = Bitmap.Config.RGB_565 -) { +/** BitmapPool is intended to be used on the main thread. In other words, it is not thread-safe. */ +class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) { - interface RecyclableBitmap { + interface ReusableBitmap { val bitmap: Bitmap? - fun recycle() + fun readyForReuse() } private var handler: Handler = Handler(Looper.getMainLooper()) - private val bitmaps: MutableList = mutableListOf() + private val container: MutableMap> = mutableMapOf() private var isRecycled = false - fun recycle() { + private fun generateKey(width: Int, height: Int): String = "$width,$height" + + fun recycleAll() { isRecycled = true - bitmaps.forEach { bitmap -> bitmap.recycle() } - bitmaps.clear() + container.forEach { (_, bitmaps) -> + bitmaps.forEach { bitmap -> bitmap.recycle() } + bitmaps.clear() + } + + container.clear() } - fun getBitmap(): RecyclableBitmap { - return if (bitmaps.isEmpty()) { + fun makeReady() { + isRecycled = false + } + + fun getBitmap(width: Int, height: Int): ReusableBitmap { + val key = generateKey(width, height) + val bitmaps = container[key] + + return if (bitmaps == null || bitmaps.isEmpty()) { LeasedBitmap(Bitmap.createBitmap(width, height, config)) } else { LeasedBitmap(bitmaps.removeLast()) } } - inner class LeasedBitmap(override val bitmap: Bitmap) : RecyclableBitmap { - override fun recycle() { + inner class LeasedBitmap(override val bitmap: Bitmap) : ReusableBitmap { + override fun readyForReuse() { + val key = generateKey(bitmap.width, bitmap.height) handler.post { if (isRecycled) { bitmap.recycle() } else { + var bitmaps = container[key] + if (bitmaps == null) { + bitmaps = mutableListOf() + container[key] = bitmaps + } bitmaps.add(bitmap) } } @@ -54,10 +70,6 @@ class BitmapPool( } companion object { - fun createBitmap(width: Int, height: Int, config: Bitmap.Config): Bitmap { - return Bitmap.createBitmap(width, height, config) - } - fun createBitmapWithDefaultConfig(width: Int, height: Int): Bitmap { return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt index b00375499..e6d9b41f9 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt @@ -8,6 +8,7 @@ package com.facebook.flipper.plugins.uidebugger.core import com.facebook.flipper.core.FlipperConnection +import com.facebook.flipper.plugins.uidebugger.common.BitmapPool import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager @@ -23,6 +24,8 @@ data class Context( PartialLayoutTraversal(descriptorRegister, observerFactory) val treeObserverManager = TreeObserverManager(this) + + val bitmapPool = BitmapPool() } data class ConnectionRef(var connection: FlipperConnection?) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt index b85d1e6d6..b6de8c05a 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/DecorViewTreeObserver.kt @@ -40,10 +40,13 @@ class DecorViewObserver(val context: Context) : TreeObserver() { val throttledUpdate = throttleLatest?>(throttleTimeMs, waitScope, mainScope) { weakView -> - if (node.width > 0 && node.height > 0) { - bitmapPool = BitmapPool(node.width, node.height) + weakView?.get()?.let { view -> + var snapshotBitmap: BitmapPool.ReusableBitmap? = null + if (view.width > 0 && view.height > 0) { + snapshotBitmap = context.bitmapPool.getBitmap(node.width, node.height) + } + processUpdate(context, view, snapshotBitmap) } - weakView?.get()?.let { view -> processUpdateWithSnapshot(context, view) } } listener = @@ -68,9 +71,6 @@ class DecorViewObserver(val context: Context) : TreeObserver() { } nodeRef = null - - bitmapPool?.recycle() - bitmapPool = null } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt index cd89e2ce8..23162820c 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserver.kt @@ -31,7 +31,6 @@ import com.facebook.flipper.plugins.uidebugger.descriptors.nodeId abstract class TreeObserver { protected val children: MutableMap> = mutableMapOf() - protected var bitmapPool: BitmapPool? = null abstract val type: String @@ -39,12 +38,12 @@ abstract class TreeObserver { abstract fun unsubscribe() - fun processUpdate(context: Context, root: Any) = this.processUpdate(context, root, false) - fun processUpdateWithSnapshot(context: Context, root: Any) = - this.processUpdate(context, root, true) - /** Traverses the layout hierarchy while managing any encountered child observers. */ - fun processUpdate(context: Context, root: Any, takeSnapshot: Boolean) { + fun processUpdate( + context: Context, + root: Any, + snapshotBitmap: BitmapPool.ReusableBitmap? = null + ) { val startTimestamp = System.currentTimeMillis() val (visitedNodes, observableRoots) = context.layoutTraversal.traverse(root) @@ -82,20 +81,18 @@ abstract class TreeObserver { Log.d(LogTag, "For Observer ${this.type} Sending ${visitedNodes.size}") - var recyclableBitmap: BitmapPool.RecyclableBitmap? = null - if (takeSnapshot && bitmapPool != null) { + if (snapshotBitmap != null) { @Suppress("unchecked_cast") val descriptor = context.descriptorRegister.descriptorForClassUnsafe(root::class.java) as NodeDescriptor - recyclableBitmap = bitmapPool?.getBitmap() - descriptor.getSnapshot(root, recyclableBitmap?.bitmap) + descriptor.getSnapshot(root, snapshotBitmap.bitmap) } val endTimestamp = System.currentTimeMillis() context.treeObserverManager.enqueueUpdate( SubtreeUpdate( - type, root.nodeId(), visitedNodes, startTimestamp, endTimestamp, recyclableBitmap)) + type, root.nodeId(), visitedNodes, startTimestamp, endTimestamp, snapshotBitmap)) } fun cleanUpRecursive() { diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt index a5dae485f..d09babb98 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt @@ -31,7 +31,7 @@ data class SubtreeUpdate( val nodes: List, val startTime: Long, val traversalCompleteTime: Long, - val snapshot: BitmapPool.RecyclableBitmap? + val snapshot: BitmapPool.ReusableBitmap? ) /** Holds the root observer and manages sending updates to desktop */ @@ -87,7 +87,7 @@ class TreeObserverManager(val context: Context) { treeUpdate.nodes, snapshot)) - treeUpdate.snapshot.recycle() + treeUpdate.snapshot.readyForReuse() } val serializationEnd = System.currentTimeMillis()