SINGLE_TOP | CLEAR_TOP, похоже, работает 95% времени. Почему 5%?

У меня есть законченное приложение с нетривиальной структурой активности. Существуют уведомления о загрузке, связанные с этим приложением, и выбор записи уведомления должен вызывать конкретную активность независимо от того, является ли приложение приоритетным/фоновым/неактивным.

Если приложение неактивно, я смог успешно запустить приложение и автоматически перейти к соответствующей части. Однако, когда приложение активно, у меня есть проблема. Я представлю упрощенную версию проблемы, чтобы сообщить о характере проблемы, и я буду публиковать детали моей структуры активности приложения и соответствующего кода по мере необходимости (фактически, работая над этим сейчас).

Итак, мой стек активности приложения (значительно упрощен) выглядит следующим образом:

A → B → X

Где A, корневая активность, является страницей входа; B - это что-то вроде "домашней страницы", а X - одно из нескольких действий, которые можно запускать с домашней страницы (но только один экземпляр активен одновременно, поскольку их можно запускать только с B).

Когда уведомление выбрано, мне нужно, чтобы приложение автоматически переходило на B, независимо от того, в каком состоянии оно было предварительно - [A], [A → B], [A → B → X] или [] (приложение не активно).

Мое уведомление передает намерение в действие A. Я пробовал использовать флаги CLEAR_TOP и NEW_TASK, и ни один. В настоящее время у него есть startmode = singleTask. Сделав это, я полагаю, что я обращаюсь ко всем возможным существующим конфигурациям стеков и сокращаю их до [A]. Intent также несет дополнительную информацию, которая идентифицирует ее как исходящую из уведомления, в отличие от обычного запуска.

Действие A после определения намерения как отправленного из уведомления (оно может сделать это как в onCreate(), так и onNewIntent()), отправляет Intent для Activity B. Это намерение содержит CLEAR_TOP и SINGLE_TOP. B имеет startmode = singleTop.

95% времени, это работает по желанию, и после нажатия уведомления стек приложения [A → B]. Примерно в 5% случаев приложение каким-то образом заканчивается стеком [A → B → B].

Любые идеи о том, что происходит здесь, или что-то, что я делаю неправильно?

Я расскажу более подробно, если это окажется нетривиальной проблемой. Фактически, размещая более подробную информацию сейчас...

~~~~~~~~~~~~~~~~~~~~~~~ БОЛЬШЕ ДЕТАЛИ ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

Прохождение через отладчик показывает, что каждый раз, когда A отправляет свое намерение в B, существующий экземпляр B является onDestroy() 'd перед тем, как быть onCreate()' d, а затем также имеет вызов 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);
}

В LoginPage (операция 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

И вот тысячи слов:

Активность A - это страница входа в систему; при успешном входе в систему он запускает B. B - TabActivity, который содержит три действия внутри него (представленные C, D, E). Каждый из C, D и E фактически является ActivityGroup, дочерние действия которого имитируют обычное поведение стека в действиях.

Таким образом, каждая вкладка содержит свой собственный стек действий и переключение между изменениями вкладок, которые из этих стеков в настоящее время выталкиваются/вывозятся из пользовательской навигации (каждая вкладка содержит стек ListActivities, просматривающий иерархическую структуру объектов). Они также могут запускать новые действия за пределами гигантской "В" TabActivity (представленной X).

Таким образом, при входе из Activity A, как минимум три действия создаются до того, как принимается больше пользовательского ввода: -B создается (и видно, из-за TabWidget сейчас в верхней части экрана) -Один из ActivityGroups создан: в зависимости от того, какая из них принадлежит вкладке по умолчанию. Однако этот ActivityGroup пока не отображается на экране, но он показывает только верхнюю активность его стека дочерних действий. -Так, наконец, создается "корневая" активность этого стека ActivityGroup (на рисунке F является примером такой активности). Эта активность отображается ниже TabWidget.

После того, как каждая вкладка была посещена один раз, больше никаких действий не создается/уничтожается путем переключения между вкладками (не считая убийств по памяти). Нажатие на любую вкладку завершает() действие в верхней части этого стека, показывая одно под ним. Нажатие назад из корневой активности (например, F) на любой вкладке завершает всю TabActivity, отправляя пользователя обратно в A.

Цель, переданная B, также дает указание ей автоматически перейти на другую вкладку, чем по умолчанию. В случае, когда мы заканчиваем стек [A → B → B], первый B перемещается на правильную вкладку, а второй - по умолчанию.

Ответ 1

TL; DR; Не используйте CLEAR_TOP с SINGLE_TOP одновременно

Если он производит ошибку только 5% времени, это может быть проблема concurrency. Вы сказали, что у вас есть SINGLE_TOP | CLEAR_TOP для вызова Activity B. CLEAR_TOP уничтожает текущий экземпляр Activity B, и намерение передается onCreate(). SINGLE_TOP не уничтожает текущий экземпляр Activity B и доставляет намерение onNewIntent().

Когда флаг SINGLE_TOP считывается первым, намерение доставляется к текущему экземпляру Activity B, вызывающему onNewIntent(). Затем считывается CLEAR_TOP и действие B уничтожается, а новый экземпляр создается с помощью onCreate(), и все работает нормально.

Когда сначала считывается CLEAR_TOP, существующий экземпляр Activity B уничтожается, а новый создается с помощью onCreate(). Затем считывается SINGLE_TOP, и цель также передается onNewIntent(). Опять же, это работает.

Когда CLEAR_TOP и SINGLE_TOP считываются одновременно, текущий экземпляр активности уничтожается, а CLEAR_TOP вызывает onCreate(), а SINGLE_TOP вызывает onCreate(), так как на данный момент не существует экземпляра Activity B. Таким образом, вы получаете A- > B- > B.