Подтвердить что ты не робот

Неисправность Backstack активности при разрушении деятельности

У меня есть два вида деятельности; скажем, A и B. В activity A есть зарегистрированный вещательный приемник, который прослушивает конкретное событие, которое завершит активность A. Я регистрирую широковещательный приемник в onCreate() и уничтожаю его в onDestroy() activity A.

Для простоты существует button в activity B с именем "Destroy Activity A". Когда пользователь нажимает button, activity A должен быть уничтожен.

Обычно все это работает бесперебойно без каких-либо проблем, но проблема возникает в следующих сценариях:

1) Предположим, что я нахожусь в activity B, и я нажимаю клавишу "Домой", чтобы переместить приложение на задний план, а затем, если я использую другие ресурсоемкие приложения, система Android убьет мое приложение на свободную память. Затем, если я открою приложение из недавних задач, activity B будет возобновлено, и будет вызываться метод onCreate(), onResume() etc. Теперь я нажимаю button, чтобы уничтожить activity A, но активность A уже была уничтожена, поэтому методы activity A onCreate(), onResume() etc не будут вызываться до тех пор, пока я не перейду к activity A, нажав кнопку back button. Таким образом, broadcast receiver не зарегистрирован для прослушивания события.

2) Такая же проблема возникает, когда пользователь выбрал "Не сохранять действия" из параметров разработчика в настройках устройства.

Я давно искал эту проблему, но я не могу найти правильный ответ. Каков наилучший способ справиться с этим сценарием? Это ошибка Android? Должно быть какое-то решение для этой проблемы.

Пожалуйста, помогите мне.

4b9b3361

Ответ 1

Это не может быть исправлено при сохранении вашей текущей широковещательной логики.

Убивание активности из backstack, imo, не является правильным подходом. Вы должны решительно рассмотреть возможность изменения логики вашей навигации.

Но если ваш проект большой, а время - проблема, и рефакторинг не может быть и речи, A.J. но вы упомянули, что у вас есть много действий, которые нужно убить, его решение становится очень сложным для реализации.

Я предлагаю следующее. Это может быть не лучшая идея, но я не могу думать о другом. Может быть, это может помочь.

У вас должно быть следующее:

  • Базовая активность для всех ваших действий.
  • A ArrayList<String> activitiesToKill объект на уровне приложения. (Если вы не расширили Application, вы можете использовать его как статическую переменную

Сначала мы должны убедиться, что activitiesToKill не потерян, когда ОС убивает приложение в низкой памяти. В BaseActivity мы сохраняем список во время onSaveInstanceState и восстанавливаем его в onRestoreInstanceState

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("activitiesToKill", activitiesToKill);
}

private void onRestoreInstanceState(Bundle state) {
    if (state != null) {
        activitiesToKill = (ArrayList<String>) state.getSerializable("activitiesToKill");
    super.onRestoreInstanceState(state); 
}

}

Идея здесь заключается в том, чтобы сохранить, какие действия должны быть убиты в списке, используя их имя.

Логика следующая:

Скажем, у вас есть действия A, B, C, D и E

В действии E вы нажмете кнопку, и вы хотите убить B и D

Когда вы нажимаете кнопку в E, вы добавляете имена B и D в объект activitiesToKill.

