Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Descripción general de los procesos y subprocesos

Cuando se inicia un componente de la aplicación y esta no tiene ningún otro componente ejecutándose, el sistema de Android inicia un nuevo proceso de Linux para la aplicación con un único subproceso de ejecución. De manera predeterminada, todos los componentes de la misma aplicación se ejecutan en el mismo proceso y subproceso (que se denomina "subproceso principal"). Si se inicia el componente de una aplicación y ya existe un proceso para la aplicación (porque existe otro componente), el componente se inicia dentro de ese proceso y usa el mismo subproceso de ejecución. Sin embargo, puedes configurar que diferentes componentes de la aplicación se ejecuten en procesos separados y puedes crear subprocesos adicionales para cualquier proceso.

En este documento, se analiza cómo funcionan los procesos y los subprocesos en una aplicación para Android.

Procesos

De manera predeterminada, todos los componentes de la misma aplicación se ejecutan en el mismo proceso, y la mayoría de las aplicaciones no deben cambiar esto. No obstante, si necesitas controlar a qué procesos pertenece un componente determinado, puedes hacerlo en el archivo de manifiesto.

La entrada de manifiesto de cada tipo de elemento de componente (<activity>, <service>, <receiver> y <provider>) admite un atributo android:process que puede especificar un proceso en el que debe ejecutarse el componente. Puedes establecer este atributo para que cada componente se ejecute en su propio proceso o para que algunos componentes compartan un proceso y otros no. También puedes establecer android:process para que los componentes de diferentes aplicaciones se ejecuten en el mismo proceso (si las aplicaciones comparten el mismo ID de usuario de Linux y están firmadas con los mismos certificados).

El elemento <application> también admite un atributo android:process para establecer un valor que se aplica a todos los componentes.

Android puede decidir finalizar un proceso en algún momento, cuando la memoria es baja y la requieren otros procesos que son inmediatamente más necesarios para lo que desea el usuario. En consecuencia, se destruyen los componentes de la aplicación que se ejecutan en el proceso que se cancela. Un proceso se inicia de nuevo para esos componentes cuando vuelve a haber trabajo para ellos.

Cuando decides qué procesos cancelar, el sistema Android pondera su importancia relativa para el usuario. Por ejemplo, cierra más rápido un proceso que aloja actividades que ya no se ven en la pantalla que un proceso que aloja actividades visibles. La decisión de si se debe finalizar un proceso, por lo tanto, depende del estado de los componentes que se ejecutan en él.

Los detalles del ciclo de vida del proceso y su relación con los estados de la aplicación se discuten en Ciclo de vida de la aplicación y los procesos.

Subprocesos

Cuando se inicia una aplicación, el sistema crea un subproceso de ejecución, que se denomina "principal". Este subproceso es muy importante porque está a cargo de distribuir eventos a los widgets correspondientes de la interfaz de usuario, incluidos los eventos de dibujo. También es casi siempre el subproceso en el que tu aplicación interactúa con los componentes del kit de herramientas de la IU de Android (componentes de los paquetes android.widget y android.view). Por esto, el subproceso principal también se suele denominar subproceso de IU. Sin embargo, en circunstancias especiales, es posible que el subproceso principal de una app no sea el de la IU. Para obtener más información, consulta Anotaciones de los subprocesos.

El sistema no crea un subproceso separado para cada instancia de un componente. Se crean instancias de todos los componentes que se ejecutan en el mismo proceso del subproceso de IU, y las llamadas del sistema a cada componente se distribuyen desde ese subproceso. Por lo tanto, los métodos que responden a las devoluciones de llamada del sistema (como onKeyDown() para informar acciones del usuario o un método de devolución de llamada de ciclo de vida) siempre se ejecutan en el subproceso de IU del proceso.

Por ejemplo, cuando el usuario toca un botón en la pantalla, el subproceso de IU de la aplicación distribuye el evento de toque al widget, que, a su vez, establece su estado presionado y publica una solicitud de invalidación en la cola de eventos. El subproceso de IU saca de la cola la solicitud y notifica al widget que debe volver a dibujarse.

Cuando la aplicación realiza trabajo intensivo en respuesta a una interacción del usuario, este modelo de subproceso único puede generar un rendimiento deficiente, a menos que implementes la aplicación de una manera correcta. Específicamente, si todo sucede en el subproceso de IU, realizar operaciones prolongadas, como acceder a la red o consultar la base de datos, bloqueará toda la IU. Cuando se bloquea el subproceso, no se pueden distribuir los eventos, ni siquiera los de dibujo. Desde la perspectiva del usuario, la aplicación no responde. Aún peor, si se bloquea el subproceso de IU durante más de unos segundos (específicamente, unos 5), al usuario se le presenta el infame cuadro de diálogo "la aplicación no responde" (ANR). Puede que el usuario decida salir de la aplicación y desinstalarla si no está contento.

Además, el paquete de herramientas de la IU de Android no es seguro para subprocesos. En consecuencia, no debes manipular la IU desde un subproceso de trabajo (debes realizar la manipulación de la interfaz de usuario desde el subproceso de IU). Por lo tanto, solo existen dos reglas para el modelo de subproceso único de Android:

  1. No bloquear el subproceso de IU
  2. No acceder al paquete de herramientas de la IU de Android desde fuera del subproceso de IU

