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.
- ¿Cómo mostrar una actividad dentro de otra en Android?
- Cómo averiguar si la actividad es la última actividad en la pila
- Error: Theme.Translucent & FLAG_ACTIVITY_REORDER_TO_FRONT
- StartActivity () de BroadcastReceiver
- ¿Hay alguna razón para no llamar a setIntent cuando se sobrepasa onNewIntent?
Cuando se cancela una actividad y más tarde se crea una nueva instancia, puedo pensar en dos opciones:
-
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.
-
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 posiblementeonPause
) enonPause
.
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.
- Android: borra la pila trasera
- Diferentes controladores para los objetivos de teléfono y tableta
- Deslizar entre actividades como SnapChat?
- Ocultar el icono de la aplicación y ejecutarlo
- ¿Alguien por favor explique RESULT_FIRST_USER
- Cierre actividad de otra actividad a través de una intención
- Cierre una actividad pasando de derecha a izquierda con una animación suave para ir a la segunda actividad
- Actividad abierta dos veces
¿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).