Procesos y subprocesos

Quickview

  • Cada aplicación se ejecuta en su propio proceso y todos los componentes de la aplicación se ejecutan en eseproceso, por defecto
  • Cualquier operación lenta y bloqueante en una actividad debe hacerse en un nuevo hilo, para evitar la ralentización de la interfaz de usuario

En este documento

  1. Procesos
    1. Ciclo de vida de los procesos
  2. Hilos
    1. Hilos de trabajo
    2. Métodos seguros para los hilos
    3. .safe methods
  3. Interprocess Communication

Cuando un componente de la aplicación se inicia y la aplicación no tiene ningún otro componentrunning, el sistema Android inicia un nuevo proceso Linux para la aplicación con un único hilo de ejecución. Por defecto, todos los componentes de la misma aplicación se ejecutan en el mismo proceso e hilo (llamado hilo «principal»). Si un componente de la aplicación se inicia y ya existe un proceso para esa aplicación (porque existe otro componente de la aplicación), entonces el componente se inicia dentro de ese proceso y utiliza el mismo hilo de ejecución. Sin embargo, puede disponer que diferentes componentes de su aplicación se ejecuten en procesos separados, y puede crear hilos de ejecución adicionales para cualquier proceso.

Este documento trata sobre cómo funcionan los procesos y los hilos de ejecución en una aplicación Android.

Procesos

Por defecto, todos los componentes de la misma aplicación se ejecutan en el mismo proceso y la mayoría de las aplicaciones no deberían cambiar esto. Sin embargo, si necesita controlar a qué proceso pertenece un determinado componente, puede hacerlo en el archivo de manifiesto.

La entrada del 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 debe ejecutarse ese componente. Puede 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 puede establecer 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, para establecer un valor predeterminado que se aplica a todos los componentes.

Android podría decidir cerrar un proceso en algún momento, cuando la memoria es baja y es requerida por otros procesos que están sirviendo más inmediatamente al usuario. Los componentes de la aplicación que se ejecutan en el proceso que se mata son consecuentemente destruidos. Un proceso se inicia de nuevo para esos componentes cuando hay de nuevo trabajo que hacer.

Al decidir qué procesos matar, el sistema Android pesa su importancia relativa para el usuario. Por ejemplo, cierra más fácilmente un proceso que alberga actividades que ya no son visibles en la pantalla, en comparación con un proceso que alberga actividades visibles. Por lo tanto, la decisión de terminar un proceso depende del estado de los componentes que se ejecutan en ese proceso. Las reglas utilizadas para decidir qué procesos terminar se discuten a continuación.

Ciclo de vida de los procesos

El sistema Android intenta mantener un proceso de aplicación durante el mayor tiempo posible, pero eventualmente necesita eliminar los procesos antiguos para recuperar memoria para procesos nuevos o más importantes. Para determinar qué procesos mantener y cuáles eliminar, el sistema coloca cada proceso en una «jerarquía de importancia» basada en los componentes que se ejecutan en el proceso y el estado de esos componentes. Los procesos con la menor importancia se eliminan primero, luego los que tienen la siguiente menor importancia, y así sucesivamente, según sea necesario para recuperar los recursos del sistema.

