Buen diseño: hilos de trabajo y restablecimientos de actividad

Es bien sabido que Android coloca nuestros componentes de aplicación (Actividades, Servicios) bajo la amenaza de ser asesinado en cualquier momento. Esto hace que las cosas realmente complicadas si desea proporcionar una solución robusta, no fugas, mientras que al mismo tiempo mantener el código limpio y abordar la separación de las preocupaciones.

Problema :
Una actividad comienza una tarea que requiere mucho tiempo (en forma de Runnable , AsyncTask o cualquier otro mecanismo). Debe aparecer un cuadro de diálogo de progreso. La tarea abrirá varias conexiones, pero debería tener un medio para actualizar una barra de progreso, y tal vez necesite mostrar un diálogo de opción a mitad de camino hasta su finalización. También debe ser capaz de cerrar el cuadro de diálogo y mostrar un brindis cuando se produce un error, o cuando se completa.

Cuando se cancela una actividad y más tarde se crea una nueva instancia, puedo pensar en dos opciones:

  1. Intente eliminar también la tarea en ejecución y generará otra instancia de tarea cuando se cree una nueva instancia de actividad sustituta. Esto no es una opción para una tarea de ejecución larga, ya que es inaceptable desde un punto de vista de usuario para iniciar la tarea y ver el indicador de barra de progreso va de 80% de nuevo a 0% sin razón aparente. Sin embargo, este es el enfoque seguido en el ejemplo de Shelves por Romain Guy . Aparentemente este es el enfoque recomendado en la Guía oficial para desarrolladores.

  2. Mantenga la tarea en ejecución: para que pudiéramos tener una actividad de suscripción a (y posiblemente iniciar) la tarea de actualizaciones en onResume y cancelar la suscripción (y posiblemente onPause ) en onPause .

Seguir la segunda opción significa que debemos evitar la tarea de ser recogida en caso de que la actividad sea destruida temporalmente. Por ejemplo, podríamos declararlo dentro de una clase de aplicación personalizada, o incluso ofrecerla como un servicio al resto de la aplicación (usando Singleton? Un servicio de Android?) No soy fan de esto, sin embargo, ya que la tarea es Utilizado sólo por la actividad, por lo que no tiene sentido para ponerla a disposición de otras clases. Servicios de Android, por otro lado también tienen que lidiar con el ciclo de vida.

La opción # 2 también implica asegurar que la actividad antigua no se filtren. No debemos intentar actualizar la GUI si no hay actividad presente. Todo el pensamiento debe ser thread-safe. Por último, la tarea no debe permanecer en la memoria después de que estamos seguros de que ya no es necesario. Pero al mismo tiempo, si la actividad inicial se destruye, la tarea se mantiene en funcionamiento y se inicia una nueva actividad de sustitución, la tarea debe estar alrededor para actualizar la GUI de inmediato y mostrar el estado actual de la tarea ).

El hecho de que la tarea se ejecute cuando no se muestra ninguna actividad es otro problema a tratar, ya que podríamos necesitar la interacción del usuario (diálogo de opción) de forma sincrónica en algún momento. Esto podría resolverse si la actividad tuviera la capacidad de pausar inmediatamente la tarea al llegar a onPause , pero la tarea podría tomar algún tiempo para detenerse o incluso ser incapaz de hacerlo.

Sé que se han hecho preguntas similares antes, pero no estoy preguntando específicamente sobre cómo resolver el problema, sino sobre qué diseño se recomendaría para lograr un acoplamiento suelto y aislar la actividad de la tarea tanto como sea posible.

En resumen :
– Este es un problema recurrente en el desarrollo de Android y alguien probablemente ha llegado con un diseño inteligente antes. ¿Hay un patrón de diseño, o una biblioteca bien probada que simplifica esto?
– Si tuviera que implementar # 2, ¿qué clase elegiría para la tarea (Runnable, Service, etc) y cómo se comunicaría con la actividad?

PS Por favor, abstenerse de publicar soluciones basadas en la "orientación | keyboardHidden" hack XD. Aparte de esto, cualquier ayuda sería apreciada.


