J'ai beaucoup lu sur la façon de sauvegarder mon état d'instance ou sur la façon dont mon activité est détruite lors de la rotation d'écran.
Il semble y avoir beaucoup de possibilités, mais je n'ai pas encore déterminé laquelle fonctionnait le mieux pour récupérer les résultats d'une AsyncTask.
J'ai des tâches asynchrones qui sont simplement relancées et appellent la méthode isFinishing()
de l'activité. Si l'activité est terminée, elles ne sont pas mises à 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égleriez-vous cela? Quels sont les avantages ou les inconvénients des solutions possibles?
Ma première suggestion serait de vous assurer que vous avez réellement besoin que votre activité soit réinitialisée sur une rotation d'écran (le comportement par défaut). Chaque fois que j'ai eu des problèmes avec la rotation, j'ai ajouté cet attribut à ma balise <activity>
dans le fichier AndroidManifest.xml et tout s'est bien passé.
Android:configChanges="keyboardHidden|orientation"
Cela semble bizarre, mais ce que cela donne à votre méthode onConfigurationChanged()
, si vous n'en fournissez pas, cela ne fait que re-mesurer la mise en page, ce qui semble être un moyen parfaitement adéquat de gérer la rotation temps.
Vous pouvez vérifier comment je gère AsyncTask
s et les changements d’orientation à l’adresse code.google.com/p/shelves . Il y a différentes façons de le faire. Celui que j'ai choisi dans cette application consiste à annuler une tâche en cours d'exécution, à enregistrer son état et à en démarrer un nouveau avec l'état enregistré lorsque la nouvelle Activity
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 votre AsyncTask
à la nouvelle Activity
(faites attention de ne pas laisser ficher l'ancienne Activity
de cette façon.)
C'est la question la plus intéressante que j'ai vue concernant Android !!! En fait, je cherchais déjà la solution au cours des derniers mois. Toujours pas résolu.
Soyez prudent, en ignorant simplement la
Android:configChanges="keyboardHidden|orientation"
ça ne suffit pas.
Considérez le cas où l'utilisateur reçoit un appel téléphonique pendant que votre tâche Async est en cours d'exécution. Votre demande étant déjà en cours de traitement par le serveur, AsyncTask attend la réponse. En ce moment, votre application passe en arrière-plan, car l'application Phone vient juste au premier plan. Le système d'exploitation peut tuer votre activité car elle est en arrière-plan.
Pourquoi ne gardez-vous pas toujours une référence à la tâche Async actuelle sur le Singleton fournie par Android?
Chaque fois qu'une tâche démarre, sur PreExecute ou sur le générateur, vous définissez:
((Application) getApplication()).setCurrentTask(asyncTask);
Chaque fois que cela se termine, vous le définissez à null.
De cette manière, 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();
Si c'est nul, vous savez qu'il n'y en a pas actuellement!
:-)
Le moyen le plus approprié consiste à utiliser un fragment pour conserver l’instance de la tâche asynchrone, au lieu de rotations.
Voici un lien vers des exemples très simples facilitant l'intégration de cette technique dans vos applications.
De mon point de vue, il est préférable de stocker asynctask via onRetainNonConfigurationInstance
en le dissociant de l'objet d'activité actuel et en le liant à un nouvel objet d'activité après le changement d'orientation. Ici J'ai trouvé un très bel exemple de travail avec AsyncTask et ProgressDialog.
Dans Pro Android 4
. L’auteur a suggéré un moyen agréable d’utiliser weak reference
.
Android: Traitement en arrière-plan/Opération asynchrone avec modification de la configuration
Pour conserver les états d’exploitation asynchrone pendant le traitement en arrière-plan: .__, vous pouvez utiliser une aide de fragments.
Voir les étapes suivantes:
Étape 1: Créez un fragment sans en-tête, définissez une tâche d’arrière-plan et ajoutez-y une classe de tâches async privée.
Étape 2 (étape facultative): si vous souhaitez placer un curseur de chargement en haut 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
}
}
Jetez un oeil à ce post . Cette publication implique que AsyncTask exécute 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 seul exemple d'application. L'exemple d'application est disponible sur source forge
Une chose à considérer est de savoir si le résultat de la tâche Async doit être disponible uniquement pour l'activité qui a démarré la tâche. Si oui, alors la réponse de Romain Guy est la meilleure. S'il est possible qu'il soit disponible pour d'autres activités de votre application, vous pouvez utiliser onPostExecute
dans LocalBroadcastManager
.
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 suspendue.
Ma solution.
Dans mon cas, j'ai une chaîne de tâches asynchrones avec le même contexte. L'activité n'avait accès qu'au premier. Pour annuler une tâche en cours, j’ai fait ce qui suit:
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()
ou onPause()
:
protected void onStop() {
super.onStop();
TaskLoader.cancel();
}
vous pouvez également ajouter Android: configChanges = "keyboardHidden | orientation | screenSize"
à votre exemple manifeste j'espère que cela aide
<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">
@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);
}
}
}