Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SINGLE_TOP | CLEAR_TOP seem to work 95% of the time. Why the 5%?

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.

like image 558
Cephron Avatar asked Mar 30 '11 17:03

Cephron


1 Answers

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.

like image 159
siamii Avatar answered Oct 30 '22 09:10

siamii