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


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

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

8 229

8 ответов:

Шаг № 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 чтобы пользователь знал, если работа была проделана. вот сообщение в блоге С более по этой схеме.

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

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

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

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

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

реализация

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

  1. все Activitys должен продлить BaseActivity

  2. на onCreate(),super.onCreate() должен быть вызван после инициализации всех членов, которые должны быть доступны вашим ASyncTaskы. Кроме того, переопределить 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;
    }
}

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

да.

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

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

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

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

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

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

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

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

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

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

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

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

в принципе действия:

  1. я использую onSaveInstanceState чтобы сохранить задачу, если она все еще обработка.
  2. на onCreate Я получаю задачу, если она была сохранена.
  3. на onPause Я отбросить ProgressDialog если это показано.
  4. на onResume Я ProgressDialog Если задача все еще обработка.