El secreto de la jerarquía visual de Android
No es difícil ver en la imagen que la capa de control de voz que queremos implementar es en realidad una capa encima de la vista de la aplicación y el cuadro de mensaje dentro de la vista, y debajo de la ventana emergente del botón Atrás. . Debido a que la exploración del nivel de vista de Android no ha sido muy profunda, resumí y clasifiqué el conocimiento del nivel de vista de Android haciendo esta función.
Primero, utilizamos un diagrama jerárquico para aclarar algunos conceptos importantes, como Window, DecorView y mContentParent.
En Android, ya sea Activity, Toast, ActionBar o Dialog, sus vistas están adjuntas a la ventana. De hecho, básicamente todas las vistas se presentan a través de la ventana al mismo tiempo, por lo que la ventana puede entenderse como portadora y administradora de la vista. Hay tres tipos de ventanas: ventanas de aplicaciones, ventanas secundarias y ventanas del sistema. La ventana de clase de aplicación corresponde a una actividad. Las ventanas secundarias no pueden existir solas y deben adjuntarse a una ventana principal específica. Por ejemplo, algunos cuadros de diálogo comunes son ventanas secundarias. Una ventana del sistema es una ventana que solo se puede crear declarando permisos. Por ejemplo, Toast y la barra de estado del sistema son ventanas del sistema.
La vista decorativa es la vista superior de la vista en la ventana. De hecho, DecorView es una subclase de FrameLayout, que contiene LinearLayout con ActionBar y mContentParent.
El nombre mContentParent puede resultar un poco desconocido, pero en realidad es el diseño raíz de la aplicación que usamos a menudo, es decir, Android. R.id.content SetContentView en la actividad en realidad convierte el diseño XML en una vista a través de LayoutInflater y lo agrega a mContentParent.
Cada actividad contendrá una ventana y, en Android, Window tiene solo una clase de implementación PhoneWindow, por lo que cada actividad contendrá una PhoneWindow y la vista de nivel superior DecorView se mantendrá en PhoneWindow. Entonces, ¿cómo establece Activity contacto con PhoneWindow? Exploremos el código fuente:
Durante el inicio de la actividad, se ejecuta el método performLaunchActivity de ActivityThread, que llama al adjunto de la actividad. Cree una instancia de la mWindow retenida por la actividad en el método adjuntar(). Debido a que Activity implementa la interfaz de devolución de llamada de Windows, cuando Window acepta cambios de estado externos, devolverá la llamada al método de Activity.
Como puedes ver, en PhoneWindow aparece la variable miembro DecorView. Aquí, DecorView es una clase interna en PhoneWindow, heredada de FrameLayout.
Este es el método setContentView que llamamos cada vez que escribimos una actividad. Llama internamente a setContentView de getWindow(). Esta mWindow es PhoneWindow.
Vemos que hay tres métodos sobrecargados de setContentView en PhoneWindow. En setContentView (int layoutResID), primero determine el padre mcontendent. Si el padre mcontendent está vacío, se llama la primera vez y se ejecuta el método installDecor() para crear un DecorView y agregarlo al padre mcontendent. Si mContentParent no está vacío, elimine la vista de mContentParent.
Luego convierta el XML en un árbol de vista a través de mLayoutInflater y agréguelo a la vista mContentParent. Una vez completada la adición, la devolución de llamada notifica a onContentChanged, lo que indica que la carga de la interfaz se ha completado.
Primero determine si mDecor está vacío. Si está vacío, generateDecor crea un DecorView. Luego, configure la capacidad de adquisición de enfoque de DecorView para enfocar _ después de _ descendientes, es decir, primero asígnelo a la subvista para su procesamiento. Si no se procesan todas las subvistas, se procesará sola. El primer DecorView no está cargado en mContentParent, por lo que mContentParent está vacío. Llame a generateLayout para agregar el contenido de setContentView a mContentParent.
Los estudiantes que han personalizado la barra de acciones o la pantalla completa de la actividad deben saber que el método requestFeature debe llamarse antes de setContentView. Esta es la razón. La esencia de setContentView es activar el estado de reanudación de la actividad y activar el método makeVisible al mismo tiempo.
GetWindow(). getAttributes() se usa como LayoutParams aquí, en WindowManager:
Se puede ver que el tipo de ventana activa es TYPE_APPLICATION, que determina el nivel de visualización en la capa de la ventana. Una descripción general de este tipo es la siguiente:
El diálogo no pertenece a la vista, es una ventana secundaria de la aplicación, por eso no podemos evitar el diálogo agregando la vista a mContentParent. La ventana del cuadro de diálogo también se completa con el método makeNewWindow de PolicyManager. Los cuadros de diálogo normales deben utilizar el contexto activo. Si se utiliza el contexto de la aplicación, se informará un error. Esto se debe a que no existe un token de aplicación, que generalmente solo es propiedad de la Actividad. El tipo de diálogo regular es el tipo de diálogo adicional de aplicación. A través de diferentes tipos de jerarquías, podemos encontrar las propiedades de WindowManager LayoutParams, como TYPE_SYSTEM_ALERT, TYPE_TOAST, etc., que pueden cubrir completamente el cuadro de diálogo normal. La diferencia entre los dos es que uno es un cuadro de diálogo a nivel del sistema y el otro es un Toast. El cuadro de diálogo del sistema debe solicitar permiso. Por lo tanto, nuestra primera solución es utilizar el cuadro de diálogo normal para el cuadro de diálogo de oclusión y TYPE_SYSTEM_ALERT para el cuadro de mensaje de voz. Sin embargo, todos sabemos que Android tiene un problema inevitable que es la personalización del fabricante. En la capa del marco MUI, por motivos de "seguridad", los permisos de las ventanas flotantes están desactivados para los usuarios de forma predeterminada. Esto significa que las vistas con el atributo TYPE_SYSTEM_ALERT establecido no se pueden mostrar de forma predeterminada y solo se pueden mostrar después de que el usuario las active manualmente. los permisos.
Aunque puede saltar para abrir la página de permisos según el modelo del usuario cuando el usuario inicia, esta experiencia imperfecta como desarrollo perceptivo sigue siendo inaceptable. Según investigaciones anteriores sobre la jerarquía de vistas de Android, tenemos una segunda solución. La vista de la aplicación se almacena en mContentParent. Pertenece a la misma jerarquía de ventanas TYPE_APPLICATION que Actividad y pertenece a la capa inferior. El nivel del cuadro de diálogo normal es TYPE_APPLICATION_attached_Dialog, por lo que consideramos el cuadro de diálogo normal como uno discreto en el nivel superior. Para los cuadros de aviso, solo debe considerar la discreta ventana emergente y la capa de control de voz a continuación. Debido a que la capa de control de voz debe poder evitar que aparezcan mensajes emergentes, debe estar en la parte superior de la ventana emergente. Después del aprendizaje previo, agregamos la ventana emergente a mContentParent y la capa de control de voz a la capa DecorView, lo que resolvió perfectamente este problema.
mContentParent es un FrameLayout y la vista de la aplicación se agrega primero a MContentParent a través de sentContentView. Como ventana emergente de aviso, el orden de adición debe colocarse después de la vista de la aplicación, de modo que cuando la ventana emergente de aviso se agregue nuevamente a mContentParent, se agregará a la vista de la aplicación. DecorView es el contenedor principal de mContentParent y también de FrameLayout. Al agregar un cuadro de mensaje de voz, mContentParent ya debe existir, por lo que debe estar encima de mContentParent al agregarlo.
De esta forma se resolvió perfectamente un requerimiento aparentemente complejo explorando el código fuente de Android. Muchas veces, cuando nos encontramos con un problema que es difícil de resolver, también podríamos intentar volver al origen del problema y pensar en la esencia del problema. Muchas veces se harán diferentes descubrimientos.