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

Как вы можете справиться с увольнением DialogFragment (совместимость lib) после завершения AsyncTask

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

Вот проблема, если у меня есть работа AsyncTask, которая должна отклонить DialogFragment в onPostExecute(), я получаю исключение IllegalStateException, если приложение находится в фоновом режиме при попытке отклонить DialogFragment.

private static class SomeTask extends AsyncTask<Void, Void, Boolean> {

    public SomeTask(SomeActivity tActivity)
    {
        mActivity = tActivity;
    }

    private SomeActivity mActivity;

    /** Set the view during/after config change */
    public void setView(Activity tActivity) {
        mActivity tActivity;
    }

    @Override
    protected Boolean doInBackground(Void... tParams) {
        try {
          //simulate some time consuming process
          TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException ignore) {}
        return true;
    }

    @Override
    protected void onPostExecute(Boolean tRouteFound) {
        mActivity.dismissSomeDialog();  
    }

}

Действие выглядит следующим образом:

import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;

public class SomeActivity extends FragmentActivity {

    public void someMethod() {
        ...
        displaySomeDialog();
        new SomeTask(this).execute();
        ...
    }

    public void displaySomeDialog() {
        DialogFragment someDialog = new SomeDialogFragment();
        someDialog.show(getFragmentManager(), "dialog");
    }

    public void dismissSomeDialog() {
        SomeDialogFragment someDialog = (SomeDialogFragment) getFragmentManager().findFragmentByTag("dialog");
        someDialog.dismiss();
    }

    ....

}

Работает отлично, ЕСЛИ приложение не переключается на фон, а SomeTask все еще работает. В этом случае, когда SomeTask пытается отклонитьSomeDialog(), я получаю исключение IllegalStateException.

05-25 16:36:02.237: E/AndroidRuntime(965): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

Все сообщения, которые я видел, похоже, указывают на какое-то смелое направление с продуманными обходными решениями. Не существует ли какой-либо андроид? Если бы это был Dialog вместо DialogFragment, то Activity dispDialog() обработал бы его правильно. Если бы это был реальный DialogFragment, а не один из ACP, тогда функция offsetAllowingStateLoss() обработала бы его. Разве нет что-то подобное для версии ACP DialogFragment?

4b9b3361

Ответ 1

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

getFragmentManager().beginTransaction().remove(someDialog).commitAllowingStateLoss();

Это должно решить проблему без взломанного кода. То же самое можно применить и для показа, если у вас есть потоки, связывающие обработчик с потоком пользовательского интерфейса, используя dialog.show(); Который может вызвать также незаконное исключение государства

getFragmentManager().beginTransaction().add(someDialog).commitAllowingStateLoss();


@joneswah является правильным, учитывая вопрос с плакатами. Если вы используете библиотеку поддержки, замените
getFragmentManager()

с

getSupportFragmentManager()


Для будущих гуглеров: @Alex Lockwood поднимает хорошие и актуальные проблемы с этим решением. Решение действительно устраняет ошибку и будет работать в большинстве случаев, но подсказывает, что проблемы с подходом в исходном вопросе возникают с точки зрения UX.

В Activity должно быть указано, что задача async может не завершиться и что она не будет выполнять onPostExecute(). Независимо от того, какое действие UI (т.е. Прядильщик, в идеале не диалоговое окно) запускается, чтобы уведомить пользователя о работе async, должны иметь положения, которые автоматически останавливаются либо на тайм-аут, либо на отслеживание состояния и проверку событий onRestore/onResume, чтобы обеспечить Пользовательский интерфейс обновляется должным образом. Услуги также заслуживают изучения.

Ответ 2

Фрагменты сохраняются как часть каждого состояния Activity, поэтому выполнение транзакций после onSaveInstanceState() было вызвано технически, не имеет смысла.

