Есть ли способ программно найти все Windows в данном приложении?

Можно ли программно перечислить все android.view.Windows или виды декора в приложении?

Например, Dialogs оба будут открываться в новом Window, отдельном от основного окна Activity. Я могу найти их через Dialog.getWindow(), но я не уверен, как бы я сделал это со встроенными компонентами, такими как всплывающее меню действий.

Есть ли способ из Application, Context или WindowManager или чего-то еще перечислить Windows, связанные с моим приложением?

Я могу видеть все окна моего приложения с помощью adb dumpsys window, но я ищу способ сделать это в своем приложении, не требуя root.


person Andrew Lavers    schedule 29.10.2013    source источник
comment
Окно для всплывающего меню активности будет таким же, как и у Activity, не будет ли Activity.getWindow() работать для вас?   -  person kassim    schedule 01.11.2013
comment
К сожалению нет. Я запускаю пример Android FingerPaint (на 4.3), и после того, как я нажимаю кнопку меню с тремя точками, я вижу на мониторе, что всплывающее окно находится в своем собственном окне. Я также могу запустить токены окна adb shell dumpsys и увидеть, что приложение рисования действительно имеет два связанных с ним окна: allAppWindows=[Window{418f9ce8 u0 com.example.paintsample/com.example.paintsample.PaintSample}, Window{41a06d08 u0 PopupWindow :41ac65a0}] То же самое с диалогами.   -  person Andrew Lavers    schedule 01.11.2013
comment
Просто любопытно, зачем вам эта информация, или, точнее, что вы собираетесь с ней делать, когда она у вас есть?   -  person Josh    schedule 10.12.2014
comment
@Josh Я писал библиотеку для создания скриншотов в данном приложении. Достаточно просто вызвать getDrawingCache в корневом представлении, но чтобы включить такие вещи, как наложенные диалоги, которые не являются той же иерархией окон, мне пришлось преодолеть это препятствие.   -  person Andrew Lavers    schedule 11.12.2014
comment
Вау, удачи с этим, звучит как много боли :)   -  person Josh    schedule 11.12.2014


Ответы (6)


Я нашел способ сделать это, поразмыслив над файлом @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 VE.... R... .... 0,0-768,1184}

Найдено корневое представление: PopupWindow:42887380/android.view.ViewRootImpl@42891820: android.widget.PopupWindow$PopupViewContainer{42891450 VE............. 0,0-424,618}

Конечно, Bounty по-прежнему доступен для всех, кто найдет лучший способ :)

person Andrew Lavers    schedule 04.11.2013

Я не совсем уверен, что это отвечает на реальный вопрос, но это лучший способ получить все корневые представления, как это предлагается в принятом ответе.

Как упоминалось там, мне также удалось выполнить это только с помощью отражения, за исключением того, что этот код поддерживает все версии от 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>();
}
person Boris    schedule 12.12.2016
comment
Это лучшее решение здесь !! - person refaelos; 12.12.2016

Инструмент Hierarchyviewer, входящий в состав SDK, на вес золота.

person Pedantic    schedule 04.11.2013
comment
Это так, но я искал способ сделать это программно (который у меня есть, хотя для этого мне нужно коснуться скрытого класса). Немного исправлю вопрос, чтобы было понятнее. - person Andrew Lavers; 05.11.2013
comment
О, все в порядке - я не подумал, что вы используете adb dumpsys на самом устройстве. - person Pedantic; 05.11.2013

Вы можете использовать @hidden API напрямую, без использования отражения, получив доступ к файлам классов и добавив их в свой файл android.jar в Android SDK. Вот как: https://devmaze.wordpress.com/2011/01/18/using-com-android-internal-part-1-introduction/

А исходники android.jar для конкретной версии Android (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 VE.... R... .... 0,0-768,1184}

Корневой вид: PopupWindow:42887380/android.view.ViewRootImpl@42891820: android.widget.PopupWindow$PopupViewContainer{42891450 VE............. 0,0-424,618}

person Uday Koushik    schedule 10.03.2017


Каждое решение находится на Java выше, и я сделал решение для Kotlin, конвертируя Ответ Эндрю Лаверса-

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 {}
person Gk Mohammad Emon    schedule 22.09.2020