Question

Je lis beaucoup sur la façon de sauver mon état d'instance ou comment faire face à mon activité se détruit pendant la rotation de l'écran.

Il semble y avoir beaucoup de possibilités, mais je n'ai pas compris ce qui fonctionne le mieux pour récupérer les résultats d'un AsyncTask.

J'ai quelques AsyncTasks qui sont tout simplement commencé à nouveau et appeler la méthode isFinishing() de l'activité et si l'activité termine ils l'habitude de mettre à jour quoi que ce soit.

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 voulez-vous résoudre ce problème? Quels sont les avantages ou les inconvénients des solutions possibles?

Était-ce utile?

La solution

Ma première suggestion serait de vous assurer que vous avez réellement besoin de votre activité pour être remis à zéro sur une rotation de l'écran (le comportement par défaut). Chaque fois que j'ai eu des problèmes avec rotation je l'ai ajouté cet attribut à mon tag <activity> dans le AndroidManifest.xml, et a été très bien.

android:configChanges="keyboardHidden|orientation"

Il a l'air bizarre, mais ce qu'il fait le remettre hors de votre méthode de onConfigurationChanged(), si vous ne fournissez pas un il n'a tout simplement rien d'autre que remesurer la mise en page, ce qui semble être une façon parfaitement adéquate de gérer la rotation la plupart du temps.

Autres conseils

Vous pouvez vérifier comment je gère AsyncTasks et les changements d'orientation à code.google.com/p/shelves . Il existe différentes façons de le faire, celui que je choisi dans cette application est d'annuler une tâche en cours d'exécution, enregistrer son état et commencer une nouvelle avec l'état enregistré lorsque le nouveau Activity est créé. Il est facile à faire, il fonctionne bien et en prime, il prend soin d'arrêter vos tâches lorsque les feuilles de l'utilisateur de l'application.

Vous pouvez également utiliser onRetainNonConfigurationInstance() pour passer votre AsyncTask à la nouvelle Activity (attention de ne pas la fuite Activity précédente de cette façon si.)

Ceci est la question la plus intéressante que j'ai vu en ce qui concerne à Android !!! En fait, je cherchais déjà la solution au cours des derniers mois. ont pas résolu encore.

Attention, remplaçant simplement le

android:configChanges="keyboardHidden|orientation"

choses ne suffit pas.

Prenons le cas lorsque 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, de sorte que le AsyncTask attend pour la réponse. En ce moment votre application va en arrière-plan, car l'application de téléphone est venu juste au premier plan. OS peut tuer votre activité car il est en arrière-plan.

Pourquoi ne gardez-vous pas toujours une référence à la AsyncTask actuelle sur le Singleton fourni par Android?

Chaque fois qu'une tâche commence, sur PreExecute ou sur le constructeur, vous définissez:

((Application) getApplication()).setCurrentTask(asyncTask);

Chaque fois qu'il se termine vous définissez null.

De cette façon, vous avez toujours une référence qui vous permet de faire quelque chose comme, onCreate ou onResume comme approprié pour votre logique spécifique:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

Si ce que vous savez de nulle qu'actuellement il n'y a rien en cours d'exécution!

: -)

La façon la plus appropriée à cette question est d'utiliser un fragment de conserver l'instance de la tâche async, sur les rotations.

Voici un lien vers exemple très simple rendant facile à suivre intégrer cette technique dans vos applications.

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

Dans Pro android 4. auteur a suggérer une façon agréable, que vous devez utiliser weak reference.

faible note de référence

A mon point de vue, il est préférable de stocker AsyncTask via onRetainNonConfigurationInstance découplant de l'objet de l'activité en cours et se lier à un nouvel objet d'activité après le changement d'orientation. j'ai trouvé un très bel exemple comment travailler avec AsyncTask et ProgressDialog.

Applications: traitement de fond / Async opeartion avec changement de configuration

Pour maintenir les états de opeartion async au cours du processus d'arrière-plan: vous pouvez prendre une aide de fragments.

Voir les étapes suivantes:

Étape 1:. Créer un fragment de entete laisser dire tâche de fond et ajouter une classe de tâche async privée avec en elle

Étape 2 (étape optionnelle): si vous voulez mettre un curseur de chargement sur le dessus de l'utilisation de l'activité ci-dessous le code:

Étape 3: Dans votre activité principale mise en œuvre BackgroundTaskCallbacks interface 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
}

}

Une chose à considérer est de savoir si le résultat de la AsyncTask devrait être disponible uniquement à l'activité qui a commencé la tâche. Si oui, Romain réponse de Guy est le meilleur. Si elle devrait être disponible à d'autres activités de votre application, puis à onPostExecute vous pouvez utiliser LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

Vous devrez également vous assurer que l'activité gère correctement la situation lors de leur diffusion est envoyée alors que l'activité est en pause.

Jetez un oeil à cette poste . Ce poste implique AsyncTask effectuer longue opération en cours d'exécution et fuite de mémoire lors de la rotation de l'écran se produit à la fois dans une application échantillon. L'application échantillon est disponible sur la forge source

Ma solution.

Dans mon cas, j'ai une chaîne de AsyncTasks avec le même contexte. Activité a eu un accès qu'aux premier. Pour annuler toutes les tâches en cours d'exécution je l'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);
     }
}

doInBackground() Tâche:

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

Activité onStop() ou onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}
@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);
        }
    }
}

vous pouvez également ajouter android: configChanges = "keyboardHidden | orientation | ScreenSize"

à votre exemple manifeste je l'espère l'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">
scroll top