Inicie correctamente la Actividad de la Notificación independientemente del estado de la aplicación

Tengo una aplicación con una actividad de pantalla de bienvenida, seguida de una actividad principal. La pantalla de inicio carga cosas (base de datos, etc.) antes de iniciar la Actividad principal. A partir de esta actividad principal, el usuario puede navegar a varias actividades de otro niño y volver. Algunas de las actividades secundarias se inician con startActivityForResult() , otras sólo startActivity() .

La jerarquía de actividad se muestra a continuación.

 | Child A (startActivityForResult) | / |--> Splash --> Main -- Child B (startActivityForResult) | ^ \ | | Child C (startActivity) | \ | This Activity is currently skipped if a Notification is started | while the app is not running or in the background. 

Tengo que lograr el siguiente comportamiento al hacer clic en una notificación :

  1. El estado en la actividad debe mantenerse , ya que el usuario ha seleccionado algunas recetas para crear una lista de compras. Si se inicia una nueva Actividad, creo que el estado se perderá.
  2. Si la aplicación está en la actividad principal, traer a la parte delantera y me dejó saber en el código que llegué de una notificación.
  3. Si la aplicación está en un niño Actividad iniciada con startActivityForResult() , necesito agregar datos a un intento antes de volver a la actividad principal para que pueda captar el resultado correctamente.
  4. Si la aplicación está en un niño Actividad iniciada con startActivity() Sólo tengo que volver ya que no hay nada más que hacer (esto funciona actualmente).
  5. Si la aplicación no está en segundo plano, ni en primer plano (es decir, no está en ejecución) debo iniciar la Actividad Principal y también sé que llegué desde una Notificación, para poder configurar cosas que aún no están configuradas, ya que La actividad Splash se omite en este caso en mi configuración actual.

He probado muchas sugerencias aquí en SO y en otros lugares, pero no he sido capaz de conseguir el comportamiento descrito anteriormente. También he intentado leer la documentación sin llegar a ser mucho más sabio, sólo un poco. Mi situación actual para los casos anteriores al hacer clic en mi notificación es:

  1. onNewIntent() Actividad Principal en onNewIntent() . No llego aquí si la aplicación no se está ejecutando (o en segundo plano). Esto parece ser el comportamiento esperado y deseado.
  2. No puedo captar que vengo de una notificación en actividades de niños, por lo que no puedo llamar correctamente a setResult() en esas actividades. ¿Cómo debería hacer esto?
  3. Esto funciona actualmente, ya que la notificación sólo cierra el niño Actividad, que está bien.
  4. Puedo obtener la Intención de notificación en onCreate() utilizando getIntent() y Intent.getBooleanExtra() con un conjunto booleano en la notificación. Debería ser capaz de hacer que funcione, pero no estoy seguro de que esta sea la mejor manera. ¿Cuál es la forma preferida de hacer esto?

Código actual

Creación de notificaciones:

La notificación se crea cuando una solicitud HTTP dentro de un servicio devuelve algunos datos.

 NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(getNotificationIcon()) .setAutoCancel(true) .setColor(ContextCompat.getColor(context, R.color.my_brown)) .setContentTitle(getNotificationTitle(newRecipeNames)) .setContentText(getContentText(newRecipeNames)) .setStyle(new NotificationCompat.BigTextStyle().bigText("foo")); Intent notifyIntent = new Intent(context, MainActivity.class); notifyIntent.setAction(Intent.ACTION_MAIN); notifyIntent.addCategory(Intent.CATEGORY_LAUNCHER); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); /* Add a thing to let MainActivity know that we came from a Notification. */ notifyIntent.putExtra("intent_bool", true); PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(notifyPendingIntent); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(111, builder.build()); 

