Как обрабатывать AsyncTask onPostExecute при паузе, чтобы избежать исключения IllegalStateException
Я ценю многочисленные сообщения об AsyncTask при изменении вращения. У меня возникает следующая проблема при использовании библиотеки совместимости и попытка отклонить DialogFragment
в onPostExecute
.
У меня есть фрагмент, который запускает AsyncTask, который показывает прогресс DialogFragment
, а затем в onPostExecute
отклоняет диалог, а затем потенциально вызывает еще один DialogFragment
.
Если при отображении диалогового окна выполнения я помещаю приложение в фоновый режим, я получаю следующее для моего фрагмента:
1) onPause
2) onSaveInstanceState
3) onPostExecute
, в котором я пытаюсь отклонить и вызвать диалог.
Я получаю IllegalStateException
, потому что я пытаюсь эффективно совершить транзакцию, когда активность сохранила его состояние, и я это понимаю.
При повороте я предположил (возможно, неправильно), что я не получил бы onPostExecute
до тех пор, пока активность не будет воссоздана. Однако при установке приложения на задний план я предположил (определенно неправильно), что onPostExectute
не будет вызван, пока фрагмент/действие приостановлено.
Мой вопрос в том, является ли мое решение просто обнаружить в onPostExecute
, что фрагмент/действие приостановлено и просто выполнить то, что мне нужно сделать в onResume
вместо этого? Мне кажется несколько уродливым.
Заранее спасибо, peter.
Изменить 1
Необходимо поддерживать 2.1 и выше
Изменить 2
Я рассмотрел отображение диалога с помощью FragmentTransaction:add
и FragmentTransaction:commitAllowingStateLoss
, однако это не лишено проблем.
Ответы
Ответ 1
Если вам нужно синхронизировать свою задачу с жизненным циклом действия, я считаю, что Loaders - именно то, что вам нужно. В частности, вы должны использовать AsyncTaskLoader для выполнения задания. Итак, вместо того, чтобы запускать AsyncTask, вы запускаете свой загрузчик, а затем ждите ответа в прослушивателе. Если действие приостановлено, вы не получите обратный вызов, эта часть будет управляться для вас.
Существует другой способ справиться с этой задачей: использование фрагмента, который сохраняет свой экземпляр. Общая идея заключается в том, что вы создаете фрагмент без пользовательского интерфейса и вызываете setRetainInstance(true)
. У него есть задача, которая уведомляется о том, что деятельность доступна или нет. Если нет, поток задач приостанавливается до тех пор, пока активность не станет доступной.
Ответ 2
Другим способом достижения того, что вам требуется, является реализация класса PauseHandler, который я документировал в этом сообщении.
Затем в вашем методе onPostExecute вызовите sendMessage(), чтобы отправить сообщение в обработчик.
Когда ваше приложение возобновится, действие будет обработано.
Ответ 3
Вместо использования BroadcastReceiver я предпочитаю использовать библиотеки шин, такие как guava, otto или eventbus. Их производительность намного лучше, чем реализация широковещательного приемника.
Ответ 4
Я придумал решение этой проблемы без какого-либо серьезного решения:
Основная идея, как поддерживать progressdialog и aynctask описана в этом blogentry (конечно, я использовал AsyncTaskComplex-Version). Все кредиты идут к автору этого blogentry, я только добавил крошечную вещь:
Очевидно, я больше не использую showDialog(). Вместо этого я придерживаюсь DialogFragments.
Вторая настройка является важной, а также решает проблему с IllegalStateException:
Вместо того, чтобы указывать только asynctask в onRetainCustomNonConfigurationInstance(), что больше нет активности, я также делаю это в onPause(). И вместо того, чтобы указывать только asynctask в onCreate(), что есть новая активность, я также делаю это в onResume().
И вот вы, ваша AsyncTask не будет пытаться сообщить свою активность о его окончании, вызывая исключение IllegalStateException, когда действие не видно.
Если вы хотите увидеть больше кода вместо слов, оставьте комментарий.
/редактирование:
Sourcecode, чтобы показать мое решение, которое, по моему мнению, довольно приличное:)
public class MyActivity extends Activity {
private MyTask mTask;
@Override
protected void onCreate(Bundle pSavedInstanceState) {
super.onCreate(pSavedInstanceState);
setContentView(R.layout.editaccount);
Object retained = getLastCustomNonConfigurationInstance();
if ( retained instanceof NewContactFolderIdTask ) {
mTask = (MyTask) retained;
mTask.setActivity(this);
}
}
@Override
protected void onPause() {
if(mTask != null) {
mTask.setActivity(null);
}
super.onPause();
}
@Override
public Object onRetainCustomNonConfigurationInstance() {
if(mTask != null) {
mTask.setActivity(null);
return mTask;
}
return null;
}
@Override
protected void onResume() {
if(mTask != null) {
mTask.setActivity(this);
}
loadValues(); // or refreshListView or whatever you need to do
super.onResume();
}
public void onTaskCompleted() {
loadValues(); // or refreshListView or whatever you need to do
DialogFragment dialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(PROGRESS_DIALOG_FRAGMENT);
if(dialogFragment != null) {
dialogFragment.dismiss();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// app icon in Action Bar clicked; go home
Intent intent = new Intent(this, OXClient.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
case R.id.menu_refresh:
mTask = new MyTask(this);
mTask.execute();
break;
}
return super.onOptionsItemSelected(item);
}
private class NewContactFolderIdTask extends AsyncTask<Boolean, Integer, Bundle> {
private MyActivity mActivity;
private boolean mCompleted;
private NewContactFolderIdTask(MyActivity pActivity) {
this.mActivity = pActivity;
}
public void setActivity(MyActivity pActivity) {
this.mActivity = pActivity;
if(mCompleted) {
notifiyActivityTaskCompleted();
}
}
private void notifiyActivityTaskCompleted() {
if(mActivity != null) {
mActivity.onTaskCompleted();
}
}
@Override
protected Bundle doInBackground(Boolean... pBoolean) {
// Do your stuff, return result
}
@Override
protected void onPreExecute() {
DialogFragment newFragment = ProgressDialogFragment.newInstance();
newFragment.show(getSupportFragmentManager(), PROGRESS_DIALOG_FRAGMENT);
}
@Override
protected void onPostExecute(Bundle pResult) {
mCompleted = true;
notifiyActivityTaskCompleted();
}
}
}
Ответ 5
Вкл Как обрабатывать сообщения обработчика при приостановке действия/фрагмента. Я предлагаю другой подход с использованием BroadcastReceiver.
Я считаю его более элегантным, и он предлагает преимущества, которые вы можете вызывать код на своем базовом фрагменте извне в вашем приложении, и используя липкие трансляции, ваш вызов может быть "запомнен" и выполнен после возобновления фрагмента.