Najlepsza praktyka: AsyncTask podczas zmiany orientacji


151

AsyncTask wykonywanie złożonych zadań w innym wątku to świetna sprawa.

Ale gdy nastąpi zmiana orientacji lub inna zmiana konfiguracji, gdy AsyncTasknadal działa, prąd Activityjest niszczony i restartowany. Ponieważ instancja programu AsyncTaskjest połączona z tym działaniem, kończy się niepowodzeniem i powoduje wyświetlenie okna komunikatu „wymuś zamknięcie”.

Dlatego szukam jakiejś „najlepszej praktyki”, aby uniknąć tych błędów i zapobiec niepowodzeniu AsyncTask.

Do tej pory widziałem:

  • Wyłącz zmiany orientacji (na pewno nie jest to sposób, w jaki powinieneś sobie z tym poradzić).
  • Pozostawienie zadania na przetrwanie i zaktualizowanie go o nową instancję działania za pośrednictwem onRetainNonConfigurationInstance
  • Po prostu anuluj zadanie, gdy Activityzostanie zniszczone i uruchom je ponownie, gdy Activityzostanie ponownie utworzone.
  • Powiązanie zadania z klasą aplikacji zamiast z instancją działania.
  • Jakaś metoda używana w projekcie „półki” (przez onRestoreInstanceState)

Niektóre przykłady kodu:

Android AsyncTasks podczas obracania ekranu, część I i część II

ShelvesActivity.java

Czy możesz mi pomóc znaleźć najlepsze podejście, które najlepiej rozwiązuje problem i jest łatwe do wdrożenia? Sam kod jest również ważny, ponieważ nie wiem, jak to poprawnie rozwiązać.


Jest duplikat, sprawdź ten stackoverflow.com/questions/4584015/… .
TeaCupApp

To pochodzi z bloga Marka Murphy'ego ... AsyncTask i ScreenRotation mogą pomóc ... link
Gopal.

Chociaż jest to stary post, ale ten IMO jest znacznie łatwiejszym (i lepszym?) Podejściem.
DroidDev

Zastanawiam się tylko, dlaczego dokumentacja nie mówi o tak trywialnych sytuacjach.
Sreekanth Karumanaghat

Odpowiedzi:


140

Czy nie używać android:configChangesw celu rozwiązania tego problemu. To bardzo zła praktyka.

Czy nie używać Activity#onRetainNonConfigurationInstance()albo. Jest to mniej modułowe i nie nadaje się do Fragmentaplikacji opartych na podstawach.

Możesz przeczytać mój artykuł opisujący, jak radzić sobie ze zmianami konfiguracji przy użyciu zachowanych plików Fragments. AsyncTaskŁadnie rozwiązuje problem utrzymywania zmiany w poprzek obrotu. Zasadniczo musisz hostować swoje AsyncTaskwnętrze Fragment, dzwonić setRetainInstance(true)do niego Fragmenti zgłaszać AsyncTaskpostępy / wyniki z powrotem do niego Activityprzez zachowane Fragment.


26
Niezły pomysł, ale nie wszyscy używają Fragmentów. Istnieje wiele starszego kodu napisanego na długo przed pojawieniem się fragmentów.
Scott Biggs

14
Fragmenty @ScottBiggs są dostępne za pośrednictwem biblioteki pomocy technicznej aż do Androida 1.6. Czy możesz podać przykład jakiegoś starszego kodu, który jest nadal aktywnie używany, a który miałby problemy z używaniem fragmentów biblioteki wsparcia? Bo szczerze nie sądzę, żeby to był problem.
Alex Lockwood

4
@tactoth Nie czułem potrzeby poruszania tych kwestii w mojej odpowiedzi, ponieważ 99,9% ludzi już nie używa TabActivity. Szczerze mówiąc, nie jestem pewien, dlaczego w ogóle o tym rozmawiamy ... wszyscy zgadzają się, że Fragmentjest to właściwy kierunek. :)
Alex Lockwood

2
Co się stanie, jeśli AsyncTask musi zostać wywołany z zagnieżdżonego fragmentu?
Eduardo Naveda

3
@AlexLockwood - „wszyscy zgadzają się, że Fragmenty są do zrobienia”. Devs w Squared nie zgodzą się!
JBeckton

36

Zwykle rozwiązuję ten problem, mając intencje emisji ognia AsyncTasks w wywołaniu zwrotnym .onPostExecute (), więc nie modyfikują one działania, które je bezpośrednio uruchomiło. Aktywności słuchają tych transmisji z dynamicznymi odbiornikami BroadcastReceivers i odpowiednio reagują.

W ten sposób AsyncTasks nie muszą przejmować się konkretną instancją Activity, która obsługuje ich wynik. Po prostu „krzyczą”, kiedy skończą, a jeśli jakieś działanie jest w tym czasie (jest aktywne i skoncentrowane / jest w stanie wznowienia), które jest zainteresowane wynikami zadania, zostanie ono obsłużone.