Subprocesos de trabajo

Debido al modelo de subproceso único descrito previamente, es fundamental para la capacidad de respuesta de la IU de la aplicación que no bloquees el subproceso de IU. Si tienes que realizar operaciones que no son inmediatas, debes asegurarte de hacerlo en subprocesos separados (subprocesos "en segundo plano" o "de trabajo").

Sin embargo, ten en cuenta que no puedes actualizar la IU desde ningún otro subproceso que no sea el de la IU o el "principal".

Para solucionar este problema, Android ofrece varias maneras de acceder al subproceso de IU desde otros subprocesos. En la siguiente lista, se incluyen métodos que pueden ser útiles:

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // a potentially time consuming task
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // a potentially time consuming task
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Esta implementación es segura para subprocesos: la operación en segundo plano se realiza desde un subproceso separado, mientras que el ImageView se manipula desde el subproceso de IU.

No obstante, a medida que aumenta la complejidad de la operación, este tipo de código puede volverse complicado y difícil de mantener. Para manejar interacciones más complejas con un subproceso de trabajo, deberías considerar usar un Handler en el subproceso de trabajo a fin de procesar mensajes enviados desde el subproceso de IU. Posiblemente, la mejor solución sea extender la clase AsyncTask, que simplifica la ejecución de las tareas del subproceso de trabajo que tienen que interactuar con la IU.

Cómo usar AsyncTask

AsyncTask te permite realizar trabajos asíncronos en la interfaz de usuario. Realiza las operaciones de bloqueo en un subproceso de trabajo y, luego, publica los resultados en el subproceso de IU sin que debas administrar los subprocesos o controladores.

Para usarla, debes crear una subclase de AsyncTask e implementar el método de devolución de llamada doInBackground(), que se ejecuta en un grupo de subprocesos en segundo plano. Para actualizar la IU, debes implementar onPostExecute(), que entrega el resultado de doInBackground() y se ejecuta en el subproceso de IU. Por ello, puedes actualizar la IU de forma segura. Luego, puedes ejecutar la tarea llamando a execute() desde el subproceso de IU.

Debes leer la referencia de AsyncTask para comprender completamente cómo usar esta clase.

Métodos seguros para subprocesos

En algunos casos, los métodos que implementas pueden ser llamados por más de un subproceso y, por lo tanto, se deben escribir de modo que sean seguros para subprocesos.

Esto es particularmente cierto para los métodos que se pueden llamar de manera remota (como los de un servicio enlazado). Cuando una llamada a un método implementado en un IBinder se origina en el mismo proceso en el que se ejecuta el IBinder, el método se ejecuta en el subproceso del emisor. Sin embargo, cuando la llamada se origina en otro proceso, el método se ejecuta en un subproceso elegido entre un grupo de subprocesos que el sistema mantiene en el mismo proceso que el IBinder (no se ejecuta en el subproceso de IU del proceso). Por ejemplo, mientras que el método onBind() de un servicio sería llamado desde un subproceso de IU del proceso de servicio, los métodos implementados en el objeto que muestra onBind() (como una subclase que implementa métodos de RPC) serían llamados desde los subprocesos del grupo. Como un servicio puede tener más de un cliente, más de un subproceso de grupo puede interactuar con el mismo método IBinder al mismo tiempo. Por lo tanto, deben implementarse los métodos IBinder a fin de que sean seguros para subprocesos.

Asimismo, un proveedor de contenido puede recibir solicitudes de datos que se originen en otros procesos. Aunque las clases ContentResolver y ContentProvider ocultan los detalles sobre cómo se administra la comunicación entre los procesos, los métodos ContentProvider que responden a esas solicitudes (los métodos query(), insert(), delete(), update() y getType()) se llaman desde un grupo de subprocesos en el proceso del proveedor de contenido, no el subproceso de IU correspondiente al proceso. Como se puede llamar a estos métodos desde cualquier cantidad de subprocesos al mismo tiempo, también se deben implementar a fin de que sean seguros para subprocesos.

Comunicación entre procesos

Android ofrece un mecanismo para comunicación entre procesos (IPC) con llamadas de procedimiento remoto (RPC), en el que una actividad u otro componente de la aplicación llama a un método, pero este se ejecuta de manera remota (en otro proceso), y los resultados se devuelven al emisor. Esto implica la descomposición de una llamada a un método y sus datos a un nivel en el que el sistema operativo pueda comprender; su transmisión desde el proceso y el espacio de dirección locales hacia el proceso y el espacio de dirección remotos; y, a continuación, el reensamble y la recreación de la llamada allí. Los valores de retorno se transmiten en la dirección opuesta. Android proporciona todo el código para realizar estas transacciones de IPC de manera que tú puedas centrarte en definir e implementar la interfaz de programación de RPC.

Para realizar una IPC, tu aplicación debe vincularse con un servicio mediante bindService(). Para obtener más información, consulta la guía para desarrolladores Servicios.