Procedura consigliata: AsyncTask durante il cambio di orientamento


151

AsyncTask è un'ottima cosa eseguire attività complesse in un altro thread.

Ma quando si verifica un cambio di orientamento o un'altra modifica della configurazione mentre AsyncTaskè ancora in esecuzione, la corrente Activityviene distrutta e riavviata. E poiché l'istanza di AsyncTaskè connessa a quell'attività, fallisce e provoca una finestra di messaggio "forza chiusura".

Quindi, sto cercando una sorta di "best practice" per evitare questi errori e impedire che AsyncTask fallisca.

Quello che ho visto finora è:

  • Disabilita le modifiche all'orientamento (non è certo il modo in cui dovresti gestirlo).
  • Lasciare sopravvivere l'attività e aggiornarla con la nuova istanza di attività tramite onRetainNonConfigurationInstance
  • Annullando semplicemente l'attività quando Activityviene distrutta e riavviandola quando Activityviene nuovamente creata.
  • Associare l'attività alla classe dell'applicazione anziché all'istanza dell'attività.
  • Alcuni metodi utilizzati nel progetto "scaffali" (tramite onRestoreInstanceState)

Alcuni esempi di codice:

Async Android: attività durante la rotazione dello schermo, parte I e parte II

ShelvesActivity.java

Potete aiutarmi a trovare l'approccio migliore che risolve meglio il problema ed è anche facile da implementare? Anche il codice stesso è importante poiché non so come risolverlo correttamente.


C'è un duplicato, controlla questo stackoverflow.com/questions/4584015/… .
TeaCupApp

Questo è tratto dal blog di Mark Murphy ... AsyncTask e ScreenRotation potrebbero aiutare ... link
Gopal

Anche se questo è un vecchio post, ma questo IMO, è un approccio molto più semplice (e migliore?).
DroidDev,

Mi sto solo chiedendo perché la documentazione non parli di situazioni così banali.
Sreekanth Karumanaghat,

Risposte:


140

Da non utilizzare android:configChangesper risolvere questo problema. Questa è una brutta pratica.

Da non usare Activity#onRetainNonConfigurationInstance()neanche. Questo è meno modulare e non adatto per Fragmentapplicazioni basate su.

Puoi leggere il mio articolo che descrive come gestire le modifiche alla configurazione usando gli Fragments conservati . Risolve il problema di trattenere bene un AsyncTaskcambio di rotazione. Fondamentalmente devi ospitare il tuo AsyncTaskinterno a Fragment, chiamare setRetainInstance(true)il Fragment, e riportare AsyncTaski progressi / i risultati al suo interno Activitytramite il mantenimento Fragment.


26
Bella idea, ma non tutti usano i frammenti. C'è molto codice legacy scritto molto prima che i frammenti fossero un'opzione.
Scott Biggs,

14
I frammenti di @ScottBiggs sono disponibili tramite la libreria di supporto fino ad Android 1.6. E potresti fornire un esempio di alcuni codici legacy che vengono ancora utilizzati attivamente e che potrebbero avere problemi a utilizzare la libreria di supporto Frammenti? Perché onestamente non penso che sia un problema.
Alex Lockwood,

4
@tactoth Non ho sentito il bisogno di affrontare questi problemi nella mia risposta, dal momento che il 99,9% delle persone non usa più TabActivity. Ad essere onesti, non sono sicuro del motivo per cui stiamo parlando di questo ... tutti concordano sul fatto che Fragmentsia la strada da percorrere. :)
Alex Lockwood,

2
Cosa succede se AsyncTask deve essere chiamato da un frammento nidificato?
Eduardo Naveda,

3
@AlexLockwood - "tutti concordano sul fatto che i frammenti sono la strada da percorrere". Gli sviluppatori di Squared non sarebbero d'accordo!
JBeckton,

36

Di solito risolvo questo problema facendo in modo che i miei AsyncTasks trasmettessero gli Intenti nel callback .onPostExecute (), quindi non modificano l'attività che li ha avviati direttamente. Le Attività ascoltano queste trasmissioni con Dynamic BroadcastReceivers e agiscono di conseguenza.

In questo modo gli AsyncTasks non devono preoccuparsi della specifica istanza Activity che gestisce il loro risultato. Esse "gridano" quando hanno finito e se un'attività è in quel momento (è attiva e focalizzata / è nel suo stato ripreso) che è interessata ai risultati dell'attività, allora sarà gestita.

