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:
committed by
Facebook GitHub Bot
parent
a0ee774159
commit
4341cbdf3d
@@ -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
|
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.TreeObserver
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.Context
|
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.flipper.plugins.uidebugger.observers.TreeObserverBuilder
|
||||||
import com.facebook.litho.LithoView
|
import com.facebook.litho.LithoView
|
||||||
|
|
||||||
class LithoViewTreeObserver(val context: Context) : TreeObserver<LithoView>() {
|
class LithoViewTreeObserver(val context: Context) : TreeObserver<LithoView>() {
|
||||||
|
|
||||||
|
override val type = "Litho"
|
||||||
|
|
||||||
var nodeRef: LithoView? = null
|
var nodeRef: LithoView? = null
|
||||||
|
|
||||||
override fun subscribe(node: Any) {
|
override fun subscribe(node: Any) {
|
||||||
node as LithoView
|
|
||||||
|
|
||||||
nodeRef = node
|
nodeRef = node as LithoView
|
||||||
|
|
||||||
val listener: (view: LithoView) -> Unit = {
|
val listener: (view: LithoView) -> Unit = { traverseAndSend(context, node) }
|
||||||
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()))
|
|
||||||
}
|
|
||||||
node.setOnDirtyMountListener(listener)
|
node.setOnDirtyMountListener(listener)
|
||||||
|
|
||||||
listener(node)
|
listener(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unsubscribe() {
|
override fun unsubscribe() {
|
||||||
|
Log.i(LogTag, "Unsubscribing from litho view")
|
||||||
nodeRef?.setOnDirtyMountListener(null)
|
nodeRef?.setOnDirtyMountListener(null)
|
||||||
nodeRef = null
|
nodeRef = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import com.facebook.flipper.plugins.uidebugger.identityHashCode
|
|||||||
*/
|
*/
|
||||||
class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRef>() {
|
class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRef>() {
|
||||||
|
|
||||||
|
override val type = "Application"
|
||||||
|
|
||||||
override fun subscribe(node: Any) {
|
override fun subscribe(node: Any) {
|
||||||
Log.i(LogTag, "subscribing to application / activity changes")
|
Log.i(LogTag, "subscribing to application / activity changes")
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import android.util.Log
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
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.TreeObserver
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.Context
|
import com.facebook.flipper.plugins.uidebugger.core.Context
|
||||||
import com.facebook.flipper.plugins.uidebugger.identityHashCode
|
|
||||||
|
|
||||||
typealias DecorView = View
|
typealias DecorView = View
|
||||||
|
|
||||||
@@ -23,10 +21,12 @@ class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
|
|||||||
|
|
||||||
val throttleTimeMs = 500
|
val throttleTimeMs = 500
|
||||||
|
|
||||||
// maybe should be weak reference in ctor?
|
// should this be a weak reference?
|
||||||
private var nodeRef: View? = null
|
private var nodeRef: View? = null
|
||||||
private var listener: ViewTreeObserver.OnPreDrawListener? = null
|
private var listener: ViewTreeObserver.OnPreDrawListener? = null
|
||||||
|
|
||||||
|
override val type = "DecorView"
|
||||||
|
|
||||||
override fun subscribe(node: Any) {
|
override fun subscribe(node: Any) {
|
||||||
|
|
||||||
node as View
|
node as View
|
||||||
@@ -38,22 +38,9 @@ class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
|
|||||||
object : ViewTreeObserver.OnPreDrawListener {
|
object : ViewTreeObserver.OnPreDrawListener {
|
||||||
var lastSend = 0L
|
var lastSend = 0L
|
||||||
override fun onPreDraw(): Boolean {
|
override fun onPreDraw(): Boolean {
|
||||||
val start = System.currentTimeMillis()
|
if (System.currentTimeMillis() - lastSend > throttleTimeMs) {
|
||||||
if (start - lastSend > throttleTimeMs) {
|
traverseAndSend(context, node)
|
||||||
val (nodes, skipped) = context.layoutTraversal.traverse(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()
|
lastSend = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -64,11 +51,9 @@ class DecorViewObserver(val context: Context) : TreeObserver<DecorView>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun unsubscribe() {
|
override fun unsubscribe() {
|
||||||
Log.i(LogTag, "Try Unsubscribing to decor view changes")
|
Log.i(LogTag, "Unsubscribing from decor view changes")
|
||||||
|
|
||||||
listener.let {
|
listener.let {
|
||||||
Log.i(LogTag, "Actually Unsubscribing to decor view changes")
|
|
||||||
|
|
||||||
nodeRef?.viewTreeObserver?.removeOnPreDrawListener(it)
|
nodeRef?.viewTreeObserver?.removeOnPreDrawListener(it)
|
||||||
listener = null
|
listener = null
|
||||||
nodeRef = null
|
nodeRef = null
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ It is responsible for:
|
|||||||
1. listening to the relevant framework events
|
1. listening to the relevant framework events
|
||||||
2. Traversing the hierarchy of the managed nodes
|
2. Traversing the hierarchy of the managed nodes
|
||||||
3. Diffing to previous state (optional)
|
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
|
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
|
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()
|
protected val children: MutableMap<Int, TreeObserver<*>> = mutableMapOf()
|
||||||
|
|
||||||
// todo try to pass T again?
|
abstract val type: String
|
||||||
|
|
||||||
abstract fun subscribe(node: Any)
|
abstract fun subscribe(node: Any)
|
||||||
|
|
||||||
abstract fun unsubscribe()
|
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() {
|
fun cleanUpRecursive() {
|
||||||
|
Log.i(LogTag, "Cleaning up observer ${this}")
|
||||||
children.values.forEach { it.cleanUpRecursive() }
|
children.values.forEach { it.cleanUpRecursive() }
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
children.clear()
|
children.clear()
|
||||||
@@ -192,7 +231,6 @@ class PartialLayoutTraversal(
|
|||||||
val attributes = mutableMapOf<String, InspectableObject>()
|
val attributes = mutableMapOf<String, InspectableObject>()
|
||||||
descriptor.getData(node, attributes)
|
descriptor.getData(node, attributes)
|
||||||
|
|
||||||
// NOTE active child null here
|
|
||||||
visited.add(
|
visited.add(
|
||||||
Node(
|
Node(
|
||||||
descriptor.getId(node),
|
descriptor.getId(node),
|
||||||
|
|||||||
Reference in New Issue
Block a user