Cuando se inicia un componente de la aplicación y esta no tiene ningún otro componente en ejecución, 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, llamado subproceso principal.
Si se inicia un componente de la aplicación y ya hay un proceso para esa aplicación, porque ya se inició otro componente de la aplicación, el componente se inicia dentro de ese proceso y usa el mismo subproceso de ejecución. Sin embargo, puedes hacer que los diferentes componentes de tu aplicación se ejecuten en procesos separados y crear subprocesos adicionales para cualquier proceso.
En este documento, se explica 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 configurar este atributo para que cada componente se ejecute en su propio proceso o para que algunos componentes compartan un proceso mientras que 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 se firmen con los mismos certificados.
El <application>
elemento también admite un atributo android:process, que puedes usar para establecer un valor predeterminado que se aplique a todos los componentes.
Android podría decidir cerrar un proceso en algún momento, cuando otros procesos que atienden al usuario de forma más inmediata requieran recursos. En consecuencia, se destruyen los componentes de la aplicación que se ejecutan en el proceso que se cierra. Se vuelve a iniciar un proceso para esos componentes cuando tienen trabajo que hacer.
Cuando decide qué procesos cerrar, el sistema Android sopesa su importancia relativa para el usuario. Por ejemplo, cierra más fácilmente un proceso que aloja actividades que ya no están visibles en la pantalla, en comparación con 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 explican en Ciclo de vida de procesos y aplicaciones.
Subprocesos
Cuando se inicia una aplicación, el sistema crea un subproceso de ejecución para la aplicación, llamado subproceso principal. Este subproceso es muy importante, ya que se encarga de enviar eventos a los widgets de la interfaz de usuario adecuados, incluidos los eventos de dibujo. También es casi siempre el subproceso en el que tu 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, a veces se llama al subproceso principal subproceso de IU. Sin embargo, en circunstancias especiales, es posible que el subproceso principal de una app no sea su subproceso de IU. Para obtener más información, consulta Anotaciones
de subprocesos.
El sistema no crea un subproceso separado para cada instancia de un componente. Todos los componentes que se ejecutan en el mismo proceso se crean en el subproceso de IU, y las llamadas del sistema a cada componente se envían desde ese subproceso. En consecuencia, los métodos que responden a las devoluciones de llamada del sistema, como onKeyDown() para informar las 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 quita la solicitud de la cola y notifica al widget para que se vuelva a dibujar.
A menos que implementes tu aplicación de forma adecuada, este modelo de un solo subproceso puede generar un rendimiento deficiente cuando tu app realiza un trabajo intensivo en respuesta a la interacción del usuario. Realizar operaciones largas en el subproceso de IU, como el acceso a la red o las consultas de bases de datos, bloquea toda la IU. Cuando se bloquea el subproceso, no se pueden enviar eventos, incluidos los eventos de dibujo.
Desde la perspectiva del usuario, la aplicación parece dejar de responder. Peor aún, si el subproceso de IU se bloquea durante más de unos segundos, el usuario verá el diálogo "La aplicación no responde" (ANR). Luego, el usuario podría decidir salir de tu aplicación o incluso desinstalarla.
Ten en cuenta que el kit de herramientas de IU de Android no es seguro para los subprocesos. Por lo tanto, no manipules tu IU desde un subproceso de trabajo. Realiza toda la manipulación en tu interfaz de usuario desde el subproceso de IU. Existen dos reglas para el modelo de un solo subproceso de Android:
- No bloquees el subproceso de IU.
- 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 operaciones que realizar que no son instantáneas, asegúrate de hacerlas en subprocesos en segundo plano o de trabajo separados. Solo recuerda que no puedes actualizar la IU desde ningún subproceso que no sea el subproceso de IU o principal.
Para ayudarte a seguir estas reglas, Android ofrece varias formas de acceder al subproceso de IU desde otros subprocesos. Esta es una lista de métodos que pueden ayudarte:
En los siguientes ejemplos, se muestra cómo transferir una tarea a un subproceso en segundo plano y actualizar el subproceso de IU una vez que se completa la tarea:
Kotlin
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
Esta implementación es segura para los subprocesos, ya que la operación en segundo plano se realiza desde un subproceso separado, 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, puedes considerar usar un Handler en tu subproceso de trabajo para procesar los mensajes entregados desde el subproceso de IU. Para obtener una explicación completa sobre cómo
programar el trabajo en subprocesos en segundo plano y comunicarse con el subproceso de IU, consulta
Descripción general del trabajo en segundo plano.
Métodos seguros para los subprocesos
En algunas situaciones, los métodos que implementas se llaman desde más de un subproceso y, por lo tanto, deben escribirse para que sean seguros para los subprocesos.
Esto es principalmente cierto para 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 el
IBinder, el método se ejecuta en el subproceso del llamador.
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 el IBinder.
No se ejecuta en el subproceso de IU del proceso.
Por ejemplo, mientras que se llama al método
onBind() de un servicio desde el subproceso de IU del proceso del
servicio, los métodos implementados en el objeto que onBind() devuelve, como una
subclase que implementa métodos de llamada de procedimiento remoto (RPC), se llaman desde subprocesos
en el grupo. Debido a que un servicio puede tener más de un cliente, más de un subproceso del grupo puede activar el mismo método IBinder al mismo tiempo, por lo que los métodos IBinder deben implementarse para que sean seguros para los subprocesos.
Del mismo modo, un proveedor de contenido puede recibir solicitudes de datos que se originan en otros procesos.
Las clases ContentResolver y ContentProvider
ocultan los detalles de cómo se administra la comunicación entre procesos (IPC),
pero 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
para el proceso. Debido a que estos métodos se pueden llamar desde cualquier cantidad de subprocesos al mismo tiempo, también deben implementarse para que sean seguros para los subprocesos.
Comunicación entre procesos
Android ofrece un mecanismo para IPC con RPCs, en el que una actividad u otro componente de la aplicación llama a un método, pero se ejecuta de forma remota en otro proceso, y cualquier resultado se devuelve al llamador. Esto implica descomponer una llamada de método y sus datos a un nivel que el sistema operativo pueda comprender, transmitirla desde el proceso local y el espacio de direcciones al proceso remoto y el espacio de direcciones, y luego volver a ensamblar y representar la llamada allí.
Luego, los valores de retorno se transmiten en la dirección opuesta. Android proporciona todo el código para realizar estas transacciones de IPC, por lo que puedes enfocarte en definir e implementar la interfaz de programación de RPC.
Para realizar IPC, tu aplicación debe vincularse a un servicio con bindService(). Para obtener más información, consulta la descripción general de Services.