Ciò comporta un po 'più di sovraccarico, poiché il runtime deve gestire la trasmissione, ma di solito non mi dispiace. Penso che usando LocalBroadcastManager invece del sistema predefinito si acceleri un po 'le cose.


6
se puoi aggiungere un esempio alla risposta sarebbe più utile
Sankar V

1
Penso che questa sia la soluzione che offre meno accoppiamento tra attività e frammenti
Roger Garzon Nieto,

7
Questo potrebbe far parte di una soluzione, ma non sembra che risolva il problema di AsyncTask che viene ricreato dopo il cambio di orientamento.
miguel,

4
Cosa succede se si è sfortunati e nessuna attività è attiva durante la trasmissione? (cioè sei a metà rotazione)
Sam,

24

Ecco un altro esempio di un AsyncTask che utilizza un Fragmentper gestire le modifiche alla configurazione di runtime (come quando l'utente ruota lo schermo) con setRetainInstance(true). Viene inoltre dimostrata una determinata barra di avanzamento (regolarmente aggiornata).

L'esempio si basa in parte sui documenti ufficiali, Conservare un oggetto durante una modifica della configurazione .

In questo esempio il lavoro che richiede un thread in background è il semplice caricamento di un'immagine da Internet nell'interfaccia utente.

Alex Lockwood sembra aver ragione quando si tratta di gestire le modifiche alla configurazione di runtime con AsyncTasks usando un "frammento mantenuto" è la migliore pratica. onRetainNonConfigurationInstance()viene deprecato in Lint, in Android Studio. I documenti ufficiali ci avvertono di non usare android:configChanges, dalla Gestione della modifica della configurazione , ...

Gestire da soli la modifica della configurazione può rendere molto più difficile l'utilizzo di risorse alternative, poiché il sistema non le applica automaticamente per te. Questa tecnica deve essere considerata l'ultima risorsa quando è necessario evitare il riavvio a causa di una modifica della configurazione e non è consigliata per la maggior parte delle applicazioni.

Quindi c'è il problema se si debba usare un AsyncTask per il thread in background.

Il riferimento ufficiale per AsyncTask avverte ...

AsyncTasks dovrebbe essere idealmente usato per operazioni brevi (al massimo pochi secondi). Se è necessario mantenere i thread in esecuzione per lunghi periodi di tempo, si consiglia vivamente di utilizzare le varie API fornite dal pacakge java.util.concurrent come Executor, ThreadPoolExecutor e FutureTask.

In alternativa, è possibile utilizzare un servizio, un caricatore (utilizzando un CursorLoader o AsyncTaskLoader) o un provider di contenuti per eseguire operazioni asincrone.

Divido il resto del post in:

  • La procedura; e
  • Tutto il codice per la procedura sopra descritta.

La procedura

  1. Inizia con un AsyncTask di base come classe interna di un'attività (non è necessario che sia una classe interna ma probabilmente sarà conveniente esserlo). In questa fase, AsyncTask non gestisce le modifiche alla configurazione di runtime.

    public class ThreadsActivity extends ActionBarActivity {
    
        private ImageView mPictureImageView;
    
        private class LoadImageFromNetworkAsyncTask
                              extends AsyncTask<String, Void, Bitmap> {
    
            @Override
            protected Bitmap doInBackground(String... urls) {
                return loadImageFromNetwork(urls[0]);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                mPictureImageView.setImageBitmap(bitmap);
            }
        }
    
        /**
         * Requires in AndroidManifext.xml
         *  <uses-permission android:name="android.permission.INTERNET" />
         */
        private Bitmap loadImageFromNetwork(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream((InputStream)
                                              new URL(url).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            mPictureImageView =
                (ImageView) findViewById(R.id.imageView_picture);
        }
    
        public void getPicture(View view) {
            new LoadImageFromNetworkAsyncTask()
                .execute("http://i.imgur.com/SikTbWe.jpg");
        }
    
    }
  2. Aggiungi una classe nidificata RetainedFragment che estende la classe Fragement e non ha la sua interfaccia utente. Aggiungi setRetainInstance (true) all'evento onCreate di questo frammento. Fornire procedure per impostare e ottenere i tuoi dati.

    public class ThreadsActivity extends Activity {
    
        private ImageView mPictureImageView;
        private RetainedFragment mRetainedFragment = null;
        ...
    
        public static class RetainedFragment extends Fragment {
    
            private Bitmap mBitmap;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                // The key to making data survive
                // runtime configuration changes.
                setRetainInstance(true);
            }
    
            public Bitmap getData() {
                return this.mBitmap;
            }
    
            public void setData(Bitmap bitmapToRetain) {
                this.mBitmap = bitmapToRetain;
            }
        }
    
        private class LoadImageFromNetworkAsyncTask
                        extends AsyncTask<String, Integer,Bitmap> {
        ....
  3. Nella classe esterna Activity onCreate () gestisce RetainedFragment: fai riferimento se esiste già (nel caso in cui l'attività si riavvii); crearlo e aggiungerlo se non esiste; Quindi, se esiste già, ottieni i dati da RetainedFragment e imposta l'interfaccia utente con tali dati.

    public class ThreadsActivity extends Activity {
    
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            final String retainedFragmentTag = "RetainedFragmentTag";
    
            mPictureImageView =
                      (ImageView) findViewById(R.id.imageView_picture);
            mLoadingProgressBar =
                    (ProgressBar) findViewById(R.id.progressBar_loading);
    
            // Find the RetainedFragment on Activity restarts
            FragmentManager fm = getFragmentManager();
            // The RetainedFragment has no UI so we must
            // reference it with a tag.
            mRetainedFragment =
              (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
    
            // if Retained Fragment doesn't exist create and add it.
            if (mRetainedFragment == null) {
    
                // Add the fragment
                mRetainedFragment = new RetainedFragment();
                fm.beginTransaction()
                    .add(mRetainedFragment, retainedFragmentTag).commit();
    
            // The Retained Fragment exists
            } else {
    
                mPictureImageView
                    .setImageBitmap(mRetainedFragment.getData());
            }
        }
  4. Avviare AsyncTask dall'interfaccia utente

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Aggiungi e codifica una determinata barra di avanzamento:

    • Aggiungi una barra di avanzamento al layout dell'interfaccia utente;
    • Ottieni un riferimento ad esso in Activity oncreate ();
    • Renderlo visibile e invisibile all'inizio e alla fine del processo;
    • Definire l'avanzamento da segnalare all'interfaccia utente in onProgressUpdate.
    • Modificare il secondo parametro generico AsyncTask da Void a un tipo in grado di gestire gli aggiornamenti di avanzamento (ad es. Intero).
    • publishingProgress in punti regolari in doInBackground ().

Tutto il codice per la procedura sopra descritta

Layout delle attività.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mysecondapp.ThreadsActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/imageView_picture"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="@android:color/black" />

        <Button
            android:id="@+id/button_get_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@id/imageView_picture"
            android:onClick="getPicture"
            android:text="Get Picture" />

        <Button
            android:id="@+id/button_clear_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/button_get_picture"
            android:layout_toEndOf="@id/button_get_picture"
            android:layout_toRightOf="@id/button_get_picture"
            android:onClick="clearPicture"
            android:text="Clear Picture" />

        <ProgressBar
            android:id="@+id/progressBar_loading"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/button_get_picture"
            android:progress="0"
            android:indeterminateOnly="false"
            android:visibility="invisible" />

    </RelativeLayout>
</ScrollView>

L'attività con: classe interna AsyncTask sottoclassata; classe interna RetainedFragment sottoclasse che gestisce le modifiche alla configurazione di runtime (ad es. quando l'utente ruota lo schermo); e una determinata barra di avanzamento che si aggiorna a intervalli regolari. ...

public class ThreadsActivity extends Activity {

    private ImageView mPictureImageView;
    private RetainedFragment mRetainedFragment = null;
    private ProgressBar mLoadingProgressBar;

    public static class RetainedFragment extends Fragment {

        private Bitmap mBitmap;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // The key to making data survive runtime configuration changes.
            setRetainInstance(true);
        }

        public Bitmap getData() {
            return this.mBitmap;
        }

        public void setData(Bitmap bitmapToRetain) {
            this.mBitmap = bitmapToRetain;
        }
    }

    private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
            Integer, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... urls) {
            // Simulate a burdensome load.
            int sleepSeconds = 4;
            for (int i = 1; i <= sleepSeconds; i++) {
                SystemClock.sleep(1000); // milliseconds
                publishProgress(i * 20); // Adjust for a scale to 100
            }

            return com.example.standardapplibrary.android.Network
                    .loadImageFromNetwork(
                    urls[0]);
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            mLoadingProgressBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            publishProgress(100);
            mRetainedFragment.setData(bitmap);
            mPictureImageView.setImageBitmap(bitmap);
            mLoadingProgressBar.setVisibility(View.INVISIBLE);
            publishProgress(0);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threads);

        final String retainedFragmentTag = "RetainedFragmentTag";

        mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
        mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);

        // Find the RetainedFragment on Activity restarts
        FragmentManager fm = getFragmentManager();
        // The RetainedFragment has no UI so we must reference it with a tag.
        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
                retainedFragmentTag);

        // if Retained Fragment doesn't exist create and add it.
        if (mRetainedFragment == null) {

            // Add the fragment
            mRetainedFragment = new RetainedFragment();
            fm.beginTransaction().add(mRetainedFragment,
                                      retainedFragmentTag).commit();

            // The Retained Fragment exists
        } else {

            mPictureImageView.setImageBitmap(mRetainedFragment.getData());
        }
    }

    public void getPicture(View view) {
        mLoadingProgressBar.setVisibility(View.VISIBLE);
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }

    public void clearPicture(View view) {
        mRetainedFragment.setData(null);
        mPictureImageView.setImageBitmap(null);
    }
}