Hay cinco niveles en la jerarquía de importancia. La siguiente lista presenta los diferentes tipos de procesos en orden de importancia (el primer proceso es el más importante y se elimina el último):

  1. Proceso en primer plano

    Un proceso que es necesario para lo que el usuario está haciendo en ese momento. Se considera que un proceso está en primer plano si se cumple alguna de las siguientes condiciones:

    • Alberga un Activity con el que el usuario está interactuando (se ha llamado al método onResume() del Activity).
    • Alberga un Service que está vinculado a la actividad con la que el usuario está interactuando.
    • Alberga un Service que se está ejecutando «en primer plano»-el servicio ha llamado a startForeground().
    • Alberga a un Service que está ejecutando una de sus devoluciones del ciclo de vida (onCreate(), onStart() o onDestroy()).
    • Alberga a un BroadcastReceiver que está ejecutando su método onReceive().

    Generalmente, sólo existen unos pocos procesos en primer plano en un momento dado. Sólo se eliminan como último recurso, si la memoria es tan baja que no pueden seguir ejecutándose todos. Por lo general, en ese punto, el dispositivo ha alcanzado un estado de paginación de memoria, por lo que matar algunos procesos en primer plano es necesario para mantener la interfaz de usuario sensible.

  2. Proceso visible

    Un proceso que no tiene ningún componente en primer plano, pero todavía puede afectar lo que el usuario ve en la pantalla. Se considera que un proceso es visible si se cumple alguna de las siguientes condiciones:

    • Alberga un Activity que no está en primer plano, pero que sigue siendo visible para el usuario (se ha llamado a su método onPause()). Esto podría ocurrir, por ejemplo, si la actividad en primer plano inició un diálogo, lo que permite que la actividad anterior se vea detrás de ella.
    • Alberga un Service que está vinculado a una actividad visible (o en primer plano).

    Un proceso visible se considera extremadamente importante y no se matará a menos que sea necesario para mantener todos los procesos en primer plano.

  3. Proceso de servicio

    Un proceso que está ejecutando un servicio que se ha iniciado con el método startService() y no entra en ninguna de las dos categorías superiores. Aunque los procesos de servicio no están directamente ligados a nada que el usuario vea, por lo general están haciendo cosas que le interesan al usuario (como reproducir música en segundo plano o descargar datos en la red), por lo que el sistema los mantiene en ejecución a menos que no haya suficiente memoria para retenerlos junto con todos los procesos en primer plano y visibles.

  4. Proceso en segundo plano

    Un proceso que mantiene una actividad que no es actualmente visible para el usuario (el métodoonStop() de la actividad ha sido llamado). Estos procesos no tienen ningún impacto directo en la experiencia del usuario, y el sistema puede matarlos en cualquier momento para recuperar memoria para un proceso anterior, visible o de servicio. Normalmente hay muchos procesos en segundo plano que se ejecutan, por lo que se mantienen en una lista LRU (least recently used) para asegurar que el proceso con la actividad que fue vista más recientemente por el usuario sea el último en ser eliminado. Si una actividad implementa sus métodos de ciclo de vida correctamente, y guarda su estado actual, matar su proceso no tendrá un efecto visible en la experiencia del usuario, porque cuando éste vuelve a navegar a la actividad, ésta restaura todo su estado visible. Ver el documento de Actividades para información sobre guardar y restaurar el estado.

  5. Proceso vacío

    Un proceso que no contiene ningún componente activo de la aplicación. La única razón para mantener vivo este tipo de proceso es por motivos de caché, para mejorar el tiempo de inicio la próxima vez que un componente necesite ejecutarse en él. El sistema a menudo mata a estos procesos con el fin de equilibrar los recursos generales del sistema entre las cachés de procesos y las cachés del núcleo subyacente.

Android clasifica un proceso en el nivel más alto que puede, basado en la importancia de los componentes actualmente activos en el proceso. Por ejemplo, si un proceso alberga un servicio y una actividad visible, el proceso se clasifica como un proceso visible, no como un proceso de servicio.

Además, la clasificación de un proceso puede aumentar porque otros procesos dependen de él: un proceso que está sirviendo a otro proceso nunca puede tener una clasificación inferior a la del proceso al que está sirviendo. Por ejemplo, si un proveedor de contenido en el proceso A está sirviendo a un cliente en el proceso B, o si un servicio en el proceso A está vinculado a un componente en el proceso B, el proceso A siempre se considera al menos tan importante como el proceso B.

Debido a que un proceso que ejecuta un servicio se clasifica más alto que un proceso con actividades de fondo, una actividad que inicia una operación de larga duración podría hacer bien en iniciar un servicio para esa operación, en lugar de simplemente crear un hilo de trabajo, sobre todo si la operación probablemente durará más que la actividad.Por ejemplo, una actividad que está subiendo una foto a un sitio web debería iniciar un servicio para realizar la subida, de modo que ésta pueda continuar en segundo plano incluso si el usuario abandona la actividad. Esta es la misma razón por la que los receptores de difusión deberían emplear servicios en lugar de simplemente poner las operaciones que consumen tiempo en un hilo.

Hilos

Cuando se lanza una aplicación, el sistema crea un hilo de ejecución para la aplicación, llamado «main». Este hilo es muy importante porque es el encargado de enviar los eventos a los widgets de la interfaz de usuario correspondientes, incluyendo los eventos de dibujo. También es el hilo en el que su aplicación interactúa con los componentes del kit de herramientas de interfaz de usuario de Android (componentes de los paquetes android.widget y android.view). Como tal, el hilo principal también se llama a veces el hilo de la interfaz de usuario.

