无论应用状态

时间:2016-02-21 17:20:08

标签: android android-intent notifications onactivityresult

我有一个带有启动画面活动的应用程序,后跟一个主要活动。启动主屏幕之前,启动画面会加载内容(数据库等)。通过此主Activity,用户可以导航到多个其他子活动并返回。一些子活动是使用startActivityForResult()启动的,其他活动仅使用startActivity()启动。

活动层次结构如下所示。

|                    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.

点击通知时,我需要达到以下行为

  1. 必须维护活动中的状态,因为用户已选择一些食谱来创建购物清单。如果新的活动开始,我相信州将会失去。
  2. 如果应用程序位于主要活动中,请将其放在前面,并通过我从通知到达的代码告诉我。
  3. 如果应用程序位于以startActivityForResult()开头的子活动中,我需要在返回主活动之前将数据添加到Intent,以便它可以正确捕获结果。
  4. 如果应用程序位于以startActivity()开头的子活动中,我只需要返回,因为没有其他任何事情要做(这当前有效)。
  5. 如果应用程序不在后台,也不在前台(即正在运行),我必须启动主要活动,并且知道我是从通知到达的,所以我可以设置尚未设置的内容,因为在我当前的设置中跳过了Splash Activity。
  6. 我已经在SO和其他地方尝试了很多不同的建议,但我无法成功地获得上述行为。我也试过阅读documentation而没有变得更聪明,只是一点点。单击我的通知时上述案例的当前情况是:

    1. 我在onNewIntent()的主要活动中到达。如果应用程序未运行(或在后台运行),我不会到达此处。这似乎是预期和期望的行为。
    2. 我无法发现我在任何子活动中都来自通知,因此我无法在这些活动中正确调用setResult()我该怎么做?
    3. 这当前有效,因为通知只关闭子Activity,这没关系。
    4. 我可以使用onCreate()getIntent()在通知中使用布尔值设置,在Intent.getBooleanExtra()中获取通知意图。因此,我应该能够使它工作,但我不确定这是最好的方法。 这样做的首选方式是什么?
    5. 当前代码

      创建通知:

      当服务中的HTTP请求返回某些数据时,将创建通知。

      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.
          ... 
      }
      

      在儿童内部活动以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>
      

3 个答案:

答案 0 :(得分:5)

这么复杂的问题:D 以下是您应该如何处理这个问题:

  
      
  1. 在通知中使用IntentService而不是   Intent notifyIntent = new Intent(context, MainActivity.class);
  2.   

到目前为止,每当用户点击通知时,都会调用一个intentservice。

  
      意图服务中的
  1. ,广播一些东西。

  2.   在所有您想要的活动的OnResume中
  3. 注册广播监听器(对于您在第二阶段创建的广播)和OnPause取消注册

  4.   

到目前为止,无论您何时进行任何活动并且用户点击通知,都会毫无问题地通知您,并且无需重新开展活动

  
      Application类中的
  1. 定义了一个公共布尔值。我们叫它APP_IS_RUNNING = false;在你的MainActivity中,在OnPause中将其设为false,并在OnResume中使其成为真;
  2.   

通过这样做,您可以了解您的应用是否正在运行或处于后台运行。

  

注意:如果你想处理更多的状态,比如isInBackground,Running,Destroyed等......你可以使用枚举或任何你喜欢的东西

你想在应用程序运行时做不同的事情,对吗?因此,在第1阶段声明的intent服务中,检查您在Application Class中定义的参数。 (我的意思是APP_IS_RUNNING在我们的例子中)如果它是真的使用广播,否则调用打开你想要的活动的意图。

答案 1 :(得分:0)

你的哥们错了。 onActivityResult不是解决方案。 只需一个简单的答案就是使用广播接收器

清单文件中声明操作:

<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>

创建广播接收器类:

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.
         }
    }
}

在你的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

现在,当用户点击通知中的按钮并且广播接收时,r将捕获该事件并执行操作。

答案 2 :(得分:0)

这是我最终做的事情。这是一个有效的解决方案,并测试app状态,子Activity等的每种情况。进一步的评论非常感谢。

创建通知

仍然会按照原始问题创建通知。我尝试使用带有广播的IntentService,如@Smartiz所建议的那样。应用程序运行时,此工作正常;注册儿童活动接收广播,我们可以从那时起做我们喜欢的事情,就像照顾国家一样。但是,问题是当应用程序在前台运行时。然后我们必须使用Intent中的标志Intent.FLAG_ACTIVITY_NEW_TASK来从IntentService广播(Android需要这个),因此我们将创建一个新的堆栈,事情开始变得混乱。这可能会解决,但我认为使用SharedPreferences或其他人指出的类似事情更容易保存状态。这也是存储持久状态的更有用的方法。

因此,通知只是像以前一样创建:

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());

保存状态

在需要保存状态的子活动中,我只需将{I}需要保存到onPause()中的SharedPreferences。因此,该状态可以在以后需要的地方重复使用。这也是以更一般的方式存储状态的非常有用的方式。我之所以没有,因为我认为SharedPreferences是为首选项保留的,但它可以用于任何事情。我希望我早点意识到这一点。

打开通知

现在,在打开通知时,会发生以下情况,具体取决于应用程序的状态以及打开/暂停的子活动。请记住,使用的标志是Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP

一种。儿童活动

  1. 在前面跑:子活动已关闭,使用onPause中的SharedPreferences保存适用的状态,可以在onCreate或主活动中的任何位置获取。
  2. 应用程序在后台:相同的行为。
  3. 应用程序在后台,但被操作系统杀死(使用adb shell测试:此时没有堆栈,因此打开了MainActivity。但是,应用程序处于脏状态,所以我还原意图使用传入的数据返回到初始屏幕并返回到主Activity。当用户关闭它时,状态再次保存在子活动的onPause中,并且可以在主Activity中获取它。
  4. B中。主要活动

    1. 在前面跑:意图被onNewIntent抓住,一切都是金色的。做我们想做的事。
    2. 应用程序在后台:相同的行为。
    3. 应用程序在后台,但被操作系统杀死(使用adb shell测试:应用程序处于脏状态,因此我们将意图恢复到启动屏幕/加载屏幕并返回主活动。
    4. ℃。应用程序根本没有运行

      这就像Android在后台杀死应用程序以释放资源一样。只需打开主Activity,恢复到启动屏幕即可加载并返回主Activity。

      d。启动活动

      在按下通知时,用户不太可能处于启动活动/加载活动中,但理论上可能。如果用户这样做,StrictMode会在关闭应用程序时抱怨有2个主要活动,但我不确定它是完全正确的。无论如何,这是非常假设的,所以我现在不会花太多时间在它上面。

      我认为这不是一个完美的解决方案,因为它需要在这里进行一些编码并且在那里进行一些编码并且如果应用程序处于脏状态则来回恢复Intent,但它可以工作。评论非常感谢。