Wiąże się to z nieco większym obciążeniem, ponieważ środowisko wykonawcze musi obsługiwać transmisję, ale zwykle nie mam nic przeciwko. Myślę, że użycie LocalBroadcastManager zamiast domyślnego systemu w całym systemie nieco przyspiesza.


6
jeśli możesz dodać przykład do odpowiedzi, byłoby bardziej pomocne
Sankar V

1
Myślę, że jest to rozwiązanie, które oferuje mniej sprzężeń między działaniami i fragmentami
Roger Garzon Nieto

7
Może to być częścią rozwiązania, ale wydaje się, że nie rozwiązałoby to problemu z odtwarzaniem AsyncTask po zmianie orientacji.
miguel

4
A co, jeśli nie masz szczęścia i podczas transmisji nie ma żadnej aktywności? (tzn. jesteś w połowie rotacji)
Sam,

24

Oto kolejny przykład AsyncTask, który używa a Fragmentdo obsługi zmian konfiguracji środowiska uruchomieniowego (na przykład, gdy użytkownik obraca ekran) z setRetainInstance(true). Pokazany jest również określony (regularnie aktualizowany) pasek postępu.

Przykład jest częściowo oparty na oficjalnej dokumentacji Zachowanie obiektu podczas zmiany konfiguracji .

W tym przykładzie praca wymagająca wątku w tle polega na samym załadowaniu obrazu z Internetu do interfejsu użytkownika.

Wydaje się, że Alex Lockwood ma rację, że jeśli chodzi o obsługę zmian konfiguracji środowiska wykonawczego za pomocą AsyncTasks, najlepszym rozwiązaniem jest użycie „Zachowanego fragmentu”. onRetainNonConfigurationInstance()zostaje uznany za przestarzały w Lint w Android Studio. Oficjalna dokumentacja ostrzega nas przed używaniem android:configChanges, od samodzielnej obsługi zmiany konfiguracji , ...

Samodzielna obsługa zmiany konfiguracji może znacznie utrudnić korzystanie z alternatywnych zasobów, ponieważ system nie zastosuje ich automatycznie. Technikę tę należy traktować jako ostateczność, gdy należy unikać ponownego uruchamiania z powodu zmiany konfiguracji i nie jest zalecana dla większości aplikacji.

Następnie pojawia się kwestia, czy w ogóle należy używać AsyncTask dla wątku w tle.

Oficjalny wniosek o AsyncTask ostrzega ...

AsyncTasks powinno być idealnie używane do krótkich operacji (najwyżej kilka sekund). Jeśli chcesz, aby wątki działały przez długi czas, zdecydowanie zaleca się użycie różnych interfejsów API dostarczonych przez pakiet java.util.concurrent, takich jak Executor, ThreadPoolExecutor i FutureTask.

Alternatywnie można użyć usługi, programu ładującego (przy użyciu CursorLoader lub AsyncTaskLoader) lub dostawcy treści do wykonywania operacji asynchronicznych.

Resztę postu dzielę na:

  • Procedura; i
  • Cały kod powyższej procedury.

Procedura

  1. Zacznij od podstawowego AsyncTask jako wewnętrznej klasy działania (nie musi to być klasa wewnętrzna, ale prawdopodobnie będzie to wygodne). Na tym etapie AsyncTask nie obsługuje zmian konfiguracji środowiska wykonawczego.

    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. Dodaj zagnieżdżoną klasę RetainedFragment, która rozszerza klasę Fragement i nie ma własnego interfejsu użytkownika. Dodaj setRetainInstance (true) do zdarzenia onCreate tego fragmentu. Zapewnij procedury ustawiania i pobierania danych.

    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. W najbardziej zewnętrznej klasie Activity, onCreate () obsługuje RetainedFragment: Odwołaj się do niego, jeśli już istnieje (w przypadku ponownego uruchamiania działania); utwórz i dodaj, jeśli nie istnieje; Następnie, jeśli już istniał, pobierz dane z RetainedFragment i ustaw interfejs użytkownika przy użyciu tych danych.

    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. Zainicjuj AsyncTask z interfejsu użytkownika

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Dodaj i zakoduj określony pasek postępu:

    • Dodaj pasek postępu do układu interfejsu użytkownika;
    • Uzyskaj do niego odniesienie w działaniu oncreate ();
    • Spraw, aby był widoczny i niewidoczny na początku i na końcu procesu;
    • Zdefiniuj postęp raportowania do interfejsu użytkownika w onProgressUpdate.
    • Zmień parametr AsyncTask 2nd Generic z Void na typ, który może obsługiwać aktualizacje postępu (np. Integer).
    • publikujProgress w regularnych punktach w doInBackground ().

Cały kod powyższej procedury

Układ zajęć.

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

Działanie z: podklasą klasy wewnętrznej AsyncTask; podklasa klasy wewnętrznej RetainedFragment, która obsługuje zmiany konfiguracji środowiska wykonawczego (np. gdy użytkownik obraca ekran); i określony pasek postępu aktualizowany w regularnych odstępach czasu. ...

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

