Single bitmap pool with view size changes support

Summary:
This change aims to reduce the number of bitmap pools by definiting a default pool which can contain bitmaps of different sizes.

A side benefit is that this single pool facilitates handling the case where a view gets resized too.

So overall, this should be both more generic and efficient compared to the previous approach.

Reviewed By: LukeDefeo

Differential Revision: D39815821

fbshipit-source-id: e0aa17ba55db07b74d8f22ea16e0c864288fb169
This commit is contained in:
Lorenzo Blasa
2022-09-27 13:00:04 -07:00
committed by Facebook GitHub Bot
parent 945e26d0f1
commit fadaf26f1e
6 changed files with 52 additions and 38 deletions

View File

@@ -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 {

View File

@@ -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<Bitmap> = mutableListOf()
private val container: MutableMap<String, MutableList<Bitmap>> = 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)
}

View File

@@ -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?)

View File

@@ -40,10 +40,13 @@ class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
val throttledUpdate =
throttleLatest<WeakReference<View>?>(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<DecorView>() {
}
nodeRef = null
bitmapPool?.recycle()
bitmapPool = null
}
}

View File

@@ -31,7 +31,6 @@ import com.facebook.flipper.plugins.uidebugger.descriptors.nodeId
abstract class TreeObserver<T> {
protected val children: MutableMap<Int, TreeObserver<*>> = mutableMapOf()
protected var bitmapPool: BitmapPool? = null
abstract val type: String
@@ -39,12 +38,12 @@ abstract class TreeObserver<T> {
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<T> {
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<Any>
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() {

View File

@@ -31,7 +31,7 @@ data class SubtreeUpdate(
val nodes: List<Node>,
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()