Фоновая задача, диалог прогресса, изменение ориентации - есть ли 100% рабочее решение?

Я загружаю некоторые данные из Интернета в фоновый поток (я использую AsyncTask) и отображаю диалог выполнения при загрузке. Изменения ориентации, активность перезапускается, а затем моя AsyncTask завершена. Я хочу отклонить диалог progess и начать новое действие. Но вызов функции dispDialog иногда вызывает исключение (вероятно, из-за того, что действие было уничтожено, а новая активность еще не запущена).

Каков наилучший способ справиться с такой проблемой (обновление пользовательского интерфейса из фонового потока, который работает, даже если пользователь меняет ориентацию)? Кто-то из Google предоставил "официальное решение"?

Ответ 1

Шаг # 1. Создайте свой AsyncTask a static вложенный класс или полностью отдельный класс, а не внутренний (нестатический вложенный) класс.

Шаг № 2: Удерживайте AsyncTask в элементе Activity через элемент данных, заданный через конструктор и сеттер.

Шаг №3: При создании AsyncTask поставьте текущий Activity в конструктор.

Шаг 4: В onRetainNonConfigurationInstance() верните AsyncTask, после отсоединения его от исходной, текущей активности.

Шаг № 5: В onCreate(), если getLastNonConfigurationInstance() не null, переведите его в класс AsyncTask и вызовите ваш сеттер, чтобы связать ваше новое действие с задачей.

Шаг # 6: не ссылайтесь на элемент данных активности из doInBackground().

Если вы будете следовать приведенному выше рецепту, все будет работать. onProgressUpdate() и onPostExecute() приостановлены между началом onRetainNonConfigurationInstance() и концом последующего onCreate().

Вот пример проекта, демонстрирующий технику.

Другой подход состоит в том, чтобы вырезать AsyncTask и переместить вашу работу в IntentService. Это особенно полезно, если работа, которая должна быть выполнена, может быть длинной и должна продолжаться независимо от того, что пользователь делает с точки зрения действий (например, загружает большой файл). Вы можете использовать упорядоченную широковещательную рассылку Intent для того, чтобы либо реакция реагировала на выполняемую работу (если она все еще находится на переднем плане), либо поднять Notification, чтобы сообщить пользователю, была ли выполнена работа. Вот сообщение в блоге с более подробным описанием этого шаблона.

Ответ 2

Принятый ответ был очень полезен, но у него нет диалогового окна прогресса.

К счастью для вас, читатель, я создал чрезвычайно подробный и рабочий пример AsyncTask с диалоговом окне прогресса!

  • Вращение работает, и диалог сохраняется.
  • Вы можете отменить задачу и диалог, нажав кнопку "Назад" (если вам нужно это поведение).
  • Он использует фрагменты.
  • Макет фрагмента под действием изменяется правильно, когда устройство вращается.

Ответ 3

Я неделю работаю, чтобы найти решение этой дилеммы, не прибегая к редактированию файла манифеста. Предположениями для этого решения являются:

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

Реализация

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

  • Все ваши Activity должны расширять BaseActivity

  • В onCreate(), super.onCreate() следует вызывать после инициализации всех членов, к которым нужно обращаться с помощью ASyncTask s. Кроме того, переопределите getContentViewId(), чтобы предоставить идентификатор формы.

  • Переопределить onCreateDialog() как обычно для создания диалогов, управляемых действием.

  • См. приведенный ниже код для образца статического внутреннего класса для создания AsyncTasks. Вы можете сохранить свой результат в mResult для доступа позже.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

И, наконец, для запуска вашей новой задачи:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

Что это! Я надеюсь, что это надежное решение поможет кому-то.

BaseActivity.java (организуйте импорт самостоятельно)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}

Ответ 4

Кто-нибудь из Google предоставил какое-то "официальное решение"?

Да.

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

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

Предложение объясняется в отзывах клиентов Android REST во время Google I/O 2010 Вергилия Добжански. Он длится 1 час, но он заслуживает особого внимания.

В основе этого лежит абстрагирование сетевых операций с Service, которое работает независимо от любого Activity в приложении. Если вы работаете с базами данных, использование ContentResolver и Cursor даст вам готовый шаблон наблюдателя, который удобно обновлять интерфейс без какой-либо дополнительной логики, один раз вы обновили свою локальную базу данных с помощью извлеченных удаленных данных. Любой другой код после операции будет выполняться через обратный вызов, переданный в Service (для этого я использую подкласс ResultReceiver).

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

Ответ 5

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

Вы можете обрабатывать изменения ориентации и редкие разрушенные события Activity, используя объект Application для ссылки на вашу ASyncTask.

Здесь есть отличное объяснение проблемы и решение здесь:

Кредит полностью уходит Райану за то, что он понял это.

Ответ 6

вы должны вызывать все действия, используя обработчик активности. Поэтому, если вы находитесь в каком-то потоке, вы должны создать Runnable и опубликовать с помощью обработчика Activitie. В противном случае ваше приложение будет иногда сбой с фатальным исключением.

Ответ 7

Спустя 4 года Google решила проблему, просто вызвав setRetainInstance (true) в Activity onCreate. Он сохранит ваш экземпляр активности во время вращения устройства. У меня также есть простое решение для старых Android.

Ответ 8

Это мое решение: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

В основном этапы:

  • Я использую onSaveInstanceState, чтобы сохранить задачу, если она еще обработка.
  • В onCreate я получаю задачу, если она была сохранена.
  • В onPause я отбрасываю ProgressDialog, если это показано.
  • В onResume я показываю ProgressDialog, если задача по-прежнему обработка.