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:commitAllowingStateLoss
mais ce n'est pas sans problèmes.
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.
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.
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.
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();
}
}
}
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.