web-dev-qa-db-fra.com

Comment gérer AsyncTask onPostExecute en pause pour éviter IllegalStateException

J'apprécie les nombreuses publications concernant AsyncTask sur un changement de rotation. J'ai le problème suivant lorsque j'utilise la bibliothèque de compatibilité et que j'essaie de supprimer un DialogFragment dans onPostExecute.

J'ai un fragment qui se déclenche d'une AsyncTask qui affiche une progression DialogFragment, puis dans onPostExecute rejette la boîte de dialogue et puis jette potentiellement un autre DialogFragment.

Si lorsque la boîte de dialogue de progression est affichée, je mets l'application en arrière-plan, j'obtiens ce qui suit pour mon fragment:

1) onPause

2) onSaveInstanceState

3) onPostExecute dans lequel j'essaie de fermer et d'appeler une boîte de dialogue.

J'obtiens un IllegalStateException parce que j'essaie de valider efficacement une transaction lorsque l'activité a enregistré son état et je comprends cela.

Lors d'une rotation, j'ai supposé (peut-être à tort) que je n'obtiendrais pas de onPostExecute tant que l'activité n'aurait pas été recréée. Cependant, lors de la mise en arrière-plan de l'application, j'ai supposé (certainement de manière incorrecte) que le onPostExectute ne serait pas appelé pendant que le fragment/l'activité était en pause.

Ma question est la suivante: ma solution consiste-t-elle simplement à détecter dans onPostExecute que le fragment/l'activité est interrompu et à effectuer simplement ce que je dois faire dans onResume à la place? Cela me semble un peu moche.

Merci d'avance, Peter.

Modifier 1

Besoin de prendre en charge 2.1 et supérieur

Modifier 2

J'ai envisagé d'afficher la boîte de dialogue à l'aide de FragmentTransaction:add et FragmentTransaction:commitAllowingStateLossmais ce n'est pas sans problèmes.

37
PJL

Si vous devez synchroniser votre tâche avec le cycle de vie de l'activité, je crois que Loaders sont exactement ce dont vous avez besoin. Plus précisément, vous devez utiliser AsyncTaskLoader pour faire le travail. Alors maintenant, au lieu d'exécuter une AsyncTask, vous lancez votre chargeur, puis attendez la réponse dans un écouteur. Si l'activité est suspendue, vous n'aurez pas de rappel, cette partie sera gérée pour vous.

Il existe une autre façon de gérer cette tâche: utiliser un fragment qui conserve son instance . L'idée générale est que vous créez un fragment sans interface utilisateur et appelez setRetainInstance(true). Il a une tâche qui est notifiée de l'activité disponible ou non. Sinon, le thread de la tâche se suspend jusqu'à ce qu'une activité soit disponible.

16
Malcolm

Une autre façon de réaliser ce dont vous avez besoin est d'implémenter la classe PauseHandler que j'ai documentée dans cet article .

Ensuite, dans votre méthode onPostExecute, appelez sendMessage () pour publier votre message dans le gestionnaire.

Lorsque votre application reprendra, l'action sera traitée.

8
quickdraw mcgraw

Plutôt que d'utiliser BroadcastReceiver, je préfère utiliser des bibliothèques de bus comme goyave, otto ou eventbus. Leurs performances sont bien meilleures que la mise en œuvre du récepteur de diffusion.

3
Oguz Ozcan

J'ai trouvé une solution à ce problème sans aucune solution de contournement majeure: L'idée de base pour maintenir un dialogue de progression et un asynctask est décrite dans ce blogentry (bien sûr, j'ai utilisé la version AsyncTaskComplex). Tous les crédits vont à l'auteur de ce blog, j'ai seulement ajouté une petite chose:

Évidemment, je n'utilise plus showDialog (). Au lieu de cela, je m'en tiens à DialogFragments.

Le deuxième Tweak est l'important et résout également le problème avec IllegalStateException:

Au lieu de dire uniquement à l'asynctask dans onRetainCustomNonConfigurationInstance () qu'il n'y a plus d'activité, je le fais aussi dans onPause (). Et au lieu de simplement dire à l'asynctask dans onCreate () qu'il y a une nouvelle activité, je le fais aussi dans onResume ().

Et voilà, votre AsyncTask n'essaiera pas d'informer votre activité de sa fin provoquant une IllegalStateException lorsque l'activité n'est pas visible.

Si vous souhaitez voir plus de code au lieu de mots, laissez un commentaire.

/edit: Sourcecode pour montrer ma solution, qui je pense est assez décente :)

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

}

2

On Comment gérer les messages du gestionnaire lorsque l'activité/le fragment est en pause J'offre une autre approche en utilisant un BroadcastReceiver.

Je le considère plus propre et plus élégant et il offre les avantages que vous pouvez invoquer du code sur votre fragment de base de partout dans votre application et en utilisant des diffusions collantes, votre invocation peut être "mémorisée" et exécutée après la reprise de votre fragment.

2
dangel