Come gestire un AsyncTask durante la rotazione dello schermo?


88

Ho letto molto su come salvare lo stato dell'istanza o su come gestire la mia attività che viene distrutta durante la rotazione dello schermo.

Sembra che ci siano molte possibilità ma non ho capito quale funziona meglio per recuperare i risultati di un AsyncTask.

Ho alcuni AsyncTask che vengono semplicemente riavviati e chiamano il isFinishing()metodo dell'attività e se l'attività sta finendo non aggiorneranno nulla.

Il problema è che ho un'attività che esegue una richiesta a un servizio Web che può non riuscire o avere esito positivo e il riavvio dell'attività comporterebbe una perdita finanziaria per l'utente.

Come risolveresti questo? Quali sono i vantaggi o gli svantaggi delle possibili soluzioni?


1
Vedi la mia risposta qui . Potresti anche trovare utili queste informazioni su ciò che setRetainInstance(true)effettivamente fa .
Timmmm

quello che farei è semplicemente implementare un servizio locale che esegue l'elaborazione (in un thread) che sta facendo il tuo asyncTask. Per visualizzare i risultati, trasmetti i dati alla tua attività. Ora l'attività è responsabile solo della visualizzazione dei dati e l'elaborazione non viene mai interrotta da una rotazione dello schermo.
Someone Somewhere

Che ne dici di usare AsyncTaskLoader invece di AsyncTask ??
Sourangshu Biswas

Risposte:


6

Il mio primo suggerimento sarebbe quello di assicurarti di aver effettivamente bisogno che la tua attività venga reimpostata su una rotazione dello schermo (il comportamento predefinito). Ogni volta che ho avuto problemi con la rotazione, ho aggiunto questo attributo al mio <activity>tag in AndroidManifest.xml e ho funzionato bene.

android:configChanges="keyboardHidden|orientation"

Sembra strano, ma quello che fa passa al tuo onConfigurationChanged()metodo, se non ne fornisci uno non fa altro che rimisurare il layout, che sembra essere un modo perfettamente adeguato di gestire la rotazione per la maggior parte del tempo .


5
Ma ciò impedirà all'attività di modificare il layout. E quindi obbliga l'utente ad utilizzare il proprio dispositivo con un preciso orientamento dettato dalla propria applicazione e non dalle sue esigenze.
Janusz

77
L'utilizzo di questa tecnica impedisce di utilizzare facilmente le risorse specifiche della configurazione. Ad esempio, se vuoi che il tuo layout, i drawables o le stringhe o qualsiasi altra cosa siano diversi in verticale e paesaggi, ti consigliamo il comportamento predefinito. L'override della modifica della configurazione dovrebbe essere eseguita solo in casi molto specifici (un gioco, un browser Web, ecc.) E non per pigrizia o convenienza perché ti stai limitando.
Romain Guy

38
Bene, è proprio questo, Romain. "se vuoi che il tuo layout, i drawables o le stringhe o qualsiasi altra cosa siano diversi in ritratto e paesaggi, ti consigliamo il comportamento predefinito", credo che sia un caso d'uso molto più raro di quanto immagini. Quello che chiamate "casi molto specifici" è la maggior parte degli sviluppatori, credo. L'utilizzo di layout relativi che funzionano in tutte le dimensioni è la migliore pratica e non è così difficile. Parlare di pigrizia è altamente fuorviante, queste tecniche servono a migliorare l'esperienza dell'utente non a ridurre i tempi di sviluppo.
Jim Blackler

2
Ho scoperto che funziona perfettamente per LinearLayout, ma quando si utilizza RelativeLayout non ridisegna correttamente il layout quando si passa alla modalità orizzontale (almeno non su N1). Vedere questo domande: stackoverflow.com/questions/2987049/...
JohnRock

9
Sono d'accordo con Romain (sa di cosa sta parlando, sviluppa il sistema operativo). Cosa succede quando vuoi trasferire la tua applicazione su un tablet e la tua interfaccia utente sembra orribile quando allungata? Se segui l'approccio di questa risposta, dovrai ricodificare l'intera soluzione perché sei andato con questo pigro hack.
Austyn Mahoney

46

Puoi controllare come gestisco AsyncTaski cambiamenti di orientamento e su code.google.com/p/shelves . Ci sono vari modi per farlo, quello che ho scelto in questa app è annullare qualsiasi attività attualmente in esecuzione, salvarne lo stato e avviarne uno nuovo con lo stato salvato quando Activityviene creato il nuovo . È facile da fare, funziona bene e come bonus si prende cura di interrompere le tue attività quando l'utente lascia l'app.

Puoi anche usarlo onRetainNonConfigurationInstance()per passare il tuo AsyncTaskal nuovo Activity(fai attenzione a non perdere il precedente in Activityquesto modo).


1
l'ho provato e la rotazione durante la ricerca del libro si interrompe e mi dà meno risultati rispetto a quando non ruota,
peccato