In questo esempio la funzione di libreria (di cui sopra con il prefisso esplicito del pacchetto com.example.standardapplibrary.android.Network) che funziona davvero ...

public static Bitmap loadImageFromNetwork(String url) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
                .getContent());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bitmap;
}

Aggiungi tutte le autorizzazioni necessarie per l'attività in background su AndroidManifest.xml ...

<manifest>
...
    <uses-permission android:name="android.permission.INTERNET" />

Aggiungi la tua attività su AndroidManifest.xml ...

<manifest>
...
    <application>
        <activity
            android:name=".ThreadsActivity"
            android:label="@string/title_activity_threads"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.mysecondapp.MainActivity" />
        </activity>

Grande. Dovresti scrivere un blog su questo.
Akh,

2
@AKh. Vuoi dire che la mia risposta occupa troppo spazio su Stackoverflow?
John Bentley,

1
Penso che significhi solo che hai una risposta fantastica e dovresti scrivere un blog! =) @JohnBentley
Sandy D.

@ SandyD.yesterday Grazie per l'interpretazione positiva. Spero che lei o lui lo intendessero.
John Bentley,

Ho anche pensato che fosse una risposta fantastica e l'ho interpretata anche in questo modo. Le risposte molto complete come questa sono fantastiche !!
Leonardo