ACTUALIZAR:
Mi primer intento tiene un poco desordenado. La aplicación es una utilidad de impresión bluetooth, que implica Detectar el estado de BT (y pedir al usuario que lo habilite si estaba desactivado), recuperar dispositivos emparejados (y pedirle al usuario que elija uno si hubiera más de uno), luego enviar los datos Y finalmente informar al usuario sobre el resultado. Como esta tarea era el 80% de código IO, el 20% de operaciones relacionadas con GUI, tuve que agrupar el código GUI al principio y al final de la tarea, y luego moverlo de la tarea a la actividad. Tengo un IntentService que hace el trabajo pesado, luego vuelve a la actividad a través de BroadcastReceiver . Esto resuelve la mayoría de los problemas pero hay algunos problemas con tal diseño. Primero, hay todas esas cadenas constantes usadas como llaves para poner y recuperar campos de los Intents de entrada y salida, que están introduciendo el acoplamiento semántico. Habría preferido la seguridad de tipo en la comunicación de servicio de actividad <->. Y todavía tengo que resolver cómo pasar complejos objetos personalizados para el servicio en el intento, por ahora estoy atascado con parcelables y parámetros primitivos. Por último, tanto la actividad como el servicio utilizan la misma API (bluetooth), hubiera preferido tener el código relacionado con BT en una sola clase. Creo que voy a tirar este diseño, e intentar de nuevo con una cola de impresión personalizada basada en hilos, a pesar de que hará más difícil tratar con las actividades muertas.

¿Por qué no hacer que su actividad inicie un servicio para albergar la larga tarea? Un servicio es su mejor apuesta para mantener las cosas funcionando a largo plazo. Puede proporcionar sugerencias al sistema operativo para dejarlo en funcionamiento. Vea startForeground() y START_STICKY .

Puede comunicarse de nuevo con la actividad transmitiendo desde el servicio. Haga que su actividad registre programáticamente un receptor de difusión para escuchar esos intentos. Si la actividad está en pausa, anule el registro de su receptor, de esa manera usted no responderá cuando cuando no esté en primer plano.

Si el sistema operativo destruye su servicio, entonces está matando el proceso, por lo que su tarea está condenada de todos modos. Lo mejor que puedes hacer es reiniciarlo. De todos modos, esto no sucederá excepto en las condiciones más extremas si consideras lo anterior.

EDIT: resumiendo algunos pensamientos capturados en los comentarios … mantener un proceso de trabajo de largo funcionamiento y reiniciarlo automáticamente es casi siempre la cosa incorrecta a hacer. Los dispositivos móviles no tienen la fuente de alimentación para hacer esto factible. El autor declaró que tiene una condición especial que niega esta preocupación … que sólo sería cierto si el dispositivo está conectado a una fuente de alimentación casi siempre.

El modelo android es proporcionar un conjunto robusto de intentos (notificaciones) a los que su aplicación puede escuchar. Estos intentos despiertan su dispositivo / aplicación cuando es hora de hacer algo. Su aplicación debe manejar la notificación y, a continuación, permitir que el dispositivo vuelva a dormir inmediatamente.

Si no hay ninguna intención de stock que se correlaciona con su evento, puede utilizar Google Cloud Messaging para activar dispositivos desde un servidor. En otras palabras, permita que los procesos de larga duración se ejecuten en el servidor y despierte el dispositivo cuando sea necesario.

Si usted puede hacer eso, una alternativa sería utilizar AlarmManager para despertar periódicamente y comprobar algún estado. Incluso si lo hizo con frecuencia (por ejemplo, cada 5 minutos, que también es un no-no), sería mucho mejor que mantener el dispositivo siempre despierto como se sugiere. También, hacer uso de intervalos de despertar inexactos (ver la documentación vinculada arriba).

  • Intención implícita de no ser llamado
  • Nueva actividad en Android "entrar desde el lado"
  • Inicio de la actividad mediante la acción personalizada
  • Android: ¿Cómo reanudar la aplicación y la actividad de BroadcastReceiver?
  • Android onActivityResult se llama temprano
  • Android: cómo cambiar el diseño en el botón de clic?
  • ¿Cómo configurar mi actividad como actividad principal en android?
  • Eclipse: creando nueva actividad
  • Cómo abrir el modo de selección de entrada en el evento del teclado
  • Abrir una aplicación de Android dentro de otra
  • Cerrar sesión desde la aplicación Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.