【问题标题】:Is there a way to programmatically locate all Windows within a given application?有没有办法以编程方式定位给定应用程序中的所有 Windows?
【发布时间】:2013-10-29 21:58:50
【问题描述】:

是否可以以编程方式枚举所有android.view.Windows 或应用程序中的装饰视图?

Dialogs 例如将在新的Window 中打开,与主活动窗口分开。我可以通过Dialog.getWindow() 找到它们,但我不确定如何使用内置组件(例如活动菜单弹出窗口)来做到这一点。

有什么方法可以通过ApplicationContextWindowManager 或其他方式枚举与我的应用关联的 Windows?

我可以使用adb dumpsys window 查看我的应用程序的所有窗口,但我正在寻找一种无需 root 即可在我的应用程序中执行此操作的方法。

【问题讨论】:

  • 活动菜单弹出的窗口与活动的相同,Activity.getWindow() 对你不起作用吗?
  • 不幸的是,没有。我正在运行 android FingerPaint 示例(在 4.3 上),在我点击三点菜单按钮后,我可以在监视器中看到弹出窗口在它自己的窗口中。我还可以运行“adb shell dumpsys window tokens”并看到绘图应用确实有两个与之关联的窗口:allAppWindows=[Window{418f9ce8 u0 com.example.paintsample/com.example.paintsample.PaintSample}, Window{41a06d08 u0 PopupWindow:41ac65a0}] 与对话框相同。
  • 只是好奇,你为什么需要这些信息,或者更确切地说,一旦你有了这些信息你会怎么处理?
  • @Josh 我正在编写一个用于在给定应用程序中截取屏幕截图的库。简单地在根视图上调用 getDrawingCache 就很容易了,但是为了包括像重叠对话框这样的东西,它们不是同一个窗口层次结构,我不得不跳过这个障碍。
  • 哇,祝你好运,听起来很痛苦 :)

标签: android android-view android-window


【解决方案1】:

我找到了一种方法来通过@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}

对于任何能够找到更好方法的人来说,赏金当然仍然存在:)

【讨论】:

    【解决方案2】:

    我不完全确定这是否回答了实际问题,但它是获得所有根视图的更好方法,如已接受的答案中所建议的那样。

    正如那里提到的,我也设法仅使用反射来完成此操作,除了此代码支持 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>();
    }
    

    【讨论】:

    • 这是最好的解决方案!!
    【解决方案3】:

    SDK 附带的Hierarchyviewer 工具物超所值。

    【讨论】:

    • 是的,但我正在寻找一种以编程方式执行此操作的方法(我有,但我必须触摸一个隐藏的类才能做到这一点)。会稍微修正一下问题以使其更清楚。
    • 哦,没关系 - 我不认为您在设备本身上使用 adb dumpsys。
    【解决方案4】:

    您可以直接使用@hidden API,而无需通过访问类文件并将其添加到Android SDK 中的android.jar 来使用反射。这是如何做: https://devmaze.wordpress.com/2011/01/18/using-com-android-internal-part-1-introduction/

    并且可以在此处获取特定 android 版本 (19,21,22,23,24) 的 android.jar 的源代码: 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 VE.... R ....... 0,0-768,1184}

    根视图为:PopupWindow:42887380/android.view.ViewRootImpl@42891820: android.widget.PopupWindow$PopupViewContainer{42891450 VE.... ........ 0,0-424,618}

    【讨论】:

      【解决方案5】:

      那些买得起minSdk 29 的人可以使用WindowInspector.getGlobalWindowViews()。在内部它指的是来自WindowManagerGlobalmViews 属性,但可供公众使用。

      【讨论】:

        【解决方案6】:

        每个解决方案都在上面的 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 {}
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-07-25
          • 2021-12-07
          • 2021-08-15
          • 2017-03-03
          • 2010-09-14
          • 1970-01-01
          • 2021-08-28
          相关资源
          最近更新 更多