3

Di recente, ho trovato una buona soluzione qui . Si basa sul salvataggio di un oggetto attività tramite su RetainConfiguration. Dal mio punto di vista, la soluzione è molto elegante e per quanto mi riguarda ho iniziato a usarla. Hai solo bisogno di nidificare la tua asincaria dalla base e questo è tutto.


Grazie mille per questa risposta interessante. È una buona soluzione oltre a quelle menzionate nella domanda correlata.
Caw

5
Sfortunatamente, questa soluzione utilizza metodi obsoleti.
Damien,

3

Sulla base della risposta di @Alex Lockwood e delle risposte di @William & @quickdraw mcgraw su questo post: Come gestire i messaggi del gestore quando l'attività / il frammento viene messo in pausa , ho scritto una soluzione generica.

In questo modo viene gestita la rotazione e se l'attività passa in background durante l'esecuzione dell'attività asincrona, l'attività riceverà i callback (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) una volta ripresi, quindi non verrà generata alcuna IllegalStateException (vedi Come gestire Handler messaggi quando l'attività / frammento è in pausa ).

Sarebbe bello avere lo stesso ma con tipi di argomenti generici, come un AsyncTask (ad esempio: AsyncTaskFragment <Params, Progress, Result>), ma non sono riuscito a farlo rapidamente e al momento non ho tempo. Se qualcuno vuole fare il miglioramento, non esitate!

Il codice:

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class AsyncTaskFragment extends Fragment {

    /* ------------------------------------------------------------------------------------------ */
    // region Classes & Interfaces

    public static abstract class Task extends AsyncTask<Object, Object, Object> {

        private AsyncTaskFragment _fragment;

        private void setFragment(AsyncTaskFragment fragment) {

            _fragment = fragment;
        }

        @Override
        protected final void onPreExecute() {

            // Save the state :
            _fragment.setRunning(true);

            // Send a message :
            sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
        }

        @Override
        protected final void onPostExecute(Object result) {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_POST_EXECUTE_MESSAGE, result);
        }

        @Override
        protected final void onProgressUpdate(Object... values) {

            // Send a message :
            sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
        }

        @Override
        protected final void onCancelled() {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_CANCELLED_MESSAGE, null);
        }

        private void sendMessage(int what, Object obj) {

            Message message = new Message();
            message.what = what;
            message.obj = obj;

            Bundle data = new Bundle(1);
            data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
            message.setData(data);

            _fragment.handler.sendMessage(message);
        }
    }

    public interface AsyncTaskFragmentListener {

        void onPreExecute(String fragmentTag);
        void onProgressUpdate(String fragmentTag, Object... progress);
        void onCancelled(String fragmentTag);
        void onPostExecute(String fragmentTag, Object result);
    }

    private static class AsyncTaskFragmentPauseHandler extends PauseHandler {

        @Override
        final protected void processMessage(Activity activity, Message message) {

            switch (message.what) {

                case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
                case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
                case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
                case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
            }
        }
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Attributes

    private Task _task;
    private AsyncTaskFragmentListener _listener;
    private boolean _running = false;

    private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
    private static final int ON_PRE_EXECUTE_MESSAGE = 0;
    private static final int ON_POST_EXECUTE_MESSAGE = 1;
    private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
    private static final int ON_CANCELLED_MESSAGE = 3;

    private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Getters

    public AsyncTaskFragmentListener getListener() { return _listener; }
    public boolean isRunning() { return _running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Setters

    public void setTask(Task task) {

        _task = task;
        _task.setFragment(this);
    }

    public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
    private void setRunning(boolean running) { _running = running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Fragment lifecycle

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {

        super.onResume();
        handler.resume(getActivity());
    }

    @Override
    public void onPause() {

        super.onPause();
        handler.pause();
    }

    @Override
    public void onAttach(Activity activity) {

        super.onAttach(activity);
        _listener = (AsyncTaskFragmentListener) activity;
    }

    @Override
    public void onDetach() {

        super.onDetach();
        _listener = null;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Utils

    public void execute(Object... params) {

        _task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }

    public void cancel(boolean mayInterruptIfRunning) {

        _task.cancel(mayInterruptIfRunning);
    }

    public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {

        FragmentManager fm = activity.getSupportFragmentManager();
        AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);

        if (fragment == null) {

            fragment = new AsyncTaskFragment();
            fragment.setListener( (AsyncTaskFragmentListener) activity);
            fm.beginTransaction().add(fragment, fragmentTag).commit();
        }

        return fragment;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */
}

Avrai bisogno di PauseHandler:

import android.app.Activity;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
 *
 * /programming/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
 */
public abstract class PauseHandler extends Handler {

    /**
     * Message Queue Buffer
     */
    private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());

    /**
     * Flag indicating the pause state
     */
    private Activity activity;

    /**
     * Resume the handler.
     */
    public final synchronized void resume(Activity activity) {
        this.activity = activity;

        while (messageQueueBuffer.size() > 0) {
            final Message msg = messageQueueBuffer.get(0);
            messageQueueBuffer.remove(0);
            sendMessage(msg);
        }
    }

    /**
     * Pause the handler.
     */
    public final synchronized void pause() {
        activity = null;
    }

    /**
     * Store the message if we have been paused, otherwise handle it now.
     *
     * @param msg   Message to handle.
     */
    @Override
    public final synchronized void handleMessage(Message msg) {
        if (activity == null) {
            final Message msgCopy = new Message();
            msgCopy.copyFrom(msg);
            messageQueueBuffer.add(msgCopy);
        } else {
            processMessage(activity, msg);
        }
    }

    /**
     * Notification message to be processed. This will either be directly from
     * handleMessage or played back from a saved message when the activity was
     * paused.
     *
     * @param activity  Activity owning this Handler that isn't currently paused.
     * @param message   Message to be handled
     */
    protected abstract void processMessage(Activity activity, Message message);
}

Esempio di utilizzo:

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

    private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
    private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        Button testButton = (Button) findViewById(R.id.test_button);
        final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);

        testButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

                        @Override
                        protected Object doInBackground(Object... objects) {

                            // Do your async stuff

                            return null;
                        }
                    });

                    fragment.execute();
                }
            }
        });
    }

    @Override
    public void onPreExecute(String fragmentTag) {}

    @Override
    public void onProgressUpdate(String fragmentTag, Float percent) {}

    @Override
    public void onCancelled(String fragmentTag) {}

    @Override
    public void onPostExecute(String fragmentTag, Object result) {

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}

