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:
committed by
Facebook GitHub Bot
parent
55b852f90c
commit
a5da6923eb
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>) {}
|
||||
})
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
|
||||
package com.facebook.flipper.plugins.uidebugger.core
|
||||
|
||||
class Context(val application: ApplicationRef) {}
|
||||
class Context(val applicationRef: ApplicationRef) {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
Reference in New Issue
Block a user