Best Practice: AsyncTask während der Orientierungsänderung

151

AsyncTask ist eine großartige Sache, um komplexe Aufgaben in einem anderen Thread auszuführen.

Wenn sich jedoch die Ausrichtung oder eine andere Konfiguration ändert, während das AsyncTasknoch ausgeführt wird, wird der Strom Activityzerstört und neu gestartet. Wenn die Instanz von AsyncTaskmit dieser Aktivität verbunden ist, schlägt sie fehl und verursacht ein Meldungsfenster "Schließen erzwingen".

Daher suche ich nach einer Art "Best Practice", um diese Fehler zu vermeiden und zu verhindern, dass AsyncTask fehlschlägt.

Was ich bisher gesehen habe ist:

  • Deaktivieren Sie Orientierungsänderungen. (Sicher nicht so, wie Sie damit umgehen sollten.)
  • Lassen Sie die Aufgabe überleben und aktualisieren Sie sie mit der neuen Aktivitätsinstanz über onRetainNonConfigurationInstance
  • Brechen Sie die Aufgabe einfach ab, wenn die Activityzerstört ist, und starten Sie sie neu, wenn die Activityerneut erstellt wird.
  • Binden der Aufgabe an die Anwendungsklasse anstelle der Aktivitätsinstanz.
  • Einige im Projekt "Regale" verwendete Methode (über onRestoreInstanceState)

Einige Codebeispiele:

Android AsyncTasks während einer Bildschirmrotation, Teil I und Teil II

ShelvesActivity.java

Können Sie mir helfen, den besten Ansatz zu finden, der das Problem am besten löst und auch einfach zu implementieren ist? Der Code selbst ist ebenfalls wichtig, da ich nicht weiß, wie ich das richtig lösen soll.

krächzen
quelle
Es gibt ein Duplikat. Überprüfen Sie dies auf stackoverflow.com/questions/4584015/… .
TeaCupApp
Dies ist aus Mark Murphys Blog ... AsyncTask und ScreenRotation könnten helfen ... Link
Gopal
Dies ist zwar ein alter Beitrag, aber diese IMO ist ein viel einfacherer (und besserer?) Ansatz.
DroidDev
Ich frage mich nur, warum in der Dokumentation nicht über so triviale Situationen gesprochen wird.
Sreekanth Karumanaghat

Antworten:

140

Sie NICHT verwenden , android:configChangesum dieses Problem zu beheben. Das ist eine sehr schlechte Praxis.

Verwenden Sie auch NICHTActivity#onRetainNonConfigurationInstance() . Dies ist weniger modular und für FragmentAnwendungen auf Basis nicht gut geeignet .

Sie können meinen Artikel lesen, in dem beschrieben wird, wie Konfigurationsänderungen mit beibehaltenen Fragments behandelt werden. Es löst das Problem, AsyncTaskeine Änderung über eine Rotation hinweg gut beizubehalten. Sie müssen im Grunde Ihre Gastgeber AsyncTaskinnerhalb eines Fragment, Anruf setRetainInstance(true)auf das Fragment, und die Bericht AsyncTaskFortschritte / Ergebnisse s‘wieder auf , es ist Activitydurch die beibehalten Fragment.

Alex Lockwood
quelle
26
Gute Idee, aber nicht jeder verwendet Fragmente. Es wurde viel Legacy-Code geschrieben, lange bevor Fragmente eine Option waren.
Scott Biggs
14
@ ScottBiggs-Fragmente sind über die Support-Bibliothek bis hin zu Android 1.6 verfügbar. Und könnten Sie ein Beispiel für einen Legacy-Code geben, der noch aktiv verwendet wird und Probleme bei der Verwendung der Fragmente der Support-Bibliothek haben würde? Weil ich ehrlich gesagt nicht denke, dass das ein Problem ist.
Alex Lockwood
4
@tactoth Ich hatte nicht das Bedürfnis, diese Probleme in meiner Antwort anzusprechen, da 99,9% der Menschen nicht mehr verwenden TabActivity. Um ehrlich zu sein, ich bin mir nicht sicher, warum wir überhaupt darüber sprechen ... alle sind sich einig, dass dies der Fragmentrichtige Weg ist. :)
Alex Lockwood
2
Was ist, wenn die AsyncTask von einem verschachtelten Fragment aufgerufen werden muss?
Eduardo Naveda
3
@AlexLockwood - "Alle sind sich einig, dass Fragmente der richtige Weg sind." Die Entwickler bei Squared würden dem nicht zustimmen!
JBeckton
36

