Mal comportamiento de Backstack de la actividad cuando la actividad destruida
Tengo dos actividades; Digamos A y B. En la activity A
hay un receptor de radiodifusión registrado que escucha un evento particular que terminará la actividad A. Estoy registrando el receptor de broadcast en onCreate()
y destruyéndolo en onDestroy()
de la activity A
Para simplificar, hay un button
en la activity B
llamado "Destroy Activity A". Cuando un usuario hace clic en el button
, la activity A
debe ser destruida.
- Android: detecta cuando la aplicación está instalada
- Enviando la intención de BroadcastReceiver desde adb
- Cómo abortar BroadcastReceiver en android
- Llamar a un método de actividad de una clase BroadcastReceiver
- ¿No es necesario android.permission.RECEIVE_BOOT_COMPLETED?
Normalmente, todo esto funciona sin problemas, pero el problema se produce en los siguientes escenarios:
1) Supongamos que estoy en la activity B
e presiono la tecla Inicio para mover la aplicación al fondo, entonces si uso otras aplicaciones de recursos pesados, el sistema Android matará a mi aplicación para liberar memoria. Entonces, si abro mi aplicación de tareas recientes, se reanudará la activity B
, y se onCreate()
método onCreate()
, onResume()
etc. Ahora presiono el button
para destruir la activity A
, pero la actividad A ya ha sido destruida, por lo que los onCreate()
, onResume()
etc de la activity A
no serán llamados hasta y a menos que vaya a la activity A
presionando el back button
. Así, el broadcast receiver
no está registrado para escuchar el evento.
2) El mismo problema surgirá cuando el usuario haya seleccionado "No mantener actividades" de las opciones de desarrollador en la configuración del dispositivo.
He estado buscando para resolver este problema durante mucho tiempo, pero no soy capaz de encontrar una respuesta adecuada. ¿Cuál es la mejor manera de manejar este escenario? ¿Se trata de un error de Android? Debe haber alguna solución para este problema.
Por favor, ayúdame.
- ¿Cómo puedo hacer algo antes de desinstalar mi aplicación para Android?
- Ciclo de vida de BroadcastReceiver
- Cómo anular el registro de escuchas y detener el servicio desde dentro de broadcastreceiver
- Inesperado resultados diferentes de la misma entrada de cadena
- Cómo bloquear una llamada de número móvil y recepción de mensajes en el desarrollo de aplicaciones Android?
- BroadcastReceiver cómo iniciar una nueva intención
- No se puede instanciar el receptor java.lang.ClassNotFoundException
- Android: Registrar el receptor en la biblioteca
Esto no se puede arreglar mientras se mantiene la lógica de difusión actual.
Matar a las actividades desde el backstack, imo, no es un enfoque correcto. Debe considerar seriamente cambiar la lógica de su navegación.
Pero si su proyecto es grande y el tiempo es un problema, y la refactorización está fuera de cuestión, el enfoque de AJ funciona, pero usted mencionó que tiene muchas actividades que necesitan ser asesinadas, su solución se vuelve muy complicada de implementar.
Lo que sugiero es el siguiente. Esto no podría ser la mejor idea, pero no puedo pensar en otra. Así que tal vez eso podría ayudar.
Usted debe tener lo siguiente:
- Una Actividad Base para todas sus actividades.
- Un objeto
ArrayList<String> activitiesToKill
en el nivel de aplicación. (Si no extendió laApplication
puede tenerla como variable estática
Primero tenemos que asegurarnos de que las activitiesToKill
no se pierden cuando el sistema operativo mata la aplicación en poca memoria. En la BaseActivity
guardamos la lista durante onSaveInstanceState
y la restauramos en onRestoreInstanceState
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable("activitiesToKill", activitiesToKill); } private void onRestoreInstanceState(Bundle state) { if (state != null) { activitiesToKill = (ArrayList<String>) state.getSerializable("activitiesToKill"); super.onRestoreInstanceState(state); }
}
La idea aquí es guardar qué actividades deben ser eliminadas en la lista, usando su nombre.
La lógica es la siguiente:
Digamos que usted tiene Actividades A, B, C, D y E
Desde la actividad E, presiona el botón y quieres matar B y D
Cuando presiona el botón en E, agrega los nombres de B y D al objeto activitiesToKill
.
activitiesToKill.add(B.class.getSimpleName() activitiesToKill.add(D.class.getSimpleName()
En el método onCreate
de BaseActivity, tenemos que comprobar si el
if(savedInstanceState != null) { //The activity is being restored. We check if the it is in the lest to Kill and we finish it if(activitiesToKill.contains(this.getClass().getSimpleName())) { activitiesToKill.remove(this.getClass().getSimpleName()) finish(); } }
Asegúrese de eliminar el nombre de la actividad si se cancela a través de la transmisión.
Así que básicamente esto es lo que sucede en cada escenario.
Si la aplicación se ejecuta normalmente y hace clic en el botón, se emite la transmisión y B y D se matan. Asegúrese de quitar B y D de las activitiesToKill
Si la aplicación se ha eliminado y restaurado, presiona el botón, la transmisión no tendrá ningún efecto, pero ha agregado B y D al objeto activitiesToKill
. Por lo tanto, cuando hace clic de nuevo, la actividad se crea y el savedInstanceState no es nulo, la actividad está terminada.
Este enfoque considera que la actividad E sabe qué actividades tiene que matar.
En caso de que NO sepa qué actividades matar desde E, hay que modificar ligeramente esta lógica:
En lugar de utilizar un ArrayList, utilice un HashMap<String, bool>
Cuando se crea la Actividad B, se registrará en el hashmap:
activitiesToKill.put(this.class.getSimpleName(), false)
Luego de la Actividad E, todo lo que tienes que hacer es establecer todas las entradas a true
Luego en la creación on de la actividad de base debes comprobar si esta actividad está registrada en las actividadesToKill (el hashmap contiene la clave) Y el booleano es true
lo matas (no te olvides devolverlo a false o quitar el llave)
Esto asegura que cada actividad se registre en el HashMap y la Actividad E no tenga conocimiento de todas las actividades a matar. Y no se olvide de quitarlos en caso de que la transmisión los mata.
Este enfoque también asegura que la actividad no se mata cuando se abre normalmente de una intención porque en ese caso onSaveInstanceState sería nulo en el onCreate, por lo que no sucederá nada.
Pueden realizarse comprobaciones más avanzadas en caso de que haya grupos de actividades que necesiten terminarse en diferentes condiciones (no sólo un clic de botón) para que pueda tener un HashMap de un HashMap para dividirlas en categorías.
También tenga en cuenta que puede utilizar getName en lugar de getSimpleName si tiene varias actividades con el mismo nombre pero diferentes paquetes.
Espero que mi explicación sea lo suficientemente clara como lo escribí desde mi cabeza, hágamelo saber si alguna área no está clara.
La mejor de las suertes
Si su
Activity A
ha sido destruida por el sistema operativo Android, entonces no hay forma de realizar un seguimiento.
Alguna gente ha sugerido seguir esa Activity A
listing el acontecimiento en método de onDestroy
PERO si su Activity
mató por OS sistema entonces nota aquí que no llamará esos método.
No sé si es posible manejar esto de una manera "apropiada".
Lo que me viene a la mente, es marcar la actividad A de alguna manera. No puede utilizar startActivityForResult()
porque recibirá el resultado antes de que onResume()
se llame, es decir, UI ya estaba inflada.
Si utiliza un Otto, puede intentarlo con un evento pegajoso. De lo contrario, necesitará un singleton para manejar el indicador o guardarlo en las preferencias compartidas.
Tendrás que comprobar esa bandera en tu método onCreate()
antes de llamar a setContentView()
, si el flag es true acaba de terminar la actividad.
Con la información que ha dado, ¿qué hay acerca de registrar la emisión en onCreate de la Actividad B después de comprobar si ya está registrado o no. Si onDestroy de la Actividad A se ha llamado en cualquiera de los escenarios que ha mencionado, entonces el registro de la difusión habría sido llamado. Por lo tanto, en ese caso, puede registrar su difusión en onCreate de la actividad B, para que pueda escucharla, incluso si sólo tiene la actividad B en su backstack.
¿Ha pensado en usar la emisión Sticky Broadcast ? También puede registrar su receptor en el nivel de aplicación (en manifiesto) y escuchar este evento independientemente del estado de la Activity A
Pero, como ya dijo Youssef , matar a las actividades desde el backstack no es un enfoque correcto. Debe considerar seriamente cambiar la lógica de su navegación.
Una de las reglas principales con Activities
es que no puedes confiar en que ninguna actividad esté viva excepto la actividad de primer plano . Lo que estás tratando de hacer con las transmisiones no tiene nada que ver con la pila trasera – la pila de atrás no garantiza que todas las actividades estén vivas en todo momento, pero se asegurará de que se vuelvan a crear cuando llegue el momento de pasar a primer plano.
En tu ejemplo (si entiendo lo que quieres hacer) necesitas navegar por algo debajo de A
, por ejemplo, Actividad Z
, y la pila se ve así: ZA-[B]
. Hay un curso normal de los acontecimientos donde usted golpea back
y le lleva a A
, después después de otro golpe – a Z
pero en un cierto caso (por ejemplo, presionando un botón) usted quiere volver a Z
pasar por A – esto es un clásico Case para usar FLAG_ACTIVITY_CLEAR_TOP y ejecutar Z
explícitamente :
Intent intent = new Intent(this, ActivityZ.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent);
Esto terminará ambos B
y A
, y entregará la intención a Z
Probablemente también necesitará el flag FLAG_ACTIVITY_SINGLE_TOP
, preste mucha atención a la descripción de FLAG_ACTIVITY_CLEAR_TOP , hay algunos trucos que debe considerar.
Muchas soluciones me vinieron a la mente, pero como usted no ha proporcionado mucha información acerca de su aplicación, creo que esto debería funcionar en general.
En lugar de disparar una emisión para matar la actividad A, sólo ejecute el código siguiente cuando se presiona el botón "Activar actividad A" en la Actividad B.
Intent intent = new Intent(getApplicationContext(), ActivityA.class); intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); intent.putExtra("EXIT", true); startActivity(intent);
Agregue el código siguiente en la actividad A
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); if (intent.getBooleanExtra("EXIT", false)) { finish(); } } protected void onCreate(Bundle savedInstanceState) { //Ideally, there should not be anything before this super.onCreate(savedInstanceState); if(getIntent().getBooleanExtra("EXIT", false)){ finish(); return; }
En el conjunto de manifiesto "singleTop" modo de inicio para la actividad A.
<activity android:name=".ActivityA" ... android:launchMode="singleTop" />
Esto tendrá las siguientes consecuencias:
- Si la actividad A ya se está ejecutando, se llevará al frente de la pila de actividades y se terminará, lo que eliminará de la pila.
- Si la Actividad A ha sido destruida pero aún está presente en la pila de actividades (que se iniciará al pulsar el botón Atrás), se iniciará, se llevará a la parte delantera y se finalizará, eliminándola de la pila de actividades.
- Si la actividad A ya ha sido destruida y no está presente en la pila de actividades, y todavía presiona el botón "Quitar actividad A", se iniciará, se llevará a la parte delantera y se terminará.
Generalmente, usted no debe ver ninguna parpadeo.
Basándose en esta idea, puede crear una solución de mejor rendimiento para su aplicación en particular. Por ejemplo, puede utilizar FLAG_ACTIVITY_CLEAR_TOP y finalizar la Actividad A en onBackPressed () de la Actividad B.
1) Puedes contener información sobre la actividad que quieres destruir en alguna clase estática. Si puede haber pocas actividades, puede mantener la matriz de valores int
, por ejemplo:
static final int ACTIVITY_DESTROY_FLAG_ACTIVITY_A = 1 static final int ACTIVITY_DESTROY_FLAG_ACTIVITY_B = 2
En onResume
of activity verifique si debe ser destruido. Para Activity_A
podría parecer:
if (StaticClass.activityArray.contains(ACTIVITY_DESTROY_FLAG_ACTIVITY_A)) { finish(); }
2) Con anticipación a su mecanismo de recibo de difusión, puede poner actividad en Intent
por intent.putExtra("activity", Activity_A.this)
y enviarlo con difusión de onCreate
de Activity_A
. En Activity_B
registre el receptor de broadcast y en los botones onClick
call ((Activity) intent.getExtras().get("activity")).finish()
. Bueno, sólo un receptor más – ¿va a gastar recursos para ello?