SINGLE_TOP | CLEAR_TOP 看起来在大部分情况下都能正常运作,但为什么有5%的情况会出错?

15
我有一个几乎完成的应用程序,具有复杂的活动结构。此应用程序与推送通知相关联,选择通知条目应该带来特定的活动,无论应用程序是在前台、后台还是不活动状态下。
如果应用程序处于非活动状态,则我已成功启动应用程序并自动导航到适当的部分。然而,当应用程序处于活动状态时,我遇到了问题。为了传达问题的性质,我将提供一个简化版本,并根据需要发布我的应用程序的活动结构和相关代码的详细信息(实际上正在处理这个问题)。
因此,我的应用程序的活动堆栈(大大简化)如下所示:
A -> B -> X
其中A是根活动,是登录页面;B是一种“主页”,X是可以从主页启动的多个活动之一(但同时只能有一个实例处于活动状态;因为这些只能从B启动)。
当选择通知时,我需要应用程序自动导航到B,无论之前处于什么状态——无论是[A]、[A->B]、[A->B->X]还是[](应用程序不活动)。
我的通知向活动A传递一个意图。我尝试使用CLEAR_TOP和NEW_TASK标志,以及不使用任何标志。 A当前具有launchmode=singleTask。这样做,我认为我正在处理所有可能的现有堆栈配置,并将它们减少到[A]。该意图还携带一个额外的标识符,表明它来自通知,而不是普通启动。
活动A在确定意图是从通知发送的(它可以在onCreate()和onNewIntent()中都这样做)后,向活动B发送一个意图。此意图包含CLEAR_TOP和SINGLE_TOP。 B具有launchmode=singleTop。
95%的时间,这个操作按预期工作,按下通知后,应用程序的堆栈为[A->B]。 大约5%的时间,应用程序以[A->B->B]的堆栈结束。
对此有什么想法,或者我做错了什么吗?
如果这是一个非平凡的问题,我会发布更多详细信息。事实上,现在发布更多详细信息...
通过调试器逐步进行显示,每次A将其意图发送给B时,现有的B实例都会被销毁(在onCreate()之前),然后还会调用其onNewIntent()。这对我来说似乎很奇怪,并表明我可能误解了我正在使用的标志(CLEAR_TOP和SINGLE_TOP),或者其他东西正在干扰它们。
我尚未成功地在调试中重现错误的堆栈结构。不确定是因为它在调试中不会发生,还是我尝试的次数不够多。
用于制作意图的代码:
在C2DM接收器服务中:
protected void onMessage(Context context, Intent intent) {
    int icon = R.drawable.some_drawable;
    CharSequence tickerText = "blah";
    long when = System.currentTimeMillis();
    Notification notification = new Notification(icon, tickerText, when);

    //Context context = getApplicationContext(); //Don't need this; using the context passed by the message.
    CharSequence contentTitle = intent.getStringExtra("payload");
    CharSequence contentText = "Lorem ipsum dolor si amet,";
    Intent notificationIntent = new Intent(this, LoginPage.class);
    //notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); //Tried with and without
    notificationIntent.putExtra(PushManager.PUSH_INTENT, PushManager.PUSH_INTENT); //Indicator that this was send from notification

    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
    notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);

    notificationManager.notify(PushManager.ALARM_NOTIFICATION_ID, notification);
}

在登录页面(Activity A)成功登录后:

Intent i = new Intent(LoginPage.this, TabHomePage.class);
// (If we're automatically going to tab 2, inform next activity)
if(fromNotification) {
    i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    i.putExtra(TabHomePage.TAB_NUMBER, TabHomePage.TAB_2);
}
startActivity(i);

关于Activity堆栈结构的更多细节,请看下面的图片:

http://i89.photobucket.com/albums/k207/cephron/ActivityStack.png

以下是详细说明:

Activity A 是一个登录页面,成功登录后会启动B。 B是一个TabActivity,在其中包含三个活动(用C、D、E表示)。 每个活动(C、D、E)实际上都是一个ActivityGroup,其子活动模拟了常规活动的堆栈行为。

因此,每个选项卡都包含自己的活动堆栈,切换选项卡会改变用户导航时当前正在被推入/弹出的这些堆栈之一(每个选项卡都包含一个浏览实体层次结构的ListActivities堆栈)。它们还可以启动超出巨大的'B' TabActivity范围的新活动(由X表示)。

因此,在从Activity A登录后,至少需要创建三个活动才能接受更多用户输入: -创建B(并且由于TabWidget现在位于屏幕顶部,所以可以看到它) -创建ActivityGroups中的一个:属于默认选项卡的那个。但此ActivityGroup不会在屏幕上显示,它只显示其子活动堆栈的顶部活动。 -因此,该ActivityGroup堆栈的“根”活动被创建(在图片中,F是这样一种活动的示例)。此活动在TabWidget下方显示。

访问每个选项卡一次后,切换选项卡不会再创建/销毁任何活动(不包括内存清理)。 在任何选项卡中按返回键会完成该堆栈顶部的活动,并显示其下面的一个活动。 从任何选项卡中的根活动(如F)按返回键会完成整个TabActivity,将用户发送回A。

传递给B的意图还指示它自动导航到与默认选项卡不同的选项卡。在我们最终得到[A -> B -> B]堆栈的情况下,第一个B会导航到正确的选项卡,而第二个则位于默认选项卡。

1个回答

15

TL;DR: 不要同时使用CLEAR_TOP和SINGLE_TOP

如果只有5%的时间出现错误,很可能是并发问题。你说你在调用Activity B时使用了SINGLE_TOP | CLEAR_TOP. CLEAR_TOP会销毁当前的Activity B实例,并将intent传递给onCreate()方法。而SINGLE_TOP则不会销毁当前的Activity B实例,并将intent传递给onNewIntent()方法。

当先读取SINGLE_TOP标志,intent被传递给Activity B的当前实例,调用onNewIntent()方法。接着读取CLEAR_TOP标志,Activity B被销毁并创建新的实例,调用onCreate()方法,这样一切都正常。

当先读取CLEAR_TOP标志,已有的Activity B实例被销毁并创建新的实例,调用onCreate()方法。然后再读取SINGLE_TOP标志,intent同样被传递给onNewIntent()方法,这样也能正常工作。

但是,当CLEAR_TOP和SINGLE_TOP同时被读取时,当前Activity实例被销毁,CLEAR_TOP调用onCreate()方法,而此时因为没有Activity B实例存在,SINGLE_TOP也会调用onCreate()方法,结果就变成了A->B->B。


1
这似乎起作用了。现在只使用CLEAR_TOP。谢谢! - Cephron
1
只是想注意一下:截至今天,文档明确提到结合清除顶部和单个顶部标志,因此现在通常情况下这应该是可以的。 - stkent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接