是否可以以编程方式枚举应用程序中的所有android.view.Window
或装饰视图?
例如,Dialogs
都将在 新Window
中打开,与主活动窗口分开。我可以通过Dialog.getWindow()
找到它们,但我不确定如何使用内置组件(例如活动菜单弹出窗口)来做到这一点。
有什么方法,从Application
、Context
或WindowManager
或其他东西中枚举与我的应用程序关联的 Windows?
我可以用adb dumpsys window
看到应用程序的所有窗口,但我正在寻找一种在不需要 root 的情况下在我的应用程序中执行此操作的方法。
>我找到了一种方法,通过对@hidden WindowManagerGlobal
的反思来做到这一点。至少到目前为止,我知道这适用于 android-18。
private void logRootViews() {
try {
Class wmgClass = Class.forName("android.view.WindowManagerGlobal");
Object wmgInstnace = wmgClass.getMethod("getInstance").invoke(null, (Object[])null);
Method getViewRootNames = wmgClass.getMethod("getViewRootNames");
Method getRootView = wmgClass.getMethod("getRootView", String.class);
String[] rootViewNames = (String[])getViewRootNames.invoke(wmgInstnace, (Object[])null);
for(String viewName : rootViewNames) {
View rootView = (View)getRootView.invoke(wmgInstnace, viewName);
Log.i(TAG, "Found root view: " + viewName + ": " + rootView);
}
} catch (Exception e) {
e.printStackTrace();
}
}
输出:
找到的根视图: com.example.paintsample/com.example.paintsample.PaintSample/android.view.ViewRootImpl@41deeff0: com.android.internal.policy.impl.PhoneWindow$DecorView{41dcc278 V.E.....R.......0,0-768,1184}
找到的根视图: 弹出窗口:42887380/android.view.ViewRootImpl@42891820: android.widget.PopupWindow$PopupViewContainer{42891450 V.E..... ........0,0-424,618}
当然,赏金仍然可供任何能找到更好方法的人争夺:)
我不完全确定这是否回答了实际问题,但这是按照接受答案中的建议获取所有根视图的更好方法。
如前所述,我也设法仅使用反射来完成此操作,除了此代码支持 API 14 及更高版本的所有版本(我没有在下面检查):
public static List<View> getWindowManagerViews() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// get the list from WindowManagerImpl.mViews
Class wmiClass = Class.forName("android.view.WindowManagerImpl");
Object wmiInstance = wmiClass.getMethod("getDefault").invoke(null);
return viewsFromWM(wmiClass, wmiInstance);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// get the list from WindowManagerGlobal.mViews
Class wmgClass = Class.forName("android.view.WindowManagerGlobal");
Object wmgInstance = wmgClass.getMethod("getInstance").invoke(null);
return viewsFromWM(wmgClass, wmgInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
return new ArrayList<View>();
}
private static List<View> viewsFromWM(Class wmClass, Object wmInstance) throws Exception {
Field viewsField = wmClass.getDeclaredField("mViews");
viewsField.setAccessible(true);
Object views = viewsField.get(wmInstance);
if (views instanceof List) {
return (List<View>) viewsField.get(wmInstance);
} else if (views instanceof View[]) {
return Arrays.asList((View[])viewsField.get(wmInstance));
}
return new ArrayList<View>();
}
附带的Hierarchyviewer工具物有所值。
你可以直接使用@hidden API,而无需通过访问类文件并在Android SDK中添加到你的android.jar中使用反射。方法如下:https://devmaze.wordpress.com/2011/01/18/using-com-android-internal-part-1-introduction/
安卓的源代码.jar对于特定的安卓版本(19,21,22,23,24)可以在这里获取: https://github.com/anggrayudi/android-hidden-api
因此,您可以直接使用WindowManagerGlobal类来获取所有根视图,例如,
private void logRootViews() {
WindowManagerGlobal windowManagerGlobal = WindowManagerGlobal.getInstance();
String[] rootViewNames = windowManagerGlobal.getViewRootNames();
for (String viewName : rootViewNames) {
View rootView = windowManagerGlobal.getRootView(viewName);
Log.i("", "Root view is: " + viewName + ": " + rootView);
/*do what you want with the rootView*/
}
}
输出:
根视图是: com.example.paintsample/com.example.paintsample.PaintSample/android.view.ViewRootImpl@41deeff0: com.android.internal.policy.impl.PhoneWindow$DecorView{41dcc278 V.E.....R.......0,0-768,1184}
根视图是: PopupWindow:42887380/android.view.ViewRootImpl@42891820: android.widget.PopupWindow$PopupViewContainer{42891450 V.E..... ........0,0-424,618}
负担得起minSdk 29
的人可以使用WindowInspector.getGlobalWindowViews()。在内部,它指的是mViews
WindowManagerGlobal
的财产,但可供公众使用。
上面的每个解决方案都是在 Java 中,我为 Kotlin 制作了转换 Andrew Lavers 答案的解决方案-
try
{
val wmgClass = Class.forName("android.view.WindowManagerGlobal")
val wagInstance = wmgClass.getMethod("getInstance").invoke(null)
val getViewRootNames: Method = wmgClass.getMethod("getViewRootNames")
val getRootView: Method = wmgClass.getMethod("getRootView", String::class.java)
val rootViewNames = getViewRootNames.invoke(wagInstance) as Array<String>
for (viewName in rootViewNames) {
val rootView = getRootView.invoke(wagInstance, viewName) as View
}
} catch (exception: java.lang.Exception {}