Как обрабатывать AsyncTask onPostExecute при паузе, чтобы избежать исключения IllegalStateException

Я ценю многочисленные сообщения об изменении ротации AsyncTask. У меня возникла следующая проблема при использовании библиотеки совместимости и попытке отклонить 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, однако это не обошлось без проблем.


person PJL    schedule 03.11.2011    source источник


Ответы (5)


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

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

person Malcolm    schedule 03.11.2011
comment
Спасибо, нужно изучить это подробнее, хотя в моем случае это похоже на кувалду, чтобы взломать гайку, где простым решением была бы просто отсрочка задач пользовательского интерфейса во время паузы. - person PJL; 06.11.2011
comment
Я сейчас работаю над той же проблемой, и я нашел новое решение. Может быть, это поможет и вам. Проверьте это, я обновил свой ответ. - person Malcolm; 06.11.2011
comment
@Malcolm Малькольм, можете ли вы дать ссылку на любую документацию, в которой написано If the activity is paused, you won't get a callback? Я не смог найти его нигде в документации loaders. - person aandis; 12.05.2015
comment
@zack Насколько я знаю, в документации это явно не указано, но я посмотрел в исходный код, и вот как он себя ведет. Я бы все же проверил, возобновлено ли приложение. - person Malcolm; 12.05.2015

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

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

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

person quickdraw mcgraw    schedule 14.11.2011

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

person Oguz Ozcan    schedule 21.01.2016

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

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

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

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

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

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

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

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

}

person Christoph Haefner    schedule 08.11.2011
comment
Спасибо, вышеизложенное в порядке, однако, если вы поместите приложение в фоновый режим до того, как получите onPostExecute, я все равно ожидаю, что dialogFragment будет затемнен, и в этот момент вы увидите недопустимоеStateException, если я не упустил что-то очевидное. - person PJL; 08.11.2011
comment
Да, ты прав. Я также получаю исключение IllegalStateException, когда мой asyncTask завершается, когда действие/приложение находится в фоновом режиме. Такого еще не пробовал :/ - person Christoph Haefner; 08.11.2011
comment
Извините, что не внимательно прочитал ваши вопросы, полностью перечеркнул фоновую часть: / - person Christoph Haefner; 08.11.2011
comment
Я решил проблему, по крайней мере мои местные тесты были все положительные. Как я уже сказал: если вы хотите увидеть больше кода, просто оставьте комментарий. - person Christoph Haefner; 09.11.2011
comment
Определенно интересно посмотреть, что вы сделали. На данный момент я рассматриваю либо AsyncTaskLoader, либо просто откладываю функциональность onPostExecute до onResume - person PJL; 10.11.2011
comment
Добавлен исходный код. Я думаю, что AsyncTaskLoader, как вы сказали, что-то вроде кувалды, чтобы расколоть орех, а также псевдофрагмент без пользовательского интерфейса, правда? Просто не позволяйте AsyncTask вызывать вашу активность, пока она находится в фоновом режиме, и все готово. - person Christoph Haefner; 10.11.2011
comment
Спасибо, мне нужно проверить это, но я все еще немного смущен. Я получаю в onPause вы установили активность вашей асинхронной задачи на ноль? (код отсутствует). Все еще не понимаю, что мешает вам получить onPostExecute после onPause или почему оно должно появиться после onResume. - person PJL; 10.11.2011
comment
Я добавил реализацию onPause() и провел дополнительные тесты: My onPostExecute НЕ вызывается, когда я нажимаю кнопку HOME?!? - person Christoph Haefner; 10.11.2011
comment
Хорошо, спасибо, да, теперь можно посмотреть, как это работает. Хотя я не понимаю, почему ваш onPostExecute не может быть вызван после onPause, но в любом случае его функциональность onTaskCompleted не будет обрабатываться до onResume после установки действия. - person PJL; 11.11.2011

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

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

person dangel    schedule 28.09.2014