1
Non sono riuscito a trovare un singolo utilizzo di AsyncTask in quel codice. C'è una classe UserTask che sembra simile. Questo progetto è precedente ad AsyncTask?
devconsole

7
AsyncTask proviene da UserTask. Inizialmente ho scritto UserTask per le mie app e successivamente l'ho trasformato in AsyncTask. Mi dispiace, ho dimenticato che è stato rinominato.
Romain Guy

@RomainGuy Ciao, spero che tu stia bene. In base al tuo codice, vengono inviate 2 richieste al server anche se all'inizio l'attività viene annullata ma non viene annullata con successo. Non so perché Per favore, dimmi che c'è un modo per risolvere questo problema.
iamcrypticcoder

10

Questa è la domanda più interessante che ho visto riguardo ad Android !!! In realtà ho già cercato la soluzione negli ultimi mesi. Non ho ancora risolto.

Fai attenzione, ignorando semplicemente il file

android:configChanges="keyboardHidden|orientation"

le cose non sono sufficienti.

Considera il caso in cui l'utente riceve una telefonata mentre AsyncTask è in esecuzione. La tua richiesta è già in fase di elaborazione dal server, quindi AsyncTask è in attesa di risposta. In questo momento la tua app va in background, perché l'app Telefono è appena entrata in primo piano. Il sistema operativo potrebbe interrompere la tua attività poiché è in background.


6

Perché non mantieni sempre un riferimento all'attuale AsyncTask sul Singleton fornito da Android?

Ogni volta che viene avviata un'attività, in PreExecute o nel builder, si definisce:

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

Ogni volta che finisce, impostalo su null.

In questo modo hai sempre un riferimento che ti consente di fare qualcosa come, onCreate o onResume come appropriato per la tua logica specifica:

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

Se è nullo sai che attualmente non ce n'è in esecuzione!

:-)


Funzionerà? Qualcuno l'ha provato? L'attività verrà comunque interrotta dal sistema se si verifica un'interruzione della telefonata o se si passa a una nuova attività e poi si torna indietro?
Robert

6
Applicationistanza ha il suo ciclo di vita - può essere uccisa anche dal sistema operativo, quindi questa soluzione potrebbe causare un bug difficile da riprodurre.
Vit Khudenko

7
Ho pensato: se l'applicazione viene uccisa, l'intera app viene uccisa (e quindi anche tutti gli AsyncTask)?
manmal

Penso che l'applicazione possa essere terminata senza che tutti gli asynctask siano (molto rari). Ma @Arhimed con alcune verifiche facili da fare all'inizio e alla fine di ogni asynctask puoi evitare i bug.
neteinstein



3

Dal mio punto di vista, è meglio memorizzare asynctask onRetainNonConfigurationInstancedisaccoppiandolo dall'oggetto Activity corrente e associandolo a un nuovo oggetto Activity dopo il cambio di orientamento. Qui ho trovato un esempio molto carino su come lavorare con AsyncTask e ProgressDialog.


2

Android: elaborazione in background / Opeartion asincrono con modifica della configurazione

Per mantenere gli stati di operatività asincrona durante il processo in background: puoi richiedere l'aiuto dei frammenti.

Vedere i passaggi seguenti:

Passaggio 1: crea un frammento senza intestazione, diciamo un'attività in background e aggiungi una classe di attività asincrona privata al suo interno.

Passaggio 2 (Passaggio facoltativo): se si desidera posizionare un cursore di caricamento sopra la propria attività, utilizzare il codice seguente:

Passaggio 3: nella tua attività principale implementa l'interfaccia BackgroundTaskCallbacks definita nel passaggio 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
}

}


1

Una cosa da considerare è se il risultato di AsyncTask deve essere disponibile solo per l'attività che ha avviato l'attività. Se sì, la risposta di Romain Guy è la migliore. Se dovrebbe essere disponibile per altre attività della tua applicazione, onPostExecutepuoi usare LocalBroadcastManager.

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

Dovrai anche assicurarti che l'attività gestisca correttamente la situazione quando la trasmissione viene inviata mentre l'attività è in pausa.


1

Dai un'occhiata a questo post . Questo post prevede che AsyncTask esegua operazioni di lunga durata e perdita di memoria quando la rotazione dello schermo avviene sia in un'applicazione di esempio. L'app di esempio è disponibile in Source Forge


0

La mia soluzione.

Nel mio caso ho una catena di AsyncTask con lo stesso contesto. L'attività aveva accesso solo alla prima. Per annullare qualsiasi attività in esecuzione ho fatto quanto segue:

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);
     }
}

Compito doInBackground():

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

Attività onStop()o onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}

0
@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

puoi anche aggiungere android: configChanges = "keyboardHidden | guidance | screenSize"

al tuo esempio manifest spero che aiuti

 <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">
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.