android - 无论应用程序状态如何,从通知正确启动 Activity

标签 android android-intent notifications onactivityresult

我有一个带有初始屏幕 Activity 的应用程序,后跟一个主 Activity。初始屏幕在启动主 Activity 之前加载内容(数据库等)。从这个主 Activity 中,用户可以导航到多个其他子 Activity 并返回。一些子 Activity 是使用 startActivityForResult() 启动的,其他的只是 startActivity()

Activity 层次如下图所示。

|                    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. 必须维护 Activity 中的状态,因为用户已经选择了一些食谱来创建购物 list 。如果启动一个新的 Activity,我相信状态将会丢失。
  2. 如果该应用程序在主 Activity 中,请将其置于最前面,并在代码中让我知道我是从通知到达的。
  3. 如果应用程序位于使用 startActivityForResult() 启动的子 Activity 中,我需要在返回主 Activity 之前向 Intent 添加数据,以便它可以正确捕获结果。<
  4. 如果应用程序位于以 startActivity() 启动的子 Activity 中,我只需要返回,因为没有其他事情可做(目前有效)。
  5. 如果应用程序既不在后台也不在前台(即它正在运行)我必须启动主 Activity 并且还知道我是从通知到达的,以便我可以设置设置尚未设置的内容,因为在这种情况下,在我当前的设置中跳过了 Splash Activity。

我已经在 SO 和其他地方尝试了很多不同的建议,但我无法成功获得上述行为。我也试过阅读 documentation没有变得更聪明,只是一点点。单击我的通知时,我对上述情况的当前情况是:

  1. 我在 onNewIntent() 中到达主 Activity 。如果应用程序未运行(或在后台),我不会到达这里。这似乎是预期和期望的行为。
  2. 我无法捕捉到我来自任何子 Activity 中的通知,因此我无法在这些 Activity 中正确调用 setResult()我应该怎么做?
  3. 这目前有效,因为通知只是关闭子 Activity,这没问题。
  4. 我可以通过使用 getIntent() 和带有 bool 值的 Intent.getBooleanExtra()onCreate() 中获取 Notification Intent在通知中设置。因此我应该能够让它工作,但我不确定这是最好的方法。 执行此操作的首选方法是什么?

当前代码

创建通知:

当服务中的 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() 开始的子 Activity 中:

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

最佳答案

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

  1. Use an IntentService in your notification instead of Intent notifyIntent = new Intent(context, MainActivity.class);

现在,只要用户点击通知,就会调用 intentservice。

  1. in the intent service,Broadcast something.

  2. in OnResume of all your desired activity register the broadcast listener (for the broadcast you create in 2nd phase) and in OnPause unregister it

现在,无论何时您在进行任何 Activity 并且用户点击通知,您都会毫无问题地收到通知,并且不会再进行任何 Activity

  1. in your Application class define a public Boolean. lets called it APP_IS_RUNNING=false; in your MainActivity, in OnPause make it false and in OnResume make it true;

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

NOTE : if you want to handle more states, like isInBackground,Running,Destroyed,etc... you can use an enum or whatever you like

你想在应用程序运行时做不同的事情,对吗?因此,在您在第一阶段声明的 Intent 服务中,检查您在应用程序类中定义的参数。 (在我们的示例中,我指的是 APP_IS_RUNNING)如果它是真的,请使用广播,否则调用打开所需 Activity 的 Intent 。

关于android - 无论应用程序状态如何,从通知正确启动 Activity ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35539294/

相关文章:

android - 有 Intent 地运行外部后台服务

android - 关闭来自 BroadCastReceiver 的通知

java - Java Web 应用程序中的事件。何时使用

ios - react-native-push-notification 在 ios 设备上不起作用

java - 为什么我收到警告 :Class is a raw type. References to generic type Class<T> should be parameterized”?

android - 抽屉导航与 android 中的 fragment 重叠

android - 如何查看ACTION_SEND intent是否启动?

android - 如何设置文件权限 - Android

java - 在 Android Studio 3.1 中找不到 android-maven-gradle-plugin.jar?

android - 我们如何在没有源代码的情况下调试已签名的 apk?