Вы определенно не хотите использовать commitAllowingStateLoss(), чтобы избежать исключения в этом случае. Рассмотрим этот сценарий как пример:

  • Действие выполняет AsyncTask. AsyncTask показывает DialogFragment в onPreExecute() и начинает выполнение своей задачи в фоновом потоке.
  • Пользователь нажимает "Главная", а Activity останавливается и принудительно в фоновом режиме. Система решает, что устройство довольно низко в памяти, поэтому оно решает, что оно также должно также уничтожить Activity.
  • Выполняется AsyncTask и вызывается onPostExecute(). Внутри onPostExecute() вы отклоняете DialogFragment с помощью commitAllowingStateLoss(), чтобы избежать исключения.
  • Пользователь переходит к Activity. FragmentManager восстановит состояние своих фрагментов на основе сохраненного состояния Activity. Сохраненное состояние ничего не знает после того, как onSaveInstanceState() был вызван, поэтому запрос на отклонение DialogFragment не будет сохранен, и DialogFragment будет восстановлен, даже если AsyncTask уже завершен.

Из-за таких странных ошибок, которые могут возникать иногда, обычно не рекомендуется использовать commitAllowingStateLoss(), чтобы избежать этого исключения. Поскольку методы обратного вызова AsyncTask (которые вызываются в ответ на фоновый поток, заканчивающий его работу), не имеют абсолютно никакого отношения к методам жизненного цикла Activity (которые вызываются процессом системного сервера в ответ на общесистемные внешние события, например, устройство, засыпающее или работающее на низком уровне), обработка этих ситуаций требует от вас немного дополнительной работы. Конечно, эти ошибки крайне редки, и защита вашего приложения от них часто не будет разницей между рейтингом 1 звезды и 5-звездочным рейтингом в магазине воспроизведения... но это все еще нужно знать.

Надеюсь, это сделало хоть какой-то смысл. Также обратите внимание, что Dialog также существует как часть состояния Activity, поэтому, хотя использование простого старого Dialog может исключить исключение, вы, по существу, должны иметь одинаковую проблему (т.е. отклонение Dialog не будет запоминается, когда состояние Activity будет восстановлено позже).

Чтобы быть откровенным, лучшим решением было бы избежать отображения диалога на протяжении всего периода AsyncTask. Более удобное для пользователя решение - показать неопределенный прокрутка прогресса в ActionBar (например, приложения G + и Gmail). Причинение значительных сдвигов в пользовательском интерфейсе в ответ на асинхронные обратные вызовы плохо для пользователя, потому что оно неожиданно и резко выводит пользователя из того, что они делают.

Для получения дополнительной информации см. эту запись в блоге.

Ответ 3

Вы должны отменить свою AsyncTask в onPause(), если onPostExecute() будет обновлять пользовательский интерфейс. Вы не должны пытаться обновлять интерфейс, пока ваша деятельность приостановлена.

Eg. в вашем onPause():

if (task != null) task.cancel(true);

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

Если вы не хотите, чтобы изменения из задачи сохранялись, не сохраняйте изменения до тех пор, пока onPostExecute()

Ответ 4

Когда Android останавливает ваше приложение, потому что пользователь нажимает кнопку "Назад" или "Дом", ваши диалоги закрываются для вас. Обычно трюк заключается в сохранении диалогов между onStop()/onStart(). Поэтому, если вам не нужно делать больше, чем просто закрыть диалог, я бы сказал, не беспокойтесь об этом.

РЕДАКТИРОВАТЬ: В вашей деятельности, в которой размещается диалог, вы все равно можете закрыть диалоговое окно, если оно все еще открыто внутри onStop(). Это помогает предотвратить утечку памяти. Но это не нужно запускать из AsyncTask.

Как я уже сказал выше, проблема в том, что происходит, когда вы снова нажимаете onStart(), и ваша AsyncTask еще не закончена. Вам нужно будет выяснить способ определения и повторного открытия этого диалога, если это необходимо.

Ответ 5

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

В итоге, мне пришлось сделать следующее:

  • onSaveInstanceState() - если запущена SomeTask, я использую простой замок, чтобы заблокировать SomeTask от выхода doInBackground() во время паузы.
  • onResume() - мне пришлось включить оператор switch для обработки различных ситуаций возобновления. При запуске приложения я ничего не делаю, поскольку ничего не приостанавливается, если перезагрузка после скрытия или после изменения конфигурации я освобождаю блокировку, чтобы сохранившийся экземпляр SomeTask мог возобновиться там, где он был остановлен, и т.д.
  • onDestroy() - Я отменяю SomeTask, если он запущен.

Я поставлю фрагменты кода для этого решения в своем исходном сообщении.

Ответ 6

getActivity().finish(); в DialogFragment работал у меня.