Factor out common traversal code in observers

Summary: Each observer was doing a similar job of traversing, and setting up child observers and its easy to not clean up child observers in some cases. This provides a helper utility for the common use case

Reviewed By: lblasa

Differential Revision: D39466930

fbshipit-source-id: e74ae5c3709297b73c020cd148a0485ac9fc0f8f
This commit is contained in:
Luke De Feo
2022-09-13 11:05:42 -07:00
committed by Facebook GitHub Bot
parent a0ee774159
commit 4341cbdf3d
4 changed files with 64 additions and 44 deletions

View File

@@ -1,42 +1,37 @@
/*
* 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.litho
import com.facebook.flipper.plugins.uidebugger.SubtreeUpdate
import android.util.Log
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.TreeObserver
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.identityHashCode
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverBuilder
import com.facebook.litho.LithoView
class LithoViewTreeObserver(val context: Context) : TreeObserver<LithoView>() {
override val type = "Litho"
var nodeRef: LithoView? = null
override fun subscribe(node: Any) {
node as LithoView
nodeRef = node
nodeRef = node as LithoView
val listener: (view: LithoView) -> Unit = {
val start = System.currentTimeMillis()
val (nodes, skipped) = context.layoutTraversal.traverse(it)
for (observerRoot in skipped) {
if (!children.containsKey(observerRoot.identityHashCode())) {
val observer = context.observerFactory.createObserver(observerRoot, context)!!
observer.subscribe(observerRoot)
children[observerRoot.identityHashCode()] = observer
}
}
context.treeObserverManager.emit(
SubtreeUpdate("Litho", nodes, start, System.currentTimeMillis()))
}
val listener: (view: LithoView) -> Unit = { traverseAndSend(context, node) }
node.setOnDirtyMountListener(listener)
listener(node)
}
override fun unsubscribe() {
Log.i(LogTag, "Unsubscribing from litho view")
nodeRef?.setOnDirtyMountListener(null)
nodeRef = null
}

View File

@@ -24,6 +24,8 @@ import com.facebook.flipper.plugins.uidebugger.identityHashCode
*/
class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRef>() {
override val type = "Application"
override fun subscribe(node: Any) {
Log.i(LogTag, "subscribing to application / activity changes")

View File

@@ -11,10 +11,8 @@ import android.util.Log
import android.view.View
import android.view.ViewTreeObserver
import com.facebook.flipper.plugins.uidebugger.LogTag
import com.facebook.flipper.plugins.uidebugger.SubtreeUpdate
import com.facebook.flipper.plugins.uidebugger.TreeObserver
import com.facebook.flipper.plugins.uidebugger.core.Context
import com.facebook.flipper.plugins.uidebugger.identityHashCode
typealias DecorView = View
@@ -23,10 +21,12 @@ class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
val throttleTimeMs = 500
// maybe should be weak reference in ctor?
// should this be a weak reference?
private var nodeRef: View? = null
private var listener: ViewTreeObserver.OnPreDrawListener? = null
override val type = "DecorView"
override fun subscribe(node: Any) {
node as View
@@ -38,22 +38,9 @@ class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
object : ViewTreeObserver.OnPreDrawListener {
var lastSend = 0L
override fun onPreDraw(): Boolean {
val start = System.currentTimeMillis()
if (start - lastSend > throttleTimeMs) {
val (nodes, skipped) = context.layoutTraversal.traverse(node)
if (System.currentTimeMillis() - lastSend > throttleTimeMs) {
traverseAndSend(context, node)
for (observerRoot in skipped) {
if (!children.containsKey(observerRoot.identityHashCode())) {
val observer = context.observerFactory.createObserver(observerRoot, context)!!
observer.subscribe(observerRoot)
children[observerRoot.identityHashCode()] = observer
}
}
val traversalComplete = System.currentTimeMillis()
context.treeObserverManager.emit(
SubtreeUpdate("DecorView", nodes, start, traversalComplete))
lastSend = System.currentTimeMillis()
}
return true
@@ -64,11 +51,9 @@ class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
}
override fun unsubscribe() {
Log.i(LogTag, "Try Unsubscribing to decor view changes")
Log.i(LogTag, "Unsubscribing from decor view changes")
listener.let {
Log.i(LogTag, "Actually Unsubscribing to decor view changes")
nodeRef?.viewTreeObserver?.removeOnPreDrawListener(it)
listener = null
nodeRef = null

View File

@@ -103,7 +103,7 @@ It is responsible for:
1. listening to the relevant framework events
2. Traversing the hierarchy of the managed nodes
3. Diffing to previous state (optional)
4. Pushing out updates for its entire set of nodes
4. Pushing out updates for its entire set of managed nodes
If while traversing it encounters a node type which has its own TreeObserver, it
does not traverse that, instead it sets up a Tree observer responsible for that subtree
@@ -114,12 +114,51 @@ abstract class TreeObserver<T> {
protected val children: MutableMap<Int, TreeObserver<*>> = mutableMapOf()
// todo try to pass T again?
abstract val type: String
abstract fun subscribe(node: Any)
abstract fun unsubscribe()
/**
* Optional helper method that traverses the layout hierarchy while managing any encountered child
* observers correctly
*/
fun traverseAndSend(context: Context, root: Any) {
val start = System.currentTimeMillis()
val (visitedNodes, observerRootsNodes) = context.layoutTraversal.traverse(root)
// Add any new Observers
for (observerRoot in observerRootsNodes) {
if (!children.containsKey(observerRoot.identityHashCode())) {
val childObserver = context.observerFactory.createObserver(observerRoot, context)!!
Log.d(
LogTag,
"For Observer ${this.type} discovered new child of type ${childObserver.type} Node ID ${observerRoot.identityHashCode()}")
childObserver.subscribe(observerRoot)
children[observerRoot.identityHashCode()] = childObserver
}
}
// remove any old observers
val observerRootIds = observerRootsNodes.map { it.identityHashCode() }
for (childKey in children.keys) {
if (!observerRootIds.contains(childKey)) {
Log.d(
LogTag,
"For Observer ${this.type} cleaning up child of type ${children[childKey]!!.type} Node ID ${childKey}")
children[childKey]!!.cleanUpRecursive()
}
}
context.treeObserverManager.emit(
SubtreeUpdate(type, visitedNodes, start, System.currentTimeMillis()))
}
fun cleanUpRecursive() {
Log.i(LogTag, "Cleaning up observer ${this}")
children.values.forEach { it.cleanUpRecursive() }
unsubscribe()
children.clear()
@@ -192,7 +231,6 @@ class PartialLayoutTraversal(
val attributes = mutableMapOf<String, InspectableObject>()
descriptor.getData(node, attributes)
// NOTE active child null here
visited.add(
Node(
descriptor.getId(node),