W tym przykładzie funkcja biblioteki (wymieniona powyżej z wyraźnym prefiksem pakietu com.example.standardapplibrary.android.Network), która naprawdę działa ...

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

Dodaj wszelkie uprawnienia wymagane przez zadanie w tle do pliku AndroidManifest.xml ...

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

Dodaj swoją aktywność do 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>

Wspaniały. Powinieneś napisać o tym bloga.
Ach,

2
@AKh. Chcesz zasugerować, że moja odpowiedź zajmuje zbyt dużo miejsca w Stackoverflow?
John Bentley,

1
Myślę, że po prostu ma na myśli, że masz świetną odpowiedź i powinieneś napisać bloga! =) @JohnBentley
Sandy D.

@ SandyD.yesterday Dzięki za pozytywną interpretację. Mam nadzieję, że ona lub on tego zamierzał.
John Bentley,

Pomyślałem też, że to świetna odpowiedź i tak to zinterpretowałem. Takie bardzo kompletne odpowiedzi są świetne!
LeonardoSibela

3

Niedawno znalazłem rozwiązanie dobre tutaj . Opiera się na zapisaniu obiektu zadania za pośrednictwem RetainConfiguration. Z mojego punktu widzenia rozwiązanie jest bardzo eleganckie i jak dla mnie zacząłem z niego korzystać. Wystarczy zagnieździć swoje zadanie asynchroniczne z zadania podstawowego i to wszystko.


Bardzo dziękuję za tę interesującą odpowiedź. To dobre rozwiązanie oprócz tych wymienionych w powiązanym pytaniu.
krakaj

5
Niestety, to rozwiązanie wykorzystuje przestarzałe metody.
Damien,

3

Na podstawie odpowiedzi @Alex Lockwood i odpowiedzi @William & @quickdraw mcgraw w tym poście: Jak obsługiwać wiadomości obsługi, gdy aktywność / fragment jest wstrzymany , napisałem ogólne rozwiązanie.

W ten sposób rotacja jest obsługiwana, a jeśli aktywność przejdzie w tło podczas wykonywania zadania asynchronicznego, po wznowieniu działanie otrzyma wywołania zwrotne (onPreExecute, onProgressUpdate, onPostExecute i onCancelled), więc nie zostanie zgłoszony wyjątek IllegalStateException (zobacz Jak obsługiwać Handler wiadomości, gdy aktywność / fragment jest wstrzymany ).

Byłoby wspaniale mieć to samo, ale z ogólnymi typami argumentów, jak AsyncTask (np: AsyncTaskFragment <Params, Progress, Result>), ale nie udało mi się to zrobić szybko i nie mam w tej chwili czasu. Jeśli ktoś chce coś ulepszyć, nie krępuj się!

Kod:

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

Będziesz potrzebować 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);
}

Przykładowe użycie:

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


2

Dla tych, którzy chcą unikać fragmentów, możesz zachować AsyncTask działający przy zmianach orientacji za pomocą onRetainCustomNonConfigurationInstance () i okablowania.

(Zwróć uwagę, że ta metoda jest alternatywą dla przestarzałej metody onRetainNonConfigurationInstance () ).

Wydaje się, że to rozwiązanie nie jest jednak często wymieniane. Aby zilustrować, napisałem prosty przykład działania.

Twoje zdrowie!

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

Wdrożyłem bibliotekę, która może rozwiązać problemy z wstrzymywaniem aktywności i odtwarzaniem podczas wykonywania zadania.

Powinieneś wdrożyć AsmykPleaseWaitTaski AsmykBasicPleaseWaitActivity. Twoja aktywność i zadanie w tle będą działać dobrze, nawet jeśli będziesz obracać ekran i przełączać się między aplikacjami


-9

SZYBKIE OBEJŚCIE (niezalecane)

Aby uniknąć zniszczenia i utworzenia działania, należy zadeklarować swoją aktywność w pliku manifestu: android: configChanges = "orientacja | keyboardHidden | screenSize

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

Jak wspomniano w dokumentach

Zmieniła się orientacja ekranu - użytkownik obrócił urządzenie.

Uwaga: jeśli aplikacja jest przeznaczona dla interfejsu API na poziomie 13 lub wyższym (zgodnie z deklaracją w atrybutach minSdkVersion i targetSdkVersion), należy również zadeklarować konfigurację „screenSize”, ponieważ zmienia się ona również, gdy urządzenie przełącza się między orientacją pionową i poziomą.


1
Najlepiej tego unikać. developer.android.com/guide/topics/resources/… ”Uwaga: samodzielna zmiana konfiguracji może znacznie utrudnić korzystanie z alternatywnych zasobów, ponieważ system nie stosuje ich automatycznie. Tę technikę należy traktować jako ostatnią uciekaj się, gdy musisz unikać ponownego uruchamiania z powodu zmiany konfiguracji i nie jest to zalecane dla większości aplikacji. ”
David
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.