MainActivity.java:

 @Override protected void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); if (intent.getBooleanExtra("intent_bool", false)) { // We arrive here if the app was not running, as described in point 4 above. } ... } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case CHILD_A: // Intent data is null here when starting from Notification. We will thus crash and burn if using it. Normally data has values when closing CHILD_A properly. // This is bullet point 2 above. break; case CHILD_B: // Same as CHILD_A break; } ... } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); boolean arrivedFromNotification = intent.getBooleanExtra("intent_bool", false); // arrivedFromNotification is true, but onNewIntent is only called if the app is already running. // This is bullet point 1 above. // Do stuff with Intent. ... } 

Dentro de un niño Actividad iniciada con startActivityForResult() :

 @Override protected void onNewIntent(Intent intent) { // This point is never reached when opening a Notification while in the child Activity. super.onNewIntent(intent); } @Override public void onBackPressed() { // This point is never reached when opening a Notification while in the child Activity. Intent resultIntent = getResultIntent(); setResult(Activity.RESULT_OK, resultIntent); // NOTE! super.onBackPressed() *must* be called after setResult(). super.onBackPressed(); this.finish(); } private Intent getResultIntent() { int recipeCount = getRecipeCount(); Recipe recipe = getRecipe(); Intent recipeIntent = new Intent(); recipeIntent.putExtra(INTENT_RECIPE_COUNT, recipeCount); recipeIntent.putExtra(INTENT_RECIPE, recipe); return recipeIntent; } 

AndroidManifest.xml:

 <application android:allowBackup="true" android:icon="@mipmap/my_launcher_icon" android:label="@string/my_app_name" android:theme="@style/MyTheme" android:name="com.mycompany.myapp.MyApplication" > <activity android:name="com.mycompany.myapp.activities.SplashActivity" android:screenOrientation="portrait" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.mycompany.myapp.activities.MainActivity" android:label="@string/my_app_name" android:screenOrientation="portrait" android:windowSoftInputMode="adjustPan" > </activity> <activity android:name="com.mycompany.myapp.activities.ChildActivityA" android:label="@string/foo" android:parentActivityName="com.mycompany.myapp.activities.MainActivity" android:screenOrientation="portrait" android:windowSoftInputMode="adjustPan" > <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.mycompany.myapp.activities.MainActivity" > </meta-data> </activity> <activity android:name="com.mycompany.myapp.activities.ChildActivityB" android:label="@string/foo" android:parentActivityName="com.mycompany.myapp.activities.MainActivity" android:screenOrientation="portrait" > <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.mycompany.myapp.activities.MainActivity" > </meta-data> </activity> ... </manifest> 

Una pregunta tan complicada: D Aquí es cómo debe tratar este problema:

  1. Utilice un IntentService en su notificación en lugar de Intent notifyIntent = new Intent(context, MainActivity.class);

Por ahora, siempre que el usuario haga clic en la notificación, un intentservice sería llamado.

  1. En el servicio de intención, difundir algo.

  2. En OnResume de toda su actividad deseada registre el oyente de difusión (para la emisión que cree en la 2ª fase) y en OnPause cancele su registro

Por ahora cuando usted está en cualquier actividad y el usuario haga clic en la notificación, se le informará sin ningún problema y sin ninguna recreación de la actividad

  1. En su clase Application definen un booleano público. Lo llamamos APP_IS_RUNNING = false; En su MainActivity, en OnPause lo hacen falso y en OnResume lo hacen verdadero;

Al hacer esto, puedes entender que tu aplicación se está ejecutando o no, o está en segundo plano.

NOTA: si quieres manejar más estados, como isInBackground, Running, Destroyed, etc … puedes usar un enum o lo que quieras

¿Quieres hacer cosas diferentes cuando la aplicación se está ejecutando, ¿verdad? Por lo que en el servicio de intención que declaró en la primera fase compruebe el parámetro que define en su Clase de aplicación. (Me refiero a APP_IS_RUNNING en nuestro ejemplo) si se utilizó el uso de difusión y de lo contrario llamar a una intención que abre la actividad deseada.

Te estás yendo en un compañero de manera equivocada. OnActivityResult no es la solución. Sólo una simple respuesta a esto sería utilizar Broadcast Receiver

Declare una acción En su archivo de manifiesto :

 <receiver android:name="com.myapp.receiver.AudioPlayerBroadcastReceiver" > <intent-filter> <action android:name="com.myapp.receiver.ACTION_PLAY" /> <!-- add as many actions as you want here --> </intent-filter> </receiver> 

Crear la clase del receptor de difusión :

 public class AudioPlayerBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(action.equalsIgnoreCase("com.myapp.receiver.ACTION_PLAY")){ Myactivity.doSomething(); //access static method of your activity // do whatever you want to do for this specific action //do things when the button is clicked inside notification. } } } 

En el método setNotification ()

 Notification notification = new Notification.Builder(this). setWhen(System.currentTimeMillis()) .setSmallIcon(R.drawable.no_art).build(); RemoteView remoteview = new RemoteViews(getPackageName(), R.layout.my_notification); notification.contentView = remoteview; Intent playIntent = new Intent("com.myapp.receiver.ACTION_PLAY"); PendingIntent playSwitch = PendingIntent.getBroadcast(this, 100, playIntent, 0); remoteview.setOnClickPendingIntent(R.id.play_button_my_notification, playSwitch); //this handle view click for the specific action for this specific ID used in broadcast receiver 

Ahora, cuando el usuario haga clic en el botón en Notificación y recibirá broachast r capturará ese evento y realizará la acción.

Esto es lo que terminé haciendo. Es una solución de trabajo y cada situación del estado de la aplicación, actividad del niño, etc. se prueba. Comentarios adicionales son muy apreciados.

Creación de la notificación

La notificación todavía se crea como en la pregunta original. Intenté usar un IntentService con una emisión como lo sugirió @Smartiz. Esto funciona bien mientras la aplicación se está ejecutando; El niño registrado Actividades recibe la emisión y podemos hacer lo que nos gusta desde ese momento, como cuidar del Estado. El problema, sin embargo, es cuando la aplicación no se está ejecutando en primer plano. Entonces debemos usar la bandera Intent.FLAG_ACTIVITY_NEW_TASK en el intento de difundir desde IntentService (Android lo requiere), así crearemos una nueva pila y las cosas empezarán a ser desordenadas. Esto probablemente se puede trabajar alrededor, pero creo que es más fácil para guardar el estado utilizando SharedPreferences o cosas similares como otros señalaron. Esta es también una forma más útil de almacenar el estado persistente.

Así, la Notificación simplemente se crea como antes:

 NotificationCompat.Builder builder = new NotificationCompat.Builder(context) .setSmallIcon(getNotificationIcon()) .setAutoCancel(true) .setColor(ContextCompat.getColor(context, R.color.my_brown)) .setContentTitle(getNotificationTitle(newRecipeNames)) .setContentText(getContentText(newRecipeNames)) .setStyle(new NotificationCompat.BigTextStyle().bigText("foo")); Intent notifyIntent = new Intent(context, MainActivity.class); notifyIntent.setAction(Intent.ACTION_MAIN); notifyIntent.addCategory(Intent.CATEGORY_LAUNCHER); notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); /* Add a thing to let MainActivity know that we came from a Notification. Here we can add other data we desire as well. */ notifyIntent.putExtra("intent_bool", true); PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(notifyPendingIntent); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(111, builder.build()); 

Estado de guardado

En el niño Actividades que necesitan guardar el estado simplemente guardo el que necesito para SharedPreferences en onPause() . Por lo tanto, ese estado puede ser reutilizado donde sea necesario en un momento posterior. Esta es también una forma muy útil de almacenar el estado de una manera más general. No había pensado en ello, ya que pensé que las SharedPreferences estaban reservadas para las preferencias , pero se puede utilizar para cualquier cosa. Ojalá me hubiera dado cuenta de esto antes.

Abrir la notificación

Ahora, al abrir una Notificación, ocurren las siguientes cosas, dependiendo del estado de la aplicación y de qué actividad secundaria está abierta / en pausa. Recuerde que las banderas utilizadas son Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP .

A. Actividad Infantil

  1. Corriendo al frente: El niño Actividad se cierra, el estado aplicable se guarda mediante SharedPreferences en onPause y se puede obtener en onCreate o en cualquier lugar de la Actividad principal.
  2. La aplicación está en segundo plano: el mismo comportamiento.
  3. La aplicación está en segundo plano, pero ha sido eliminada por el sistema operativo (probado usando adb shell : no hay pila en este punto, por lo tanto MainActivity se abre.) La aplicación está en un estado sucio, por lo que vuelvo a volver a la pantalla de bienvenida Con los datos entrantes y volver a la actividad principal.El estado se guarda de nuevo en onPause en la actividad secundaria cuando el usuario lo cerró y se puede buscar en la actividad principal.

B. Actividad principal

  1. Corriendo en la parte delantera: La Intención está atrapada en onNewIntent y todo es de oro. Haz lo que queramos.
  2. La aplicación está en segundo plano: el mismo comportamiento.
  3. La aplicación está en segundo plano, pero ha sido eliminada por el sistema operativo (probado con adb shell : La aplicación está en un estado sucio, por lo que devolvemos el intento a la pantalla de inicio / pantalla de carga y regreso a la actividad principal.

C. La aplicación no se está ejecutando en absoluto

Esto es realmente lo mismo que si Android mató la aplicación en el fondo a los recursos libres. Sólo tienes que abrir la actividad principal, volver a la pantalla de inicio para cargar y volver a la actividad principal.

D. Actividad de Splash

No es muy probable que un usuario pueda estar en la Actividad de actividad / carga mientras se presiona la Notificación, pero es posible en teoría. Si un usuario hace esto, el StrictMode se queja de tener dos Actividades principales al cerrar la aplicación, pero no estoy seguro de que sea totalmente correcto. De todos modos, esto es altamente hipotético, así que no voy a pasar mucho tiempo en él en este momento.

No creo que esto sea una solución perfecta, ya que requiere un poco de codificación aquí y un poco de codificación allí y revertir Intents hacia adelante y hacia atrás si la aplicación está en un estado sucio, pero funciona. Los comentarios son muy apreciados.

  • Siempre Nulo devuelto después de recortar una foto de un Uri en Android Lollipop?
  • Manejar datos de varias actividades en un onActivityResult ()?
  • OnActivityResult () no se llama
  • Iniciar Actividad de Fragmento utilizando Transición (soporte API 21)
  • fragmentos startActivityForResult devuelven siempre resultCode 0 y el intento null en callback onActivityResult
  • OnActivityResult no se llama cuando se hace clic en el botón Atrás en ActionBar
  • Android reduce el tamaño del archivo para que la imagen capturada por la cámara sea inferior a 500 kb
  • Cámara Android: archivo vacío en el método onActivityResult
  • OnActivityResult devuelve datos nulos para una captura de imagen
  • Identificar en onActivityResult si se seleccionó la imagen de una galería o un video - Android
  • OnActivityResult no llama al Fragmento
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.