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 Android inicia un nuevo proceso de Linux para la aplicación con un solo subproceso de ejecución. De forma 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 un componente de la aplicación y ya existe un proceso para esa aplicación (porque otro componente de la aplicación ya se inició), 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 forma predeterminada, todos los componentes de una aplicación se ejecutan en el mismo proceso y la mayoría de las aplicaciones no cambian esto. Sin embargo, si necesitas controlar a qué proceso pertenece un componente determinado, puedes hacerlo en el archivo de manifiesto.

La entrada de manifiesto para cada tipo de elemento de componente (<activity>, <service>, <receiver> y <provider>) admite un atributo android:process que puede especificar un proceso en el que se ejecuta 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 configurar android:process para que los componentes de diferentes aplicaciones se ejecuten en el mismo proceso, siempre que las aplicaciones compartan 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, que puedes usar para establecer un valor predeterminado que se aplique a todos los componentes.

Android puede decidir cerrar un proceso en algún momento, cuando otros procesos que entregan recursos al usuario de manera más inmediata. En consecuencia, los componentes de la aplicación que se ejecutan en el proceso que se cierra se destruyen. Un proceso se inicia de nuevo para esos componentes cuando hay trabajo para ellos.

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

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

Threads

Cuando se inicia una aplicación, el sistema crea un subproceso de ejecución para la aplicación, que se denomina subproceso principal. Este subproceso es muy importante, ya que está a cargo de despachar eventos a los widgets correspondientes de la interfaz de usuario, incluidos los eventos de dibujo. Casi siempre es el subproceso en el que la aplicación interactúa con los componentes de los paquetes android.widget y android.view del kit de herramientas de IU de Android. Por este motivo, el subproceso principal a veces se denomina 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 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 en el subproceso de IU, y las llamadas del sistema a cada componente se despachan desde ese subproceso. En consecuencia, 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 tu app envía el evento táctil 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 para que se vuelva a dibujar.

A menos que implementes tu aplicación correctamente, este modelo de un solo subproceso puede generar un rendimiento deficiente cuando la app realiza un trabajo intensivo en respuesta a la interacción del usuario. Si realizas operaciones largas en el subproceso de IU, como acceder a la red o consultar la base de datos, se bloquea toda la IU. Cuando se bloquea el subproceso, no se pueden distribuir los eventos, incluidos los de dibujo.

Desde la perspectiva del usuario, la aplicación no responde. Lo que es peor, si el subproceso de IU se bloquea durante más de unos segundos, se le muestra al usuario el diálogo "Aplicación no responde" (ANR). El usuario podría decidir salir de la aplicación o incluso desinstalarla.

Ten en cuenta que el kit de herramientas de IU de Android no es seguro para subprocesos. Por lo tanto, no manipules tu IU desde un subproceso de trabajo. Toda la manipulación de la interfaz de usuario desde el subproceso de IU Existen dos reglas para el modelo de un solo subproceso de Android:

  1. No bloquees el subproceso de IU.
  2. No accedas al kit de herramientas de IU de Android desde fuera del subproceso de IU.

Subprocesos de trabajo

Debido a este modelo de un solo subproceso, es fundamental para la capacidad de respuesta de la IU de tu aplicación que no bloquees el subproceso de IU. Si tienes que realizar operaciones que no son instantáneas, asegúrate de hacerlo en subprocesos en segundo plano o de trabajo independientes. Recuerda que no puedes actualizar la IU desde ningún otro subproceso que no sea el de IU, o el principal.

Para ayudarte a seguir estas reglas, Android ofrece varias formas de acceder al subproceso de IU desde otros subprocesos. En la siguiente lista, se incluyen métodos que pueden ser útiles:

En el siguiente ejemplo, se usa View.post(Runnable):

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 los subprocesos, ya que la operación en segundo plano se realiza desde un subproceso independiente, mientras que ImageView siempre se manipula desde el subproceso de IU.

Sin embargo, a medida que aumenta la complejidad de la operación, este tipo de código puede volverse complicado y difícil de mantener. Para controlar interacciones más complejas con un subproceso de trabajo, procura usar un Handler en el subproceso de trabajo a fin de procesar los mensajes enviados desde el subproceso de IU. Para obtener una explicación completa de cómo programar el trabajo en subprocesos en segundo plano y comunicarte con el subproceso de IU, consulta Descripción general del trabajo en segundo plano.

Métodos seguros para subprocesos

En algunas situaciones, los métodos que implementas se llaman desde más de un subproceso y, por lo tanto, se deben escribir para que sean seguros para subprocesos.

Esto se aplica principalmente a los métodos que se pueden llamar de forma remota, como los métodos en un servicio vinculado. Cuando una llamada a un método implementado en un IBinder se origina en el mismo proceso en el que se ejecuta 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 de un grupo de subprocesos que el sistema mantiene en el mismo proceso que IBinder. No se ejecuta en el subproceso de IU del proceso.

Por ejemplo, mientras que el método onBind() de un servicio se llama desde el subproceso de IU del proceso del servicio, los métodos implementados en el objeto que muestra onBind(), como una subclase que implementa métodos de llamada de procedimiento remoto (RPC), se llaman desde subprocesos en el 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 que se deben implementar métodos IBinder a fin de que sean seguros para los subprocesos.

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

Comunicación entre procesos

Android ofrece un mecanismo para IPC con RPC, en el que una actividad o algún otro componente de la aplicación llama a un método, pero lo ejecuta de forma remota en otro proceso, y el llamador recibe cualquier resultado. Esto implica la descomposición de una llamada de método y sus datos a un nivel que el sistema operativo pueda comprender, su transmisión desde el proceso y el espacio de dirección locales al proceso y al espacio de direcciones remotos y, luego, el reensamble y la recreación de la llamada allí.

Luego, los valores que se muestran se transmiten en la dirección opuesta. Android proporciona todo el código para realizar estas transacciones de IPC a fin de que puedas enfocarte en definir e implementar la interfaz de programación de RPC.

Para realizar una IPC, tu aplicación debe vincularse a un servicio mediante bindService(). Para obtener más información, consulta la Descripción general de los servicios.