3

È possibile utilizzare Caricatori per questo. Controlla Doc qui


2
I caricatori sono ora obsoleti a partire dall'API 28 di Android (come indicato dal collegamento).
Sumit

I caricatori non sono deprecati, è solo il modo in cui li chiami che è cambiato
EdgeDev

2

Per coloro che vogliono schivare i frammenti, è possibile mantenere AsyncTask in esecuzione sui cambiamenti di orientamento utilizzando onRetainCustomNonConfigurationInstance () e alcuni collegamenti.

(Si noti che questo metodo è l'alternativa a deprecato onRetainNonConfigurationInstance () ).

Sembra che questa soluzione non sia menzionata spesso però. Ho scritto un semplice esempio corrente per illustrare.

Saluti!

public class MainActivity extends AppCompatActivity {

private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    result = (TextView) findViewById(R.id.textView_result);
    run = (Button) findViewById(R.id.button_run);
    asyncTaskHolder = getAsyncTaskHolder();
    run.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            asyncTaskHolder.execute();
        }
    });
}

private AsyncTaskHolder getAsyncTaskHolder() {
    if (this.asyncTaskHolder != null) {
        return asyncTaskHolder;
    }
    //Not deprecated. Get the same instance back.
    Object instance = getLastCustomNonConfigurationInstance();

    if (instance == null) {
        instance = new AsyncTaskHolder();
    }
    if (!(instance instanceof ActivityDependant)) {
        Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
    }
    return (AsyncTaskHolder) instance;
}

