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>)
|
||||
}
|
||||
|
||||
private val rootsResolver: RootViewResolver
|
||||
val rootsResolver: RootViewResolver
|
||||
private val activities: MutableList<WeakReference<Activity>>
|
||||
private var activityStackChangedlistener: ActivityStackChangedListener? = null
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import java.lang.reflect.Field
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.ArrayList
|
||||
import java.util.List
|
||||
|
||||
/**
|
||||
* Provides access to all root views in an application.
|
||||
@@ -39,28 +38,28 @@ class RootViewResolver {
|
||||
interface Listener {
|
||||
fun onRootViewAdded(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
|
||||
fun setListener(listener: Listener?) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
override fun add(value: View): Boolean {
|
||||
val ret = super.add(value)
|
||||
override fun add(element: View): Boolean {
|
||||
val ret = super.add(element)
|
||||
listener?.let { l ->
|
||||
l.onRootViewAdded(value)
|
||||
l.onRootViewAdded(element)
|
||||
l.onRootViewsChanged(this as List<View>)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
override fun remove(value: View): Boolean {
|
||||
val ret = super.remove(value)
|
||||
override fun remove(element: View): Boolean {
|
||||
val ret = super.remove(element)
|
||||
listener?.let { l ->
|
||||
l.onRootViewRemoved(value)
|
||||
l.onRootViewRemoved(element)
|
||||
l.onRootViewsChanged(this as List<View>)
|
||||
}
|
||||
|
||||
@@ -128,7 +127,7 @@ class RootViewResolver {
|
||||
viewsField?.let { field ->
|
||||
if (Build.VERSION.SDK_INT < 19) {
|
||||
val arr = field[windowManagerObj] as Array<View>
|
||||
views = arr.toList() as List<View>
|
||||
views = arr.toList()
|
||||
} else {
|
||||
views = field[windowManagerObj] as List<View>
|
||||
}
|
||||
@@ -148,16 +147,20 @@ class RootViewResolver {
|
||||
return null
|
||||
}
|
||||
|
||||
val roots: ArrayList<RootView> = ArrayList()
|
||||
val roots = mutableListOf<RootView>()
|
||||
views?.let { views ->
|
||||
params?.let { params ->
|
||||
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() {
|
||||
|
||||
@@ -8,10 +8,14 @@
|
||||
package com.facebook.flipper.plugins.uidebugger.descriptors
|
||||
|
||||
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.RootViewResolver
|
||||
|
||||
object ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
|
||||
|
||||
val rootsLocal = RootViewResolver()
|
||||
override fun onGetActiveChild(node: ApplicationRef): Any? {
|
||||
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>) {
|
||||
val activeRoots = node.rootViews
|
||||
|
||||
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
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ContextWrapper
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
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.ApplicationRef
|
||||
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
|
||||
@@ -27,66 +24,27 @@ class ApplicationTreeObserver(val context: Context) : TreeObserver<ApplicationRe
|
||||
override val type = "Application"
|
||||
|
||||
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 addRemoveListener =
|
||||
object : ApplicationRef.ActivityStackChangedListener {
|
||||
val rootViewListener =
|
||||
object : RootViewResolver.Listener {
|
||||
override fun onRootViewAdded(rootView: View) {}
|
||||
|
||||
override fun onActivityAdded(activity: Activity, stack: List<Activity>) {
|
||||
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 onRootViewRemoved(rootView: View) {}
|
||||
|
||||
override fun onActivityStackChanged(stack: List<Activity>) {}
|
||||
|
||||
override fun onActivityDestroyed(activity: Activity, stack: List<Activity>) {
|
||||
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")
|
||||
override fun onRootViewsChanged(rootViews: List<View>) {
|
||||
Log.i(LogTag, "Root views updated, num ${rootViews.size}")
|
||||
traverseAndSend(context, applicationRef)
|
||||
}
|
||||
}
|
||||
|
||||
context.applicationRef.setActivityStackChangedListener(addRemoveListener)
|
||||
context.applicationRef.rootsResolver.attachListener(rootViewListener)
|
||||
// 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.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() {
|
||||
|
||||
Reference in New Issue
Block a user