El sistema no crea un hilo separado para cada instancia de un componente. Todos los componentes que se ejecutan en el mismo proceso se instancian en el hilo de la interfaz de usuario, y las llamadas del sistema a cada componente se envían desde ese hilo. En consecuencia, los métodos que responden a las devoluciones del sistema (como onKeyDown() para informar de las acciones del usuario o un método de devolución de llamada del ciclo de vida) siempre se ejecutan en el hilo de la interfaz de usuario del proceso.

Por ejemplo, cuando el usuario toca un botón en la pantalla, el hilo de la interfaz de usuario de su aplicación envía el evento de toque al widget, que a su vez establece su estado presionado y publica una solicitud de invalidación a la cola de eventos. El hilo de la interfaz de usuario retira la solicitud y notifica al widget que debe volver a dibujarse a sí mismo.

Cuando su aplicación realiza un trabajo intensivo en respuesta a la interacción del usuario, este modelo de un solo hilo puede producir un rendimiento pobre a menos que implemente su aplicación correctamente. En concreto, si todo sucede en el hilo de la interfaz de usuario, la realización de operaciones largas, como el acceso a la red o las consultas a la base de datos, bloqueará toda la interfaz de usuario. Cuando el hilo está bloqueado, no se pueden enviar eventos, incluidos los eventos de dibujo. Desde la perspectiva del usuario, la aplicación parece colgarse. Peor aún, si el hilo de la interfaz de usuario se bloquea durante más de unos pocos segundos (unos 5 segundos actualmente) el usuario se encuentra con el infame cuadro de diálogo «la aplicación no responde» (ANR). El usuario puede entonces decidir salir de su aplicación y desinstalarla si no está contento.

Además, el kit de herramientas de interfaz de usuario de Andoid no es seguro para los hilos. Por lo tanto, no debes manipular tu UI desde un hilo de trabajo – debes hacer toda la manipulación de tu interfaz de usuario desde el hilo UI. Por lo tanto, hay simplemente dos reglas para el modelo de hilo único de Android:

  1. No bloquear el hilo de la interfaz de usuario
  2. No acceder al kit de herramientas de la interfaz de usuario de Android desde fuera del hilo de la interfaz de usuario

Hilos de trabajo

Debido al modelo de hilo único descrito anteriormente, es vital para la capacidad de respuesta de la interfaz de usuario de tu aplicación que no bloquees el hilo de la interfaz de usuario. Si tienes que realizar operaciones que no son instantáneas, debes asegurarte de hacerlas en hilos separados (hilos «de fondo» o «worker»).

Por ejemplo, a continuación hay un código para un click listener que descarga una imagen desde un hilo separado y la muestra en un ImageView:

public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start();}

Al principio, esto parece funcionar bien, porque crea un nuevo hilo para manejar la operación de red. Sin embargo, viola la segunda regla del modelo de un solo hilo: no acceder al kit de herramientas de la interfaz de usuario de Android desde fuera del hilo de la interfaz de usuario: este ejemplo modifica el ImageView desde el hilo del trabajador en lugar del hilo de la interfaz de usuario. Esto puede dar lugar a un comportamiento indefinido e inesperado, que puede ser difícil y lento de rastrear.

Para solucionar este problema, Android ofrece varias formas de acceder al hilo UI desde otroshilos. Aquí hay una lista de métodos que pueden ayudar:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

Por ejemplo, puede arreglar el código anterior utilizando el método View.post(Runnable):

public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start();}

Ahora esta implementación es segura para los hilos: la operación de red se hace desde un hilo separado mientras que el ImageView se manipula desde el hilo de la UI.

Sin embargo, a medida que la complejidad de la operación crece, este tipo de código puede complicarse y ser difícil de mantener. Para manejar interacciones más complejas con un hilo de trabajo, usted podría considerar el uso de un Handler en su hilo de trabajo, para procesar los mensajes entregados desde el hilo UI. Tal vez la mejor solución, sin embargo, es extender la clase AsyncTask, que simplifica la ejecución de las tareas del hilo de trabajo que necesitan para interactuar con la interfaz de usuario.

El uso de AsyncTask

AsyncTask le permite realizar el trabajo asíncrono en su interfaz de usuario. Realiza las operaciones de bloqueo en un hilo de trabajo y luego publica los resultados en el hilo de la interfaz de usuario, sin necesidad de manejar hilos y/o manejadores usted mismo.