@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
    return asyncTaskHolder;
}

@Override
protected void onStart() {
    super.onStart();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.attach(this);
    }
}

@Override
protected void onStop() {
    super.onStop();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.detach();
    }
}

void updateUI(String value) {
    this.result.setText(value);
}

interface ActivityDependant {

    void attach(Activity activity);

    void detach();
}

class AsyncTaskHolder implements ActivityDependant {

    private Activity parentActivity;
    private boolean isRunning;
    private boolean isUpdateOnAttach;

    @Override
    public synchronized void attach(Activity activity) {
        this.parentActivity = activity;
        if (isUpdateOnAttach) {
            ((MainActivity) parentActivity).updateUI("done");
            isUpdateOnAttach = false;
        }
    }

    @Override
    public synchronized void detach() {
        this.parentActivity = null;
    }

    public synchronized void execute() {
        if (isRunning) {
            Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
            return;
        }
        isRunning = true;
        new AsyncTask<Void, Integer, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                for (int i = 0; i < 100; i += 10) {
                    try {
                        Thread.sleep(500);
                        publishProgress(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
                }
            }

            @Override
            protected synchronized void onPostExecute(Void aVoid) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI("done");
                } else {
                    isUpdateOnAttach = true;
                }
                isRunning = false;
            }
        }.execute();
    }
}

0

Ho implementato una libreria in grado di risolvere i problemi con la pausa di attività e la ricreazione durante l'esecuzione dell'attività.

Dovresti implementare AsmykPleaseWaitTaske AsmykBasicPleaseWaitActivity. La tua attività e attività in background funzionerà benissimo anche se ruoterai lo schermo e passerai da un'applicazione all'altra


-9

Workaround veloce (non consigliato)

Per evitare che un'attività si distrugga e si crei è dichiarare la propria attività nel file manifest: android: configChanges = "orientamento | keyboardHidden | screenSize

  <activity
        android:name=".ui.activity.MyActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name">

Come è menzionato nei documenti

L'orientamento dello schermo è cambiato: l'utente ha ruotato il dispositivo.

Nota: se l'applicazione è indirizzata al livello API 13 o superiore (come dichiarato dagli attributi minSdkVersion e targetSdkVersion), è necessario dichiarare anche la configurazione "screenSize", poiché cambia anche quando un dispositivo passa da orientamento verticale a orizzontale.


1
Questo è meglio evitare. developer.android.com/guide/topics/resources/… "Nota: la gestione della modifica della configurazione può rendere molto più difficile l'utilizzo di risorse alternative, poiché il sistema non le applica automaticamente per te. Questa tecnica deve essere considerata l'ultima ricorrere quando è necessario evitare il riavvio a causa di una modifica della configurazione e non è raccomandato per la maggior parte delle applicazioni. "
David,
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.