【发布时间】:2016-06-03 00:46:47
【问题描述】:
我有一个带有启动屏幕 Activity 的应用程序,然后是一个主 Activity。启动主 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.
我需要在点击通知时实现以下行为:
- 必须维护 Activity 中的状态,因为用户选择了一些食谱来创建购物清单。如果启动一个新的Activity,我相信状态会丢失。
- 如果应用位于 Main Activity 中,请将其放在最前面,并在代码中告诉我我是从 Notification 到达的。
- 如果应用位于以
startActivityForResult()开头的子 Activity 中,我需要在返回主 Activity 之前将数据添加到 Intent,以便它可以正确捕获结果。 - 如果应用位于以
startActivity()开头的子 Activity 中,我只需要返回,因为没有其他事情可做(目前可行)。 - 如果应用程序不在后台,也不在前台(即未运行),我必须启动 Main Activity 并且还知道我是从 Notification 到达的,以便我可以设置设置尚未设置的内容,因为在我当前的设置中,在这种情况下跳过了 Splash Activity。
我在 SO 和其他地方尝试了很多不同的建议,但我未能成功获得上述行为。我也尝试过阅读documentation,但不会变得更聪明,只是一点点。单击我的通知时,我对上述情况的当前情况是:
- 我到达
onNewIntent()的主要活动。如果应用程序未运行(或在后台),我不会到达这里。这似乎是预期和期望的行为。 - 我无法在任何子活动中发现我来自通知,因此我无法在这些活动中正确调用
setResult()。 我应该怎么做? - 这目前有效,因为通知只是关闭子 Activity,这没关系。
- 我可以通过使用
getIntent()和Intent.getBooleanExtra()在通知中设置布尔值来获得onCreate()中的通知意图。因此,我应该能够使其工作,但我不确定这是最好的方法。 首选的方法是什么?
当前代码
创建通知:
当服务中的 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>
【问题讨论】:
-
这里有一个复杂的问题。我怀疑你会得到一个全面的解决方案!也就是说,可以帮助您制定解决方案的一件事是通知也可以触发广播(不仅仅是活动)。您可以利用这一点来使用 BroadcastReceiver 在调用任何活动之前决定如何处理单击。我不会非常依赖基于活动的意图来做你想做的事。
-
感谢您的提示,我将研究通知的广播部分。如果它的工作方式看起来我可能能够解决一些问题。
-
但是(推送)通知来自广播接收器。无需从您的通知中启动另一个 BroadcastReceiver。
-
如果您将活动状态存储在共享首选项中,您可以在创建通知之前访问它。例如,将所有需要的数据(购物清单、上次打开的活动等)存储在
onPause()。 -
我想我可以尝试在 SharedPreferences 中存储一些状态来让自己的生活更轻松。像这样在 SharedPreferences 中保存上次打开的 Activity 实际上可能会使一些逻辑变得更简单。有没有办法在子 Activity 由于使用 FLAG_ACTIVITY_CLEAR_TOP 标志而关闭时从通知中拦截 Intent?
标签: android android-intent notifications onactivityresult