Para usarlo, debe subclasificar AsyncTask e implementar el método de devolución de llamada doInBackground(), que se ejecuta en un grupo de hilos de fondo. Para actualizar tu UI, debes implementar onPostExecute(), que entrega el resultado de doInBackground() y se ejecuta en el hilo de UI, para que puedas actualizar tu UI de forma segura. Entonces puedes ejecutar la tarea llamando a execute()desde el hilo de la UI.

Por ejemplo, puedes implementar el ejemplo anterior usando AsyncTask de esta manera:

public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png");}private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); }}

Ahora la UI es segura y el código es más simple, porque separa el trabajo en la parte que debe hacerse en un hilo de trabajo y la parte que debe hacerse en el hilo de la UI.

Deberías leer la referencia AsyncTaskpara entender completamente cómo usar esta clase, pero aquí tienes un resumen rápido de cómo funciona:

  • Puedes especificar el tipo de los parámetros, los valores de progreso y el valor final de la tarea, utilizando genéricos
  • El método doInBackground() se ejecuta automáticamente en un hilo de trabajo
  • onPreExecute(), onPostExecute(), y onProgressUpdate() se invocan en el hilo de la UI
  • El valor devuelto por doInBackground() se envía aonPostExecute()
  • Puede llamar a publishProgress() en cualquier momento en doInBackground() para ejecutar onProgressUpdate() en el hilo de la UI
  • Puede cancelar la tarea en cualquier momento, desde cualquier hilo

Precaución: Otro problema que puedes encontrar al usar un workerthread son los reinicios inesperados en tu actividad debido a un cambio de configuración en tiempo de ejecución(como cuando el usuario cambia la orientación de la pantalla), que puede destruir tu worker thread. Para ver cómo puede persistir su tarea durante uno de estos reinicios y cómo cancelar correctamente la tarea cuando se destruye la actividad, consulte el código fuente de la aplicación de ejemplo Shelves.

Métodos a prueba de hilos

En algunas situaciones, los métodos que implementa podrían ser llamados desde más de un hilo, y por lo tanto deben ser escritos para ser a prueba de hilos.

Esto es principalmente cierto para los métodos que pueden ser llamados 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 hilo de la persona que llama. Sin embargo, cuando la llamada se origina en otro proceso, el método se ejecuta en un hilo elegido de un grupo de hilos que el sistema mantiene en el mismo proceso que el IBinder (no se ejecuta en el hilo de la interfaz de usuario del proceso). Por ejemplo, mientras que el método onBind() de un servicio se llamaría desde el hilo de la interfaz de usuario del proceso del servicio, los métodos implementados en el objeto que onBind() devuelve (por ejemplo, una subclase que implementa métodos RPC) se llamarían desde los hilos del pool. Debido a que un servicio puede tener más de un cliente, más de un hilo de la piscina puede engageethe mismo método IBinder al mismo tiempo. Los métodos IBinder deben, por lo tanto, ser implementados para ser a prueba de hilos.

Del mismo modo, un proveedor de contenido puede recibir solicitudes de datos que se originan en otros procesos.Aunque las clases ContentResolver y ContentProvider ocultan los detalles de cómo se gestiona la comunicación entre procesos, los métodos de ContentProvider que responden a esas peticiones -los métodos query(), insert(), delete(), update() y getType()– son llamados desde un grupo de hilos en el proceso del proveedor de contenidos, no desde el UIthread del proceso. Debido a que estos métodos pueden ser llamados desde cualquier número de hilos al mismo tiempo, también deben ser implementados para ser a prueba de hilos.

Comunicación entre procesos

Android ofrece un mecanismo para la comunicación entre procesos (IPC) utilizando llamadas a procedimientos remotos (RPC), en el que un método es llamado por una actividad u otro componente de la aplicación, pero ejecutado de forma remota (en otro proceso), con cualquier resultado devuelto a la persona que llama. Esto implica descomponer una llamada a un método y sus datos a un nivel que el sistema operativo pueda entender, transmitirlo desde el proceso local y el espacio de direcciones al proceso remoto y al espacio de direcciones, y luego volver a ensamblar y representar la llamada allí. Los valores de retorno se transmiten en la dirección opuesta. Android proporciona todo el código para realizar estas transacciones IPC, por lo que puedes centrarte en definir e implementar la interfaz de programación RPC.

Para realizar IPC, su aplicación debe vincularse a un servicio, utilizando bindService(). Para más información, consulte la guía del desarrollador de Servicios.

Deja un comentario