SINGLE_TOP | CLEAR_TOP, кажется, работает 95% времени. Почему 5%? - PullRequest
14 голосов
/ 30 марта 2011

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

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

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

A -> B -> X

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

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

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

Операция A, после идентификации намерения как отправляемого из уведомления (он может сделать это как в onCreate (), так и в onNewIntent ()), отправляет намерение в действие B. Это намерение содержит CLEAR_TOP и SINGLE_TOP. B имеет режим запуска = 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, просматривая иерархическую структуру объектов ). Они также могут начинать новые действия за пределами гигантской функции B TabActivity (представлена ​​X).

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

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

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

1 Ответ

14 голосов
/ 02 апреля 2011

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

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

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

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

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

...