Normalerweise löse ich dieses Problem, indem meine AsyncTasks im .onPostExecute () -Rückruf Broadcast-Absichten auslösen, damit sie die Aktivität, mit der sie direkt gestartet wurden, nicht ändern. Die Aktivitäten hören diese Sendungen mit dynamischen BroadcastReceivern ab und handeln entsprechend.

Auf diese Weise müssen sich die AsyncTasks nicht um die spezifische Aktivitätsinstanz kümmern, die ihr Ergebnis verarbeitet. Sie "schreien" nur, wenn sie fertig sind, und wenn sich eine Aktivität in dieser Zeit befindet (aktiv und konzentriert / in ihrem wieder aufgenommenen Zustand), die an den Ergebnissen der Aufgabe interessiert ist, wird sie bearbeitet.

Dies ist mit etwas mehr Aufwand verbunden, da die Laufzeit die Übertragung übernehmen muss, aber es macht mir normalerweise nichts aus. Ich denke, die Verwendung des LocalBroadcastManager anstelle des standardmäßigen systemweiten Systems beschleunigt die Dinge ein wenig.

Zsombor Erdődy-Nagy
quelle
6
Wenn Sie der Antwort ein Beispiel hinzufügen können, wäre dies hilfreicher
Sankar V
1
Ich denke, dies ist die Lösung, die weniger Kopplung zwischen Aktivitäten und Fragmenten bietet
Roger Garzon Nieto
7
Dies mag Teil einer Lösung sein, aber es scheint nicht das Problem zu lösen, dass die AsyncTask nach der Änderung der Ausrichtung neu erstellt wird.
Miguel
4
Was ist, wenn Sie Pech haben und während der Sendung keine Aktivität stattfindet? (dh Sie sind in der Mitte der Drehung)
Sam
24

Hier ist ein weiteres Beispiel für eine AsyncTask, mit der a Fragmentzur Änderung der Laufzeitkonfiguration (z. B. wenn der Benutzer den Bildschirm dreht) verwendet wird setRetainInstance(true). Ein bestimmter (regelmäßig aktualisierter) Fortschrittsbalken wird ebenfalls gezeigt.

Das Beispiel basiert teilweise auf den offiziellen Dokumenten Beibehalten eines Objekts während einer Konfigurationsänderung .

In diesem Beispiel ist die Arbeit, die einen Hintergrund-Thread erfordert, das bloße Laden eines Bildes aus dem Internet in die Benutzeroberfläche.

Alex Lockwood scheint recht zu haben, dass es eine bewährte Methode ist, Änderungen der Laufzeitkonfiguration mit AsyncTasks mithilfe eines "beibehaltenen Fragments" zu verarbeiten. onRetainNonConfigurationInstance()wird in Lint in Android Studio veraltet. Die offiziellen Dokumente warnen uns davor android:configChanges, von der Handhabung der Konfigurationsänderung selbst ...

Wenn Sie die Konfigurationsänderung selbst vornehmen, kann es viel schwieriger werden, alternative Ressourcen zu verwenden, da das System sie nicht automatisch für Sie anwendet. Diese Technik sollte als letzter Ausweg betrachtet werden, wenn Sie Neustarts aufgrund einer Konfigurationsänderung vermeiden müssen, und wird für die meisten Anwendungen nicht empfohlen.

Dann gibt es die Frage, ob man überhaupt eine AsyncTask für den Hintergrund-Thread verwenden soll.

Die offizielle Referenz für AsyncTask warnt ...

AsyncTasks sollten idealerweise für kurze Vorgänge (höchstens einige Sekunden) verwendet werden. Wenn Sie Threads über einen längeren Zeitraum laufen lassen müssen, wird dringend empfohlen, die verschiedenen APIs zu verwenden, die vom Paket java.util.concurrent bereitgestellt werden, z Executor, ThreadPoolExecutor und FutureTask.

