Фоновая задача, диалог прогресса, изменение ориентации - есть ли 100% работающее решение? - PullRequest
233 голосов
/ 29 сентября 2010

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

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

Ответы [ 8 ]

335 голосов
/ 29 сентября 2010

Шаг # 1: Сделайте ваш AsyncTask вложенный класс 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, чтобы сообщить пользователю, если работа была выполнена. Вот сообщение в блоге с более подробной информацией об этом шаблоне.

13 голосов
/ 06 сентября 2012

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

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

  1. Вращение работает, и диалог выживает.
  2. Вы можете отменить задачу и диалог, нажав кнопку «Назад» (если вы хотите, чтобы это поведение).
  3. Используются фрагменты.
  4. Расположение фрагмента под действием изменяется должным образом, когда устройство вращается.
8 голосов
/ 08 июля 2011

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

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

Реализация

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

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

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

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

  4. См. Код ниже для примера статического внутреннего класса для создания ваших 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 голосов
/ 08 августа 2015

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

Да.

Решение - это скорее предложение архитектуры приложения, а не просто некоторый код .

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

Предложение объясняется в речи клиентских REST-приложений Android во время Google I / O 2010 Вирджила Добянского. Это 1 час, но это очень стоит посмотреть.

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

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

2 голосов
/ 30 марта 2013

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

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

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

Кредит полностью предоставляется Райану для выяснениявот этот.

1 голос
/ 26 сентября 2014

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

0 голосов
/ 06 марта 2015

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

В основном это следующие шаги:

  1. Я использую onSaveInstanceState, чтобы сохранить задачу, если она все еще обрабатывается.
  2. В onCreate Я получаю задание, если оно было сохранено.
  3. В onPause Я сбрасываю ProgressDialog, если оно отображается.
  4. В onResume Я показываю ProgressDialog если задача еще обрабатывается.
0 голосов
/ 01 июля 2011

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

...