Фоновая задача, диалог выполнения, изменение ориентации-есть ли 100% рабочее решение?
Я загрузить некоторые данные из интернета в фоновом потоке (я использую AsyncTask) и отображать диалоговое окно прогресса во время загрузки. Ориентация меняется, активность перезапускается, а затем моя задача AsyncTask завершена - я хочу закрыть диалоговое окно Progress и начать новую активность. Но вызов метода dismissDialog иногда вызывает исключение (вероятно, потому, что действие было уничтожено, а новое действие еще не запущено).
каков наилучший способ справиться с такой проблемой (обновление пользовательского интерфейса из фонового потока, который работает, даже если пользователь меняет ориентацию)? Кто-то из Google предоставил какое-то "официальное решение"?
8 ответов:
Шаг № 1: Сделайте ваш
AsyncTaskastaticвложенный класс, или совершенно отдельный класс, только не внутренний (нестатический вложенный) класс.Шаг № 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 с диалогом прогресса!
- вращение работает, и диалог выживает.
- вы можете отменить задачу и диалог, нажав кнопку назад (если вы хотите, чтобы это поведение).
- он использует фрагменты.
- макет фрагмент под активностью изменяется правильно, когда устройство вращается.
я маялся в течение недели, чтобы найти решение этой дилеммы, не прибегая к редактированию файла манифеста. Предпосылки для этого решения являются:
- вам всегда нужно использовать диалог прогресса
- одновременно выполняется только одна задача
- вам нужно, чтобы задача сохранялась, когда телефон вращается, и диалоговое окно прогресса автоматически отклоняется.
реализация
вы необходимо скопировать два файла, найденные в нижней части этого сообщения в рабочую область. Просто убедитесь, что:
все
Activitys должен продлитьBaseActivityна
onCreate(),super.onCreate()должен быть вызван после инициализации всех членов, которые должны быть доступны вашимASyncTaskы. Кроме того, переопределить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; } }
кто-то из 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
в принципе действия:
- я использую
onSaveInstanceStateчтобы сохранить задачу, если она все еще обработка.- на
onCreateЯ получаю задачу, если она была сохранена.- на
onPauseЯ отброситьProgressDialogесли это показано.- на
onResumeЯProgressDialogЕсли задача все еще обработка.