单顶| CLEAR_TOP 似乎在 95% 的情况下都有效。为什么是5%?
我有一个即将完成的应用程序,具有不平凡的活动结构。有与此应用程序关联的推送通知,选择通知条目应该会显示特定的活动,无论该应用程序是否处于前台/后台/不活动状态。
如果应用程序未激活,我已经能够成功启动应用程序并自动导航到适当的部分。但是,当应用程序处于活动状态时,我遇到了问题。我将提出该问题的简化版本,以传达问题的本质,并且我将根据需要发布我的应用程序活动结构和相关代码的详细信息(实际上,现在正在处理)。
因此,我的应用程序的活动堆栈(大大简化)如下所示:
A -> B-> X
其中 A,根 Activity,是登录页面; B 是一个“主页”,X 是可以从主页启动的几个活动之一(但一次只有一个实例处于活动状态;因为这些只能从 B 启动)。
当选择通知时,我需要应用程序自动导航到 B,无论它之前处于什么状态 - 是否 [A]、[A -> ]。 B],[A→] B-> X] 或 [ ](应用程序未激活)。
我的通知将 Intent 传递给活动 A。我尝试过使用 CLEAR_TOP 和 NEW_TASK 标志,但没有。 A 目前的 launchmode=singleTask。这样做,我想我正在解决所有可能的现有堆栈配置并将它们减少到[A]。 Intent 还带有一个额外信息,将其标识为来自通知,而不是通常的启动。
活动 A 在识别出 Intent 是从通知发送时(它可以在 onCreate() 和 onNewIntent() 中执行此操作),将 Intent 发送到 Activity B。此 Intent 包含 CLEAR_TOP 和 SINGLE_TOP。 B 的 launchmode=singleTop。
95% 的情况下,这会按预期工作,按下通知后,应用程序的堆栈为 [A -> B]。 大约 5% 的情况下,应用程序最终会出现一堆 [A -> B-> B]。
对这里发生的事情有什么想法,或者我做错了什么吗?
如果这证明是一个不平凡的问题,我将发布更多详细信息。事实上,现在发布更多详细信息...
~~~~~~~~~~~~~~~~~~~~~~~~更多细节~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
单步执行调试器显示,每次 A 向 B 发送其意图时,B 的现有实例都会 onDestroy( ) 在 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);
}
在登录页面(活动 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);
有关活动堆栈结构的更多详细信息,请参见图片:
http://i89.photobucket.com/albums/k207/cephron/ActivityStack.png
这是一千个单词:
Activity A 是登录页面;登录成功后,启动B. B是一个TabActivity,其中包含三个Activity(用C、D、E表示)。 C、D 和 E 中的每一个实际上都是一个 ActivityGroup,其子 Activity 模仿 Activity 的通常堆栈行为。
因此,每个选项卡都包含自己的活动堆栈,并且选项卡之间的切换会更改当前通过用户导航推送到/弹出的堆栈(每个选项卡包含通过实体的层次结构浏览的 ListActivities 堆栈)。这些还可以启动巨型“B”TabActivity(由 X 表示)之外的新活动。
因此,从活动 A 登录后,在接受更多用户输入之前至少创建三个活动: -B 已创建(并且可见,因为 TabWidget 现在位于屏幕顶部) - 创建活动组之一:属于默认选项卡的活动组。该 ActivityGroup 在屏幕上仍然没有显示,但是......它只显示其子活动堆栈的顶部活动。 - 所以,最后,创建了该 ActivityGroup 堆栈的“根”Activity(图中,F 是此类 Activity 的一个示例)。此 Activity 显示在 TabWidget 下方。
每个选项卡被访问一次后,不会通过在选项卡之间切换来创建/销毁更多的 Activity(不计算内存占用)。 在任何选项卡中按回“finish()”将位于该堆栈顶部的“活动”,显示其下方的活动。 在任何选项卡中从根 Activity(如 F)按回即可完成整个 TabActivity,将用户发送回 A。
传递给 B 的意图还指示它自动导航到与默认选项卡不同的选项卡。在我们最终得到一堆 [A -> B-> B],第一个 B 导航到正确的选项卡,第二个 B 位于默认选项卡。
I have a nearly-finished application with a non-trivial activity structure. There are push notifications associated with this app, and selecting the notification entry is supposed to bring up a specific activity regardless of whether the app is foreground/background/not active.
If the app is not active, I have been able to successfully start the app and auto-navigate to the appropriate part. However, when the app is active, I have a problem. I will present a simplified version of the issue, to communicate the nature of the problem, and I will post the details of my app's activity structure and relevant code as needed (actually, working on that now).
So, my app's activity stack (greatly simplified) looks like this:
A -> B -> X
Where A, the root activity, is a login page; B is something of a "home page" and X is one of several activities that can be started from the home page (but only one instance active at a time; as these can only be started from B).
When the notification is selected, I need the application to automatically navigate to B, regardless of what state it was in beforehand - whether [A], [A -> B], [A -> B -> X] or [ ] (app not active).
My notification passes an Intent to activity A. I have tried using CLEAR_TOP and NEW_TASK flags, and none. A currently has launchmode=singleTask. Doing this, I figure I am addressing all possible existing stack configurations and reducing them to [A]. The Intent also carries an extra which identifies it as coming from a notification, as opposed to a usual launch.
Activity A, upon identifying the Intent as being sent from the notification (it can do this in both onCreate() and onNewIntent() ), sends an Intent to Activity B. This Intent contains CLEAR_TOP and SINGLE_TOP. B has launchmode=singleTop.
95% of the time, this works as desired, and after pressing the notification, the app's stack is [A -> B].
Roughly 5% of the time, the app somehow ends up with a stack of [A -> B -> B].
Any ideas on what's happening here, or anything I'm doing wrong?
I'll post more detail if this turns out to be a non-trivial problem. In fact, posting more details now...
~~~~~~~~~~~~~~~~~~~~~~~MORE DETAIL~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Stepping through the debugger shows that, every time A sends its intent to B, the existing instance of B is onDestroy()'d before being onCreate()'d and then also having its onNewIntent() called. This seems odd to me, and suggests that either I misunderstand the flags I am using (CLEAR_TOP and SINGLE_TOP), or something else is interfering with them.
I have not successfully reproduced the erroneous stack structure in debug. Not sure if it's because it doesn't happen in debug, or I just haven't tried enough times.
Code for Intents being made:
In the C2DM receiver service:
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);
}
In LoginPage (Activity A), after successful login:
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);
For more detail on the activity stack structure, here's the picture:
http://i89.photobucket.com/albums/k207/cephron/ActivityStack.png
And here's the thousand words:
Activity A is a login page; upon successful login, it starts B.
B is a TabActivity, which contains three Activities within it (represented by C, D, E).
Each of C, D, and E is actually an ActivityGroup whose child Activities mimic the usual stack behaviour of Activities.
So, each tab contains its own stack of activities, and switching between tabs changes which of these stacks is currently being pushed to/popped from by the user's navigation (each tab contains a stack of ListActivities browsing through a hierarchical structure of entities). These can also start new activities beyond the giant 'B' TabActivity (represented by X).
So, upon logging in from Activity A, at least three activities are created before more user input is accepted:
-B is created (and is seen, because of the TabWidget now at the top of the screen)
-One of the ActivityGroups is created: whichever one belongs to the default tab. This ActivityGroup remains unrepresented on screen, however...it only shows the top activity of its stack of child activities.
-So, finally, the "root" activity of that ActivityGroup's stack is created (in the picture, F is an example of such an Activity). This Activity shows itself below the TabWidget.
After each tab has been visited once, no more Activities are created/destroyed by switching between tabs (not counting memory kills).
Pressing back in any tab finish()es the Activity at the top of that stack, showing the one beneath it.
Pressing back from the root activity (like F) in any tab finishes the whole TabActivity, sending the user back to A.
The intent passed to B also instructs it to automatically navigate to a different tab than the default. In the case where we end up with a stack of [A -> B -> B], the first B is navigated to the correct tab, and the second is at the default.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
TL;DR;不要同时使用 CLEAR_TOP 和 SINGLE_TOP
如果仅在 5% 的时间内产生错误,则很可能是并发问题。你说你有SINGLE_TOP | CLEAR_TOP 用于调用 Activity B。CLEAR_TOP 销毁 Activity B 的当前实例,并将 Intent 传递给 onCreate()。 SINGLE_TOP 不会销毁 Activity B 的当前实例,并将意图传递给 onNewIntent()。
当首先读取 SINGLE_TOP 标志时,意图将被传递到调用 onNewIntent() 的 Activity B 的当前实例。然后读取 CLEAR_TOP 并销毁 Activity B,并使用 onCreate() 创建一个新实例,一切正常。
当第一次读取 CLEAR_TOP 时,Activity B 的现有实例将被销毁,并使用 onCreate() 创建一个新实例。然后读取 SINGLE_TOP 并将意图传递给 onNewIntent() 。再次,它成功了。
当同时读取 CLEAR_TOP 和 SINGLE_TOP 时,当前 Activity 实例将被销毁,CLEAR_TOP 调用 onCreate(),SINGLE_TOP 也会调用 onCreate(),因为此时不存在 Activity B 的实例。因此,你最终会得到 A->B->B。
TL;DR; Don't use CLEAR_TOP with SINGLE_TOP at the same time
If it only produces an error 5% of the time, it is likely to be a concurrency issue. You said you have SINGLE_TOP | CLEAR_TOP for calling Activity B. CLEAR_TOP destroys the current instance of Activity B and the intent is delivered to onCreate(). SINGLE_TOP doesn't destroy the current instance of Activity B, and delivers the intent to onNewIntent().
When the SINGLE_TOP flag is read first, the intent is delivered to the current instance of Activity B calling onNewIntent(). Then CLEAR_TOP is read and Activity B is destroyed and a new instance is created with onCreate() and everything works fine.
When CLEAR_TOP is read first, the existing instance of Activity B is destroyed and a new one is created with onCreate(). Then SINGLE_TOP is read and the intent is delivered to onNewIntent() as well. Again, it works out.
When CLEAR_TOP and SINGLE_TOP are read at the same time, the current instance of activity is destroyed and CLEAR_TOP calls onCreate() and SINGLE_TOP calls onCreate() as well, because no instance of Activity B exists at the moment. Thus, you end up with A->B->B.