J'ai beaucoup lu sur la manière de sauvegarder l'état de mon instance ou de gérer la destruction de mon activité lors de la rotation de l'écran.
Il semble y avoir beaucoup de possibilités, mais je n'ai pas trouvé celle qui fonctionne le mieux pour récupérer les résultats d'une AsyncTask.
J'ai quelques AsyncTasks qui sont simplement redémarrées et appellent la isFinishing()
méthode de l'activité et si l'activité se termine, elles ne mettront rien à jour.
Le problème est que j'ai une tâche qui fait une demande à un service Web qui peut échouer ou réussir et le redémarrage de la tâche entraînerait une perte financière pour l'utilisateur.
Comment résoudriez-vous cela? Quels sont les avantages ou les inconvénients des solutions possibles?
setRetainInstance(true)
fait réellement utile.Réponses:
Ma première suggestion serait de vous assurer que vous avez réellement besoin que votre activité soit réinitialisée lors d'une rotation d'écran (le comportement par défaut). Chaque fois que j'ai eu des problèmes de rotation, j'ai ajouté cet attribut à ma
<activity>
balise dans le fichier AndroidManifest.xml, et tout s'est bien passé.android:configChanges="keyboardHidden|orientation"
Cela a l'air bizarre, mais ce qu'il fait est transféré à votre
onConfigurationChanged()
méthode, si vous n'en fournissez pas une, cela ne fait rien d'autre que de re-mesurer la mise en page, ce qui semble être un moyen parfaitement adéquat de gérer la rotation la plupart du temps. .la source
Vous pouvez découvrir comment je gère
AsyncTask
les changements d'orientation et les changements d'orientation sur code.google.com/p/shelves . Il existe différentes façons de le faire, celle que j'ai choisie dans cette application est d'annuler toute tâche en cours d'exécution, d'enregistrer son état et d'en démarrer une nouvelle avec l'état enregistré lorsque la nouvelleActivity
est créée. C'est facile à faire, cela fonctionne bien et en prime, il s'occupe d'arrêter vos tâches lorsque l'utilisateur quitte l'application.Vous pouvez également utiliser
onRetainNonConfigurationInstance()
pour passer votreAsyncTask
au nouveauActivity
( faites attention à ne pas fuir le précédent deActivity
cette façon.)la source
C'est la question la plus intéressante que j'ai vue concernant Android !!! En fait, j'ai déjà cherché la solution ces derniers mois. Toujours pas résolu.
Soyez prudent, remplacez simplement le
android:configChanges="keyboardHidden|orientation"
les choses ne suffisent pas.
Considérez le cas où l'utilisateur reçoit un appel téléphonique pendant que votre AsyncTask est en cours d'exécution. Votre demande est déjà en cours de traitement par le serveur, donc l'AsyncTask attend une réponse. En ce moment, votre application passe en arrière-plan, car l'application Téléphone vient juste de passer au premier plan. Le système d'exploitation peut tuer votre activité car elle est en arrière-plan.
la source
Pourquoi ne gardez-vous pas toujours une référence à la AsyncTask actuelle sur le Singleton fournie par Android?
A chaque démarrage d'une tâche, sur PreExecute ou sur le générateur, vous définissez:
((Application) getApplication()).setCurrentTask(asyncTask);
Chaque fois qu'il se termine, vous le définissez sur null.
De cette façon, vous avez toujours une référence qui vous permet de faire quelque chose comme, onCreate ou onResume en fonction de votre logique spécifique:
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
S'il est nul, vous savez qu'il n'y en a actuellement aucun en cours d'exécution!
:-)
la source
Application
L'instance a son propre cycle de vie - elle peut également être tuée par le système d'exploitation, donc cette solution peut provoquer un bogue difficile à reproduire.Le moyen le plus approprié pour cela est d'utiliser un fragment pour conserver l'instance de la tâche asynchrone, au cours des rotations.
Voici un lien vers un exemple très simple facilitant l'intégration de cette technique dans vos applications.
https://gist.github.com/daichan4649/2480065
la source
Dans
Pro android 4
. l'auteur a suggéré une bonne manière, que vous devriez utiliserweak reference
.Note de référence faible
la source
À mon point de vue, il est préférable de stocker un asynctask en le
onRetainNonConfigurationInstance
découplant de l'objet Activity actuel et en le liant à un nouvel objet Activity après le changement d'orientation. Ici, j'ai trouvé un très bel exemple comment travailler avec AsyncTask et ProgressDialog.la source
Android: traitement en arrière-plan / Opeartion asynchrone avec changement de configuration
Pour maintenir les états d'opération asynchrone pendant le processus d'arrière-plan: vous pouvez prendre une aide de fragments.
Suivez les étapes suivantes:
Étape 1: Créez un fragment sans en-tête, disons une tâche d'arrière-plan et ajoutez une classe de tâche asynchrone privée avec dedans.
Étape 2 (Étape facultative): si vous souhaitez placer un curseur de chargement au-dessus de votre activité, utilisez le code ci-dessous:
Étape 3: Dans votre activité principale, implémentez l'interface BackgroundTaskCallbacks définie à l'étape 1
class BackgroundTask extends Fragment { public BackgroundTask() { } // Add a static interface static interface BackgroundTaskCallbacks { void onPreExecute(); void onCancelled(); void onPostExecute(); void doInBackground(); } private BackgroundTaskCallbacks callbacks; private PerformAsyncOpeation asyncOperation; private boolean isRunning; private final String TAG = BackgroundTask.class.getSimpleName(); /** * Start the async operation. */ public void start() { Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********"); if (!isRunning) { asyncOperation = new PerformAsyncOpeation(); asyncOperation.execute(); isRunning = true; } Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********"); } /** * Cancel the background task. */ public void cancel() { Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********"); if (isRunning) { asyncOperation.cancel(false); asyncOperation = null; isRunning = false; } Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********"); } /** * Returns the current state of the background task. */ public boolean isRunning() { return isRunning; } /** * Android passes us a reference to the newly created Activity by calling * this method after each configuration change. */ public void onAttach(Activity activity) { Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********"); super.onAttach(activity); if (!(activity instanceof BackgroundTaskCallbacks)) { throw new IllegalStateException( "Activity must implement the LoginCallbacks interface."); } // Hold a reference to the parent Activity so we can report back the // task's // current progress and results. callbacks = (BackgroundTaskCallbacks) activity; Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********"); } public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********"); super.onCreate(savedInstanceState); // Retain this fragment across configuration changes. setRetainInstance(true); Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********"); } public void onDetach() { super.onDetach(); callbacks = null; } private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> { protected void onPreExecute() { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********"); if (callbacks != null) { callbacks.onPreExecute(); } isRunning = true; Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********"); } protected Void doInBackground(Void... params) { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********"); if (callbacks != null) { callbacks.doInBackground(); } Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********"); return null; } protected void onCancelled() { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********"); if (callbacks != null) { callbacks.onCancelled(); } isRunning = false; Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********"); } protected void onPostExecute(Void ignore) { Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********"); if (callbacks != null) { callbacks.onPostExecute(); } isRunning = false; Log.d(TAG, "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********"); } } public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setRetainInstance(true); } public void onStart() { super.onStart(); } public void onResume() { super.onResume(); } public void onPause() { super.onPause(); } public void onStop() { super.onStop(); }
public class ProgressIndicator extends Dialog { public ProgressIndicator(Context context, int theme) { super(context, theme); } private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.progress_indicator); this.setCancelable(false); progressBar = (ProgressBar) findViewById(R.id.progressBar); progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN); } @Override public void show() { super.show(); } @Override public void dismiss() { super.dismiss(); } @Override public void cancel() { super.cancel(); }
public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{ private static final String KEY_CURRENT_PROGRESS = "current_progress"; ProgressIndicator progressIndicator = null; private final static String TAG = MyActivity.class.getSimpleName(); private BackgroundTask task = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(//"set your layout here"); initialize your views and widget here ............. FragmentManager fm = getSupportFragmentManager(); task = (BackgroundTask) fm.findFragmentByTag("login"); // If the Fragment is non-null, then it is currently being // retained across a configuration change. if (task == null) { task = new BackgroundTask(); fm.beginTransaction().add(task, "login").commit(); } // Restore saved state if (savedInstanceState != null) { Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: " + task.isRunning()); if (task.isRunning()) { progressIndicator = new ProgressIndicator(this, R.style.TransparentDialog); if (progressIndicator != null) { progressIndicator.show(); } } } } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); } @Override protected void onSaveInstanceState(Bundle outState) { // save the current state of your operation here by saying this super.onSaveInstanceState(outState); Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: " + task.isRunning()); outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning()); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); } progressIndicator = null; } private void performOperation() { if (!task.isRunning() && progressIndicator == null) { progressIndicator = new ProgressIndicator(this, R.style.TransparentDialog); progressIndicator.show(); } if (task.isRunning()) { task.cancel(); } else { task.start(); } } @Override protected void onDestroy() { super.onDestroy(); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); } progressIndicator = null; } @Override public void onPreExecute() { Log.i(TAG, "CALLING ON PRE EXECUTE"); } @Override public void onCancelled() { Log.i(TAG, "CALLING ON CANCELLED"); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); } public void onPostExecute() { Log.i(TAG, "CALLING ON POST EXECUTE"); if (progressIndicator != null) { progressIndicator.dismiss(); progressIndicator.cancel(); progressIndicator = null; } } @Override public void doInBackground() { // put your code here for background operation }
}
la source
Une chose à considérer est de savoir si le résultat de l'AsyncTask doit être disponible uniquement pour l'activité qui a démarré la tâche. Si oui, la réponse de Romain Guy est la meilleure. S'il doit être disponible pour d'autres activités de votre application,
onPostExecute
vous pouvez utiliserLocalBroadcastManager
.LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
Vous devrez également vous assurer que l'activité gère correctement la situation lorsque la diffusion est envoyée alors que l'activité est en pause.
la source
Jetez un œil à ce post . Ce message implique qu'AsyncTask effectue une opération de longue durée et une fuite de mémoire lorsque la rotation de l'écran se produit à la fois dans un exemple d'application. L'exemple d'application est disponible sur la forge source
la source
Ma solution.
Dans mon cas, j'ai une chaîne d'AsyncTasks avec le même contexte. L'activité n'avait accès qu'au premier. Pour annuler toute tâche en cours, j'ai effectué les opérations suivantes:
public final class TaskLoader { private static AsyncTask task; private TaskLoader() { throw new UnsupportedOperationException(); } public static void setTask(AsyncTask task) { TaskLoader.task = task; } public static void cancel() { TaskLoader.task.cancel(true); } }
Tâche
doInBackground()
:protected Void doInBackground(Params... params) { TaskLoader.setTask(this); .... }
Activité
onStop()
ouonPause()
:protected void onStop() { super.onStop(); TaskLoader.cancel(); }
la source
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); final AddTask task = mAddTask; if (task != null && task.getStatus() != UserTask.Status.FINISHED) { final String bookId = task.getBookId(); task.cancel(true); if (bookId != null) { outState.putBoolean(STATE_ADD_IN_PROGRESS, true); outState.putString(STATE_ADD_BOOK, bookId); } mAddTask = null; } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) { final String id = savedInstanceState.getString(STATE_ADD_BOOK); if (!BooksManager.bookExists(getContentResolver(), id)) { mAddTask = (AddTask) new AddTask().execute(id); } } }
la source
vous pouvez également ajouter android: configChanges = "keyboardHidden | orientation | screenSize"
à votre exemple manifeste j'espère que cela vous aidera
<application android:name=".AppController" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:configChanges="keyboardHidden|orientation|screenSize" android:theme="@style/AppTheme">
la source