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