web-dev-qa-db-fra.com

Comment gérer une tâche asynchrone pendant la rotation de l'écran?

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?

86
Janusz

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.

6
Jim Blackler

Vous pouvez vérifier comment je gère AsyncTasks 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.)

46
Romain Guy

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.

10
Vit Khudenko

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!

:-)

6
neteinstein

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.

https://Gist.github.com/daichan4649/2480065

4
user2342491

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.

3
Yury

Dans Pro Android 4. L’auteur a suggéré un moyen agréable d’utiliser weak reference

Note de référence faible

3
hqt

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
}

}

2
Piyush Gupta

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

1
Vahid

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.

1
Juozas Kontvainis

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

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">
0
eng mohamed emam
@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);
        }
    }
}
0
Atif Mahmood