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:
committed by
Facebook GitHub Bot
parent
945e26d0f1
commit
fadaf26f1e
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
container.forEach { (_, bitmaps) ->
|
||||
bitmaps.forEach { bitmap -> bitmap.recycle() }
|
||||
bitmaps.clear()
|
||||
}
|
||||
|
||||
fun getBitmap(): RecyclableBitmap {
|
||||
return if (bitmaps.isEmpty()) {
|
||||
container.clear()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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?)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user