Restore root view tracking
Summary: We use the root view resolver to detect when roots have been added. We currently have some race conditions where the activity stack managed by application ref doesnt include the first activity Reviewed By: lblasa Differential Revision: D39466929 fbshipit-source-id: fff4f830dea337d96dd9a9956a20a080fff2e965
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4341cbdf3d
commit
f06f63306e
@@ -1,53 +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.core
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewTreeObserver
|
|
||||||
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
|
||||||
|
|
||||||
class ApplicationInspector(val context: Context) {
|
|
||||||
val descriptorRegister = DescriptorRegister.withDefaults()
|
|
||||||
val traversal = LayoutTraversal(descriptorRegister, context.applicationRef)
|
|
||||||
|
|
||||||
fun attachListeners(view: View) {
|
|
||||||
// An OnGlobalLayoutListener watches the entire hierarchy for layout changes
|
|
||||||
// (so registering one of these on any View in a hierarchy will cause it to be triggered
|
|
||||||
// when any View in that hierarchy is laid out or changes visibility).
|
|
||||||
view
|
|
||||||
.getViewTreeObserver()
|
|
||||||
.addOnGlobalLayoutListener(
|
|
||||||
object : ViewTreeObserver.OnGlobalLayoutListener {
|
|
||||||
override fun onGlobalLayout() {}
|
|
||||||
})
|
|
||||||
view
|
|
||||||
.getViewTreeObserver()
|
|
||||||
.addOnPreDrawListener(
|
|
||||||
object : ViewTreeObserver.OnPreDrawListener {
|
|
||||||
override fun onPreDraw(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun observe() {
|
|
||||||
val rootResolver = RootViewResolver()
|
|
||||||
rootResolver.attachListener(
|
|
||||||
object : RootViewResolver.Listener {
|
|
||||||
override fun onRootViewAdded(view: View) {
|
|
||||||
attachListeners(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRootViewRemoved(view: View) {}
|
|
||||||
override fun onRootViewsChanged(views: java.util.List<View>) {}
|
|
||||||
})
|
|
||||||
|
|
||||||
val activeRoots = rootResolver.listActiveRootViews()
|
|
||||||
activeRoots?.let { roots -> for (root: RootViewResolver.RootView in roots) {} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ class ApplicationRef(val application: Application) : Application.ActivityLifecyc
|
|||||||
fun onActivityDestroyed(activity: Activity, stack: List<Activity>)
|
fun onActivityDestroyed(activity: Activity, stack: List<Activity>)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val rootsResolver: RootViewResolver
|
val rootsResolver: RootViewResolver
|
||||||
private val activities: MutableList<WeakReference<Activity>>
|
private val activities: MutableList<WeakReference<Activity>>
|
||||||
private var activityStackChangedlistener: ActivityStackChangedListener? = null
|
private var activityStackChangedlistener: ActivityStackChangedListener? = null
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import java.lang.reflect.Field
|
|||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.List
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to all root views in an application.
|
* Provides access to all root views in an application.
|
||||||
@@ -39,28 +38,28 @@ class RootViewResolver {
|
|||||||
interface Listener {
|
interface Listener {
|
||||||
fun onRootViewAdded(rootView: View)
|
fun onRootViewAdded(rootView: View)
|
||||||
fun onRootViewRemoved(rootView: View)
|
fun onRootViewRemoved(rootView: View)
|
||||||
fun onRootViewsChanged(rootView: List<View>)
|
fun onRootViewsChanged(rootViews: List<View>)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObservableArrayList() : ArrayList<View>() {
|
class ObservableArrayList : ArrayList<View>() {
|
||||||
private var listener: Listener? = null
|
private var listener: Listener? = null
|
||||||
fun setListener(listener: Listener?) {
|
fun setListener(listener: Listener?) {
|
||||||
this.listener = listener
|
this.listener = listener
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun add(value: View): Boolean {
|
override fun add(element: View): Boolean {
|
||||||
val ret = super.add(value)
|
val ret = super.add(element)
|
||||||
listener?.let { l ->
|
listener?.let { l ->
|
||||||
l.onRootViewAdded(value)
|
l.onRootViewAdded(element)
|
||||||
l.onRootViewsChanged(this as List<View>)
|
l.onRootViewsChanged(this as List<View>)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(value: View): Boolean {
|
override fun remove(element: View): Boolean {
|
||||||
val ret = super.remove(value)
|
val ret = super.remove(element)
|
||||||
listener?.let { l ->
|
listener?.let { l ->
|
||||||
l.onRootViewRemoved(value)
|
l.onRootViewRemoved(element)
|
||||||
l.onRootViewsChanged(this as List<View>)
|
l.onRootViewsChanged(this as List<View>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ class RootViewResolver {
|
|||||||
viewsField?.let { field ->
|
viewsField?.let { field ->
|
||||||
if (Build.VERSION.SDK_INT < 19) {
|
if (Build.VERSION.SDK_INT < 19) {
|
||||||
val arr = field[windowManagerObj] as Array<View>
|
val arr = field[windowManagerObj] as Array<View>
|
||||||
views = arr.toList() as List<View>
|
views = arr.toList()
|
||||||
} else {
|
} else {
|
||||||
views = field[windowManagerObj] as List<View>
|
views = field[windowManagerObj] as List<View>
|
||||||
}
|
}
|
||||||
@@ -148,16 +147,20 @@ class RootViewResolver {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val roots: ArrayList<RootView> = ArrayList()
|
val roots = mutableListOf<RootView>()
|
||||||
views?.let { views ->
|
views?.let { views ->
|
||||||
params?.let { params ->
|
params?.let { params ->
|
||||||
for (i in views.indices) {
|
for (i in views.indices) {
|
||||||
roots.add(RootView(views[i], params[i]))
|
val view = views[i]
|
||||||
|
// TODO FIX, len(param) is not always the same as len(views) For now just use first
|
||||||
|
val param = params[0]
|
||||||
|
// params
|
||||||
|
roots.add(RootView(view, param))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return roots as List<RootView>
|
return roots
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
|
|||||||
@@ -8,10 +8,14 @@
|
|||||||
package com.facebook.flipper.plugins.uidebugger.descriptors
|
package com.facebook.flipper.plugins.uidebugger.descriptors
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.util.Log
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
|
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
|
||||||
|
|
||||||
object ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
|
object ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
|
||||||
|
|
||||||
|
val rootsLocal = RootViewResolver()
|
||||||
override fun onGetActiveChild(node: ApplicationRef): Any? {
|
override fun onGetActiveChild(node: ApplicationRef): Any? {
|
||||||
return if (node.activitiesStack.isNotEmpty()) node.activitiesStack.last() else null
|
return if (node.activitiesStack.isNotEmpty()) node.activitiesStack.last() else null
|
||||||
}
|
}
|
||||||
@@ -28,8 +32,30 @@ object ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetChildren(node: ApplicationRef, children: MutableList<Any>) {
|
override fun onGetChildren(node: ApplicationRef, children: MutableList<Any>) {
|
||||||
for (activity: Activity in node.activitiesStack) {
|
val activeRoots = node.rootViews
|
||||||
children.add(activity)
|
|
||||||
|
Log.i(LogTag, rootsLocal.toString())
|
||||||
|
activeRoots.let { roots ->
|
||||||
|
for (root in roots) {
|
||||||
|
var added = false
|
||||||
|
/**
|
||||||
|
* This code serves 2 purposes: 1.it picks up root views not tied to an activity (dialogs)
|
||||||
|
* 2. We can get initialized late and miss the first activity, it does seem that the root
|
||||||
|
* view resolver is able to (usually ) get the root view regardless, with this we insert the
|
||||||
|
* root decor view without the activity. Ideally we wouldn't rely on this behaviour and find
|
||||||
|
* a better way to track activities
|
||||||
|
*/
|
||||||
|
for (activity: Activity in node.activitiesStack) {
|
||||||
|
if (activity.window.decorView == root) {
|
||||||
|
children.add(activity)
|
||||||
|
added = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!added) {
|
||||||
|
children.add(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,13 @@
|
|||||||
|
|
||||||
package com.facebook.flipper.plugins.uidebugger.observers
|
package com.facebook.flipper.plugins.uidebugger.observers
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.ContextWrapper
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
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.ApplicationRef
|
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
|
||||||
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.core.RootViewResolver
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* responsible for observing the activity stack and managing the subscription to the top most
|
* responsible for observing the activity stack and managing the subscription to the top most
|
||||||
@@ -27,66 +24,27 @@ class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRe
|
|||||||
override val type = "Application"
|
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 activity / root view changes")
|
||||||
|
|
||||||
val applicationRef = node as ApplicationRef
|
val applicationRef = node as ApplicationRef
|
||||||
|
|
||||||
val addRemoveListener =
|
val rootViewListener =
|
||||||
object : ApplicationRef.ActivityStackChangedListener {
|
object : RootViewResolver.Listener {
|
||||||
|
override fun onRootViewAdded(rootView: View) {}
|
||||||
|
|
||||||
override fun onActivityAdded(activity: Activity, stack: List<Activity>) {
|
override fun onRootViewRemoved(rootView: View) {}
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
val (nodes, skipped) = context.layoutTraversal.traverse(applicationRef)
|
|
||||||
val observer =
|
|
||||||
context.observerFactory.createObserver(activity.window.decorView, context)!!
|
|
||||||
observer.subscribe(activity.window.decorView)
|
|
||||||
children[activity.window.decorView.identityHashCode()] = observer
|
|
||||||
context.treeObserverManager.emit(
|
|
||||||
SubtreeUpdate("Application", nodes, start, System.currentTimeMillis()))
|
|
||||||
Log.i(
|
|
||||||
LogTag,
|
|
||||||
"Activity added,stack size ${stack.size} found ${nodes.size} skipped $skipped Listeners $children")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityStackChanged(stack: List<Activity>) {}
|
override fun onRootViewsChanged(rootViews: List<View>) {
|
||||||
|
Log.i(LogTag, "Root views updated, num ${rootViews.size}")
|
||||||
override fun onActivityDestroyed(activity: Activity, stack: List<Activity>) {
|
traverseAndSend(context, applicationRef)
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
|
|
||||||
val (nodes, skipped) = context.layoutTraversal.traverse(applicationRef)
|
|
||||||
|
|
||||||
val observer = children[activity.window.decorView.identityHashCode()]
|
|
||||||
children.remove(activity.window.decorView.identityHashCode())
|
|
||||||
observer?.cleanUpRecursive()
|
|
||||||
|
|
||||||
context.treeObserverManager.emit(
|
|
||||||
SubtreeUpdate("Application", nodes, start, System.currentTimeMillis()))
|
|
||||||
|
|
||||||
Log.i(
|
|
||||||
LogTag,
|
|
||||||
"Activity removed,stack size ${stack.size} found ${nodes.size} skipped $skipped Listeners $children")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
context.applicationRef.rootsResolver.attachListener(rootViewListener)
|
||||||
context.applicationRef.setActivityStackChangedListener(addRemoveListener)
|
// trigger a traversal on whatever roots we have now
|
||||||
|
rootViewListener.onRootViewsChanged(applicationRef.rootViews)
|
||||||
|
|
||||||
Log.i(LogTag, "${context.applicationRef.rootViews.size} root views")
|
Log.i(LogTag, "${context.applicationRef.rootViews.size} root views")
|
||||||
Log.i(LogTag, "${context.applicationRef.activitiesStack.size} activities")
|
Log.i(LogTag, "${context.applicationRef.activitiesStack.size} activities")
|
||||||
|
|
||||||
val stack = context.applicationRef.activitiesStack
|
|
||||||
for (activity in stack) {
|
|
||||||
addRemoveListener.onActivityAdded(activity, stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private fun getActivity(view: View): Activity? {
|
|
||||||
var context: android.content.Context? = view.context
|
|
||||||
while (context is ContextWrapper) {
|
|
||||||
if (context is Activity) {
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
context = context.baseContext
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unsubscribe() {
|
override fun unsubscribe() {
|
||||||
|
|||||||
Reference in New Issue
Block a user