activitiesToKill.add(B.class.getSimpleName()
activitiesToKill.add(D.class.getSimpleName()

В методе onCreate BaseActivity мы должны проверить, что

if(savedInstanceState != null)
{
    //The activity is being restored. We check if the it is in the lest to Kill and we finish it                
    if(activitiesToKill.contains(this.getClass().getSimpleName()))
    {
        activitiesToKill.remove(this.getClass().getSimpleName())
        finish();
    }
}

Обязательно удалите имя операции, если оно было убито через трансляцию.

Итак, в основном это то, что происходит в каждом сценарии.

Если приложение работает нормально, и вы нажимаете кнопку, трансляция отправляется, а B и D будут убиты. Обязательно удалите B и D из activitiesToKill

Если приложение было убито и восстановлено, вы нажмете кнопку, трансляция не будет иметь никакого эффекта, но вы добавили B и D в объект activitiesToKill. Поэтому, когда вы нажимаете "назад", действие создается, а файл savedInstanceState не равен null, действие завершено.

Этот подход предполагает, что активность E знает, какие действия он должен убить.

Если вы НЕ знаете, какие действия нужно убить из E, вам нужно немного изменить эту логику:

Вместо использования ArrayList используйте HashMap<String, bool>

Когда будет создана активность B, она зарегистрирует ее самостоятельно в hashmap:

activitiesToKill.put(this.class.getSimpleName(), false)

Затем из действия E все, что вам нужно сделать, - установить все записи в true

Затем в процессе создания базового действия вы должны проверить, зарегистрировано ли это действие в ActivityToKill (хэш-карта содержит ключ) И логическое значение true вы его убиваете (не забудьте вернуть его в false или удалить ключ)

Это гарантирует, что каждая активность регистрируется на HashMap и в действии E, но не знает всех действий, которые нужно убить. И не забудьте удалить их, если трансляция убивает их.

Этот подход также гарантирует, что активность не будет убита при обычном открытии с намерением, поскольку в этом случае onSaveInstanceState будет иметь значение null в onCreate, поэтому ничего не произойдет.

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

Также обратите внимание, что вы можете использовать getName вместо getSimpleName, если у вас есть несколько действий с тем же именем, но с разными пучками.

Надеюсь, мои объяснения достаточно ясны, поскольку я написал это с моей головы, дайте мне знать, если какая-либо область не ясна.

Желаем удачи

Ответ 2

Если ваш Activity A был уничтожен самой ОС Android, тогда есть нет возможности отслеживать.

Некоторые люди предложили отслеживать это Activity A, перечислив событие в методе onDestroy, но если ваш Activity убит системной ОС, то обратите внимание, что он не будет называть этот метод.

Ответ 3

Я не знаю, можно ли это обрабатывать "правильным" способом.

То, что мне кажется, - это каким-то образом обозначить действие A. Вы не можете использовать startActivityForResult(), потому что вы получите результат до того, как onResume() будет вызван i.e. UI уже завышен.

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

Вам нужно будет проверить этот флаг на вашем методе onCreate() перед вызовом setContentView(), если флаг является истинным, просто закончите действие.

Ответ 4

С предоставленной вами информацией, как насчет того, как вы зарегистрируете трансляцию в onCreate of Activity B после проверки, зарегистрирована ли она уже или нет. Если onDestroy Activity A был вызван в любом из описанных вами сценариев, тогда был бы вызван дерегистр Broadcast. Таким образом, в этом случае вы можете зарегистрировать свою трансляцию в onCreate of Activity B, чтобы вы могли ее слушать, даже если у вас есть только активность B в вашей задней части.

Ответ 5

Рассматривали ли вы использование липкой трансляции? Также вы можете зарегистрировать приемник на уровне приложения (в манифесте) и прослушать это событие независимо от состояния Activity A.

Но, как уже говорилось, Youssef, убийство действий из задней части стопы является неправильным подходом. Вы должны решительно рассмотреть возможность изменения логики вашей навигации.

Ответ 6

Одно из основных правил с Activities - , вы не можете полагаться на активность, за исключением активности переднего плана. То, что вы пытаетесь сделать с трансляциями, не имеет никакого отношения к обратному стеку, который не гарантирует, что все действия будут активны в любое время, но он будет уверен, что они будут воссозданы, когда придет время перейти на передний план.

В вашем примере (если я понимаю, что вы пытаетесь сделать) вам нужно перейти к чему-то под A - скажем, Activity Z, и стек выглядит следующим образом: Z-A-[B]. Там нормальный ход событий, когда вы нажимаете back, и он переносит вас на A, затем после другого нажатия - до Z, но в определенном случае (например, нажав кнопку) вы хотите вернуться к Z в обход A - это классический случай использования FLAG_ACTIVITY_CLEAR_TOP и запускать Z явно:

Intent intent = new Intent(this, ActivityZ.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

Это закончит как B, так и A, и доведите намерение до Z. Вероятно, вам также понадобится флаг FLAG_ACTIVITY_SINGLE_TOP, обратите пристальное внимание на описание FLAG_ACTIVITY_CLEAR_TOP, там есть некоторые обманки, которые вы должны рассмотреть.

Ответ 7

Многие решения пришли мне на ум, но поскольку вы не предоставили много информации о своем приложении, я думаю, что это должно работать в целом.

Вместо того, чтобы запускать трансляцию, чтобы убить Activity A, просто выполните следующий код, когда кнопка "Kill Activity A" нажата в Activity B.

        Intent intent = new Intent(getApplicationContext(),
                ActivityA.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        intent.putExtra("EXIT", true);
        startActivity(intent);

Добавьте следующий код в действие A

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (intent.getBooleanExtra("EXIT", false)) {
        finish();
    }
}

protected void onCreate(Bundle savedInstanceState) {
    //Ideally, there should not be anything before this
    super.onCreate(savedInstanceState);
    if(getIntent().getBooleanExtra("EXIT", false)){
        finish();
        return;
    }

В манифесте установлен режим запуска "singleTop" для активности A.

<activity
    android:name=".ActivityA"
    ...
    android:launchMode="singleTop" />

Это будет иметь следующие последствия:

  • Если операция A уже запущена, она будет перенесена в начало стека действий и завершится, удалив ее из стека.
  • Если Activity A был уничтожен, но все еще присутствует в стеке действий (который будет запущен при нажатии кнопки "Назад" ), он будет запущен, доведен до конца и закончен, тем самым удалив его из стека действий.
  • Если операция A уже была уничтожена и не присутствует в стеке действий, и вы все равно нажимаете кнопку "Удалить активность", она будет запущена, доведена до конца и завершена.

Как правило, вы не должны видеть мерцания.

Основываясь на этой идее, вы можете создать более эффективное решение для своего конкретного приложения. Например, вы можете использовать FLAG_ACTIVITY_CLEAR_TOP и завершить операцию A в onBackPressed() для действия B.

Ответ 8

1) Вы можете хранить информацию об активности для уничтожения в каком-либо статическом классе. Если может быть мало случаев, вы можете сохранить массив значений int, например:

static final int ACTIVITY_DESTROY_FLAG_ACTIVITY_A = 1
static final int ACTIVITY_DESTROY_FLAG_ACTIVITY_B = 2

В onResume активности проверьте, следует ли ее уничтожить. Для Activity_A он может выглядеть так:

if (StaticClass.activityArray.contains(ACTIVITY_DESTROY_FLAG_ACTIVITY_A)) {
    finish();
}

2) Заблаговременно к вашему механизму приемников трансляции вы можете поместить активность в Intent на intent.putExtra("activity", Activity_A.this) и отправить ее с трансляцией из onCreate из Activity_A. В Activity_B зарегистрируйте широковещательный приемник и в кнопках onClick вызовите ((Activity) intent.getExtras().get("activity")).finish(). Ну, еще один приемник - вы потратите на это ресурсы?