Alternativ kann ein Dienst, ein Loader (mit einem CursorLoader oder AsyncTaskLoader) oder ein Inhaltsanbieter verwendet werden, um asynchrone Vorgänge auszuführen.

Ich teile den Rest des Beitrags in:

  • Der Ablauf; und
  • Der gesamte Code für das obige Verfahren.

Der Ablauf

  1. Beginnen Sie mit einer grundlegenden AsyncTask als innere Klasse einer Aktivität (es muss keine innere Klasse sein, aber es wird wahrscheinlich bequem sein). Zu diesem Zeitpunkt verarbeitet die AsyncTask keine Änderungen der Laufzeitkonfiguration.

    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. Fügen Sie eine verschachtelte Klasse RetainedFragment hinzu, die die Fragement-Klasse erweitert und keine eigene Benutzeroberfläche hat. Fügen Sie dem onCreate-Ereignis dieses Fragments setRetainInstance (true) hinzu. Stellen Sie Verfahren zum Einstellen und Abrufen Ihrer Daten bereit.

    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. In onCreate () der äußersten Aktivitätsklasse wird das RetainedFragment behandelt: Verweisen Sie darauf, wenn es bereits vorhanden ist (falls die Aktivität neu gestartet wird). erstellen und hinzufügen, wenn es nicht existiert; Wenn es bereits vorhanden war, holen Sie sich Daten aus dem RetainedFragment und stellen Sie Ihre Benutzeroberfläche mit diesen Daten ein.

    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. Initiieren Sie die AsyncTask über die Benutzeroberfläche

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Fügen Sie einen bestimmten Fortschrittsbalken hinzu und codieren Sie ihn:

    • Fügen Sie dem UI-Layout einen Fortschrittsbalken hinzu.
    • Holen Sie sich einen Verweis darauf in der Aktivität oncreate ();
    • Machen Sie es zu Beginn und am Ende des Prozesses sichtbar und unsichtbar.
    • Definieren Sie den Fortschritt, der in onProgressUpdate an die Benutzeroberfläche gemeldet werden soll.
    • Ändern Sie den Parameter AsyncTask 2nd Generic von Void in einen Typ, der Fortschrittsaktualisierungen verarbeiten kann (z. B. Integer).
    • PublishProgress an regelmäßigen Stellen in doInBackground ().

Der gesamte Code für das obige Verfahren

Aktivitätslayout.

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

Die Aktivität mit: untergeordneter AsyncTask-Innenklasse; Unterklasse RetainedFragment innere Klasse, die Änderungen der Laufzeitkonfiguration verarbeitet (z. B. wenn der Benutzer den Bildschirm dreht); und einen bestimmten Fortschrittsbalken, der in regelmäßigen Abständen aktualisiert wird. ...

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 diesem Beispiel die Bibliotheksfunktion (auf die oben mit dem expliziten Paketpräfix com.example.standardapplibrary.android.Network verwiesen wird), die echte Arbeit leistet ...

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

Fügen Sie der AndroidManifest.xml alle Berechtigungen hinzu, die für Ihre Hintergrundaufgabe erforderlich sind ...

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

Fügen Sie Ihre Aktivität zu AndroidManifest.xml hinzu ...

<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>
John Bentley
quelle
Toll. Sie sollten einen Blog dazu schreiben.
Akh
2
@AKh. Wollen Sie damit vorschlagen, dass meine Antwort bei Stackoverflow zu viel Platz beansprucht?
John Bentley
1
Ich denke, er meint nur, dass du eine großartige Antwort hast und du solltest einen Blog schreiben! =) @ JohnBentley
Sandy D.
@ SandyD.yesterday Danke für die positive Interpretation. Ich hoffe, dass sie oder er das beabsichtigt haben.
John Bentley
Ich fand es auch eine großartige Antwort und interpretierte sie auch so. Sehr vollständige Antworten wie diese sind großartig !!
LeonardoSibela
3

Kürzlich habe ich hier eine gute Lösung gefunden . Es basiert auf dem Speichern eines Aufgabenobjekts über RetainConfiguration. Meiner Ansicht nach ist die Lösung sehr elegant und ich habe begonnen, sie zu verwenden. Sie müssen nur Ihre Asynctask aus der Basetask verschachteln und das ist alles.

Yury
quelle
Vielen Dank für diese interessante Antwort. Es ist eine gute Lösung zusätzlich zu den in der entsprechenden Frage genannten.
Caw
5
Leider verwendet diese Lösung veraltete Methoden.
Damien
3

Basierend auf der Antwort von @Alex Lockwood und den Antworten von @William & @quickdraw mcgraw in diesem Beitrag: Wie man mit Handler-Nachrichten umgeht, wenn Aktivität / Fragment angehalten wird , habe ich eine generische Lösung geschrieben.

Auf diese Weise wird die Rotation behandelt, und wenn die Aktivität während der Ausführung der asynchronen Aufgabe in den Hintergrund tritt, erhält die Aktivität die Rückrufe (onPreExecute, onProgressUpdate, onPostExecute & onCancelled), sobald sie fortgesetzt wird, sodass keine IllegalStateException ausgelöst wird (siehe Handhabung des Handlers) Nachrichten, wenn Aktivität / Fragment angehalten wird ).

Es wäre großartig, dasselbe zu haben, aber mit generischen Argumenttypen wie einer AsyncTask (z. B. AsyncTaskFragment <Parameter, Fortschritt, Ergebnis>), aber ich habe es nicht schnell geschafft und im Moment keine Zeit. Wenn jemand die Verbesserung vornehmen möchte, fühlen Sie sich bitte frei!

Der Code:

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
    /* ------------------------------------------------------------------------------------------ */
}

Sie benötigen den 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);
}

Beispielnutzung:

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;
            }
        }
    }
}
Tim Autin
quelle
3

Hierfür können Sie Loader verwenden. Überprüfen Sie Doc hier

PPD
quelle
2
Lader sind ab Android API 28 veraltet (wie der Link zeigt).
Sumit
Lader sind nicht veraltet, nur wie Sie sie nennen, hat sich geändert
EdgeDev
2

Für diejenigen, die Fragmenten ausweichen möchten, können Sie die AsyncTask bei Änderungen der Ausrichtung mithilfe von onRetainCustomNonConfigurationInstance () und einigen Verkabelungen beibehalten .

(Beachten Sie, dass diese Methode die Alternative zu der veralteten onRetainNonConfigurationInstance () ist. )

Diese Lösung scheint jedoch nicht häufig erwähnt zu werden. Ich habe ein einfaches Laufbeispiel geschrieben, um es zu veranschaulichen.

Prost!

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

Ich habe eine Bibliothek implementiert , die Probleme mit der Unterbrechung und Wiederherstellung von Aktivitäten lösen kann, während Ihre Aufgabe ausgeführt wird.

Sie sollten AsmykPleaseWaitTaskund implementieren AsmykBasicPleaseWaitActivity. Ihre Aktivität und Hintergrundaufgabe funktionieren einwandfrei, auch wenn Sie den Bildschirm drehen und zwischen Anwendungen wechseln

Mabramyan
quelle
-9

SCHNELLE UMARBEIT (nicht empfohlen)

Um zu vermeiden, dass eine Aktivität zerstört und selbst erstellt wird, müssen Sie Ihre Aktivität in der Manifestdatei deklarieren: android: configChanges = "Ausrichtung | Tastaturversteckt | Bildschirmgröße"

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

Wie in den Dokumenten erwähnt

Die Bildschirmausrichtung hat sich geändert - der Benutzer hat das Gerät gedreht.

Hinweis: Wenn Ihre Anwendung auf API-Level 13 oder höher abzielt (wie durch die Attribute minSdkVersion und targetSdkVersion deklariert), sollten Sie auch die Konfiguration "screenSize" deklarieren, da sie sich auch ändert, wenn ein Gerät zwischen Hoch- und Querformat wechselt.

Choletski
quelle
1
Dies wird am besten vermieden. developer.android.com/guide/topics/resources/… "Hinweis: Wenn Sie die Konfigurationsänderung selbst vornehmen, kann die Verwendung alternativer Ressourcen erheblich erschwert werden, da das System sie nicht automatisch für Sie anwendet. Diese Technik sollte als letzte betrachtet werden Resort, wenn Sie Neustarts aufgrund einer Konfigurationsänderung vermeiden müssen und dies für die meisten Anwendungen nicht empfohlen wird. "
David