Flatten layout during traversal

Summary: Move from a nested structure to a flatten one for data exchange, this will allow us to only send sections of the UI in the future

Reviewed By: lblasa

Differential Revision: D38982138

fbshipit-source-id: d578a07a6d2d7e117fbd741bd6e33062223ce10d
This commit is contained in:
Luke De Feo
2022-09-07 04:37:17 -07:00
committed by Facebook GitHub Bot
parent 55b852f90c
commit a5da6923eb
12 changed files with 128 additions and 114 deletions

View File

@@ -8,12 +8,14 @@
package com.facebook.flipper.plugins.uidebugger
import android.app.Application
import android.util.Log
import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.uidebugger.common.Node
import com.facebook.flipper.plugins.uidebugger.core.ApplicationInspector
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.model.InitEvent
import com.facebook.flipper.plugins.uidebugger.model.NativeScanEvent
import kotlinx.serialization.json.Json
val LogTag = "FlipperUIDebugger"
@@ -32,17 +34,22 @@ class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin {
this.connection = connection
// temp solution, get from descriptor
val inspector = ApplicationInspector(context)
val root: Node = inspector.inspect()!!
val initEvent = InitEvent(System.identityHashCode(application).toString())
val rootDescriptor =
inspector.descriptorRegister.descriptorForClassUnsafe(context.applicationRef.javaClass)
connection.send(
InitEvent.name,
Json.encodeToString(
InitEvent.serializer(), InitEvent(System.identityHashCode(application).toString())))
InitEvent.serializer(), InitEvent(rootDescriptor.getId(context.applicationRef))))
connection.send(
NativeScanEvent.name,
Json.encodeToString(NativeScanEvent.serializer(), NativeScanEvent(root)))
try {
val nodes = inspector.traversal.traverse()
connection.send(
NativeScanEvent.name,
Json.encodeToString(NativeScanEvent.serializer(), NativeScanEvent(nodes)))
} catch (e: java.lang.Exception) {
Log.e(LogTag, e.message.toString(), e)
}
}
@Throws(Exception::class)

View File

@@ -1,16 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.common
@kotlinx.serialization.Serializable
class Node() {
var id: String? = null
var name: String? = null
var attributes: Map<String, InspectableObject> = mapOf()
var children: List<Node>? = null
}

View File

@@ -9,16 +9,11 @@ package com.facebook.flipper.plugins.uidebugger.core
import android.view.View
import android.view.ViewTreeObserver
import com.facebook.flipper.plugins.uidebugger.common.Node
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
class ApplicationInspector(val context: Context) {
val descriptorRegister = DescriptorRegister.withDefaults()
val traversal = LayoutTraversal(descriptorRegister)
fun inspect(): Node? {
return traversal.inspect(context.application)
}
val traversal = LayoutTraversal(descriptorRegister, context.applicationRef)
fun attachListeners(view: View) {
// An OnGlobalLayoutListener watches the entire hierarchy for layout changes
@@ -49,7 +44,6 @@ class ApplicationInspector(val context: Context) {
}
override fun onRootViewRemoved(view: View) {}
override fun onRootViewsChanged(views: java.util.List<View>) {}
})

View File

@@ -7,4 +7,4 @@
package com.facebook.flipper.plugins.uidebugger.core
class Context(val application: ApplicationRef) {}
class Context(val applicationRef: ApplicationRef) {}

View File

@@ -7,63 +7,57 @@
package com.facebook.flipper.plugins.uidebugger.core
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.common.Node
import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.model.Node
class LayoutTraversal(private val descriptorRegister: DescriptorRegister) {
class IntermediateNode(val node: Node) {
var children: List<Any>? = null
}
class LayoutTraversal(
private val descriptorRegister: DescriptorRegister,
val root: ApplicationRef
) {
internal inline fun Descriptor<*>.asAny(): Descriptor<Any> = this as Descriptor<Any>
private fun describe(obj: Any): IntermediateNode {
var intermediate = IntermediateNode(Node())
/** Traverses the native android hierarchy */
fun traverse(): List<Node> {
val descriptor = descriptorRegister.descriptorForClass(obj::class.java)
descriptor?.let { descriptor ->
val anyDescriptor = descriptor.asAny()
val result = mutableListOf<Node>()
val stack = mutableListOf<Any>()
stack.add(this.root)
intermediate.node.id = anyDescriptor.getId(obj)
intermediate.node.name = anyDescriptor.getName(obj)
while (stack.isNotEmpty()) {
val attributes = mutableMapOf<String, InspectableObject>()
anyDescriptor.getData(obj, attributes)
intermediate.node.attributes = attributes
val node = stack.removeLast()
val children = mutableListOf<Any>()
anyDescriptor.getChildren(obj, children)
intermediate.children = children
}
try {
return intermediate
}
val descriptor = descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny()
private fun traverse(entry: Any): Node? {
val root = describe(entry)
root?.let { intermediate ->
val queue = mutableListOf<IntermediateNode>()
queue.add(intermediate)
val children = mutableListOf<Any>()
descriptor.getChildren(node, children)
while (queue.isNotEmpty()) {
val intermediateNode = queue.removeFirst()
val children = mutableListOf<Node>()
intermediateNode.children?.forEach {
val intermediateChild = describe(it)
children.add(intermediateChild.node)
queue.add(intermediateChild)
val childrenIds = mutableListOf<String>()
for (child in children) {
// it might make sense one day to remove id from the descriptor since its always the
// hash code
val childDescriptor =
descriptorRegister.descriptorForClassUnsafe(child::class.java).asAny()
childrenIds.add(childDescriptor.getId(child))
stack.add(child)
}
intermediateNode.node.children = children
val attributes = mutableMapOf<String, InspectableObject>()
descriptor.getData(node, attributes)
result.add(Node(descriptor.getId(node), descriptor.getName(node), attributes, childrenIds))
} catch (exception: Exception) {
Log.e(LogTag, "Error while processing node ${node.javaClass.name} ${node} ", exception)
}
}
return root?.node
}
fun inspect(applicationRef: ApplicationRef): Node? {
return traverse(applicationRef)
return result
}
}

View File

@@ -12,7 +12,7 @@ import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
class ApplicationDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
class ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
val rootResolver = RootViewResolver()
override fun onInit() {}

View File

@@ -13,6 +13,7 @@ import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.TextView
import com.facebook.flipper.plugins.uidebugger.common.UIDebuggerException
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
class DescriptorRegister {
@@ -23,7 +24,7 @@ class DescriptorRegister {
fun withDefaults(): DescriptorRegister {
val mapping = DescriptorRegister()
mapping.register(Any::class.java, ObjectDescriptor())
mapping.register(ApplicationRef::class.java, ApplicationDescriptor())
mapping.register(ApplicationRef::class.java, ApplicationRefDescriptor())
mapping.register(Activity::class.java, ActivityDescriptor())
mapping.register(Window::class.java, WindowDescriptor())
mapping.register(ViewGroup::class.java, ViewGroupDescriptor())
@@ -57,11 +58,16 @@ class DescriptorRegister {
register[clazz] = descriptor
}
fun descriptorForClass(clazz: Class<*>): Descriptor<*>? {
var clazz = clazz
fun <T> descriptorForClass(clazz: Class<T>): Descriptor<T>? {
var clazz: Class<*> = clazz
while (!register.containsKey(clazz)) {
clazz = clazz.superclass
}
return register[clazz]
return register[clazz] as Descriptor<T>
}
fun <T> descriptorForClassUnsafe(clazz: Class<T>): Descriptor<T> {
return descriptorForClass(clazz)
?: throw UIDebuggerException("No descriptor found for ${clazz.name}")
}
}

View File

@@ -5,21 +5,18 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger
import com.facebook.flipper.plugins.uidebugger.common.Node
package com.facebook.flipper.plugins.uidebugger.model
@kotlinx.serialization.Serializable
data class InitEvent(val rootId: String) {
companion object {
val name = "init"
const val name = "init"
}
}
// TODO flatten the tree into normalised list
@kotlinx.serialization.Serializable
data class NativeScanEvent(val root: Node) {
data class NativeScanEvent(val nodes: List<Node>) {
companion object {
val name = "nativeScan"
const val name = "nativeScan"
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.model
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
@kotlinx.serialization.Serializable
data class Node(
val id: String,
val name: String,
val attributes: Map<String, InspectableObject>,
val children: List<String>
)