Как обрабатывать AsyncTask onPostExecute при паузе, чтобы избежать IllegalStateException - PullRequest
36 голосов
/ 03 ноября 2011

Я ценю многочисленные сообщения об AsyncTask об изменении ротации. У меня есть следующая проблема при использовании lib совместимости и пытается отклонить DialogFragment в onPostExecute.

У меня есть фрагмент, который запускает AsyncTask, который отображает прогресс DialogFragment, затем в onPostExecute закрывает диалоговое окно и затем потенциально подбрасывает еще один DialogFragment.

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

1) onPause

2) onSaveInstanceState

3) onPostExecute, в котором я пытаюсь закрыть и вызвать диалог.

Я получаю IllegalStateException, потому что я пытаюсь эффективно зафиксировать транзакцию, когда активность сохранила свое состояние, и я понимаю это.

В ротации я предположил (возможно, неправильно), что я не получу onPostExecute, пока действие не будет воссоздано. Однако, помещая приложение в фоновый режим, я предположил (определенно неправильно), что onPostExectute не будет вызван, пока фрагмент / действие приостановлено.

Мой вопрос заключается в том, является ли мое решение просто обнаружить в onPostExecute, что фрагмент / действие приостановлено, и просто выполнить вместо этого то, что мне нужно сделать в onResume? Мне немного некрасиво.

Заранее спасибо, Питер.

Редактировать 1

Требуется поддержка 2.1 и выше

Редактировать 2

Я рассмотрел показ диалога, используя FragmentTransaction:add и FragmentTransaction:commitAllowingStateLoss, но это не без проблем.

Ответы [ 5 ]

15 голосов
/ 03 ноября 2011

Если вам нужно синхронизировать вашу задачу с жизненным циклом активности, я считаю, что Loaders - это именно то, что вам нужно.В частности, вы должны использовать AsyncTaskLoader для выполнения этой работы.Так что теперь вместо запуска AsyncTask вы запускаете загрузчик, а затем ждете ответа в слушателе.Если действие приостановлено, вы не получите обратный вызов, эта часть будет управляться за вас.

Существует еще один способ решения этой задачи: использование фрагмента, который сохраняет свой экземпляр ,Общая идея заключается в том, что вы создаете фрагмент без интерфейса и вызываете setRetainInstance(true).У него есть задача, которая уведомляется о том, доступна ли операция или нет.Если нет, поток задачи приостанавливается, пока действие не станет доступным.

8 голосов
/ 14 ноября 2011

Еще один способ добиться того, что вам нужно, - реализовать класс PauseHandler, который я задокументировал в этом посте .

Затем в вашем методе onPostExecute вызовите sendMessage () для отправки вашего сообщения в обработчик.

Когда ваше приложение возобновит работу, действие будет обработано.

3 голосов
/ 21 января 2016

Вместо использования BroadcastReceiver, я предпочитаю использовать библиотеки шин, такие как guava, otto или eventbus. Их производительность намного лучше, чем у вещательного приемника.

2 голосов
/ 28 сентября 2014

Вкл. Как обрабатывать сообщения обработчика, когда действие / фрагмент приостановлен Я предлагаю другой подход с использованием BroadcastReceiver.

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

2 голосов
/ 08 ноября 2011

Я нашел решение этой проблемы без каких-либо серьезных обходных путей: Основная идея о том, как поддерживать ProgressDialog и Asynctask, описана в этом blogentry (конечно, я использовал AsyncTaskComplex-Version). Все кредиты принадлежат автору этого блогентства, я только добавил крошечную вещь:

Очевидно, я больше не использую showDialog (). Вместо этого я придерживаюсь DialogFragments.

Второй твик является важным и также решает проблему с IllegalStateException:

Вместо того, чтобы только сообщать асинхронной задаче в onRetainCustomNonConfigurationInstance (), что больше нет активности, я также делаю это в onPause (). И вместо того, чтобы только сообщать асинхронной задаче в onCreate (), что есть новое действие, я также делаю это в onResume ().

И вот, ваш AsyncTask не будет пытаться сообщить вашей активности о своем завершении, вызвав исключение IllegalStateException, когда действие не видно.

Если вы хотите увидеть больше кода вместо слов, оставьте комментарий.

/ редактирования: Исходный код, чтобы показать мое решение, которое я считаю довольно приличным:)

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();
    }
}

}

...