Praktik terbaik: AsyncTask selama perubahan orientasi

151

AsyncTask adalah hal yang hebat untuk menjalankan tugas kompleks di utas lainnya.

Tetapi ketika ada perubahan orientasi atau perubahan konfigurasi lain saat AsyncTaskmasih berjalan, saat Activityini dihancurkan dan restart. Dan ketika instance AsyncTaskterhubung ke aktivitas itu, ia gagal dan menyebabkan jendela pesan "tutup paksa".

Jadi, saya mencari semacam "praktik terbaik" untuk menghindari kesalahan ini dan mencegah kegagalan AsyncTask.

Apa yang saya lihat sejauh ini adalah:

  • Nonaktifkan perubahan orientasi. (Pasti bukan cara Anda harus menangani ini.)
  • Membiarkan tugas bertahan dan memperbaruinya dengan instance aktivitas baru via onRetainNonConfigurationInstance
  • Membatalkan tugas saat Activitydimusnahkan dan memulai kembali saat tugas Activitydibuat kembali.
  • Mengikat tugas ke kelas aplikasi alih-alih instance aktivitas.
  • Beberapa metode yang digunakan dalam proyek "rak" (via onRestoreInstanceState)

Beberapa contoh kode:

Android AsyncTasks selama rotasi layar, Bagian I dan Bagian II

ShelvesActivity.java

Dapatkah Anda membantu saya menemukan pendekatan terbaik yang memecahkan masalah terbaik dan juga mudah diimplementasikan? Kode itu sendiri juga penting karena saya tidak tahu bagaimana menyelesaikannya dengan benar.

gak
sumber
Ada duplikat, periksa stackoverflow.com/questions/4584015/… ini .
TeaCupApp
Ini dari Mark Murphy Blog ... AsyncTask dan ScreenRotation mungkin membantu ... tautan
Gopal
Meskipun ini adalah pos lama, tetapi IMO ini , apakah pendekatan yang jauh lebih mudah (dan lebih baik?).
DroidDev
Saya hanya ingin tahu mengapa dokumentasi tidak berbicara tentang situasi yang sangat sepele.
Sreekanth Karumanaghat

Jawaban:

140

Apakah TIDAK menggunakan android:configChangesuntuk mengatasi masalah ini. Ini praktik yang sangat buruk.

Apakah TIDAK menggunakan Activity#onRetainNonConfigurationInstance()baik. Ini kurang modular dan tidak cocok untuk Fragmentaplikasi berbasis.

Anda dapat membaca artikel saya yang menjelaskan cara menangani perubahan konfigurasi menggunakan retained Fragments. Ini memecahkan masalah mempertahankan AsyncTaskperubahan rotasi yang baik. Pada dasarnya Anda perlu untuk menjadi tuan rumah Anda AsyncTaskdi dalam Fragment, panggilan setRetainInstance(true)pada Fragment, dan melaporkan AsyncTask's kemajuan / hasil kembali ke itu Activitymelalui dipertahankan Fragment.

Alex Lockwood
sumber
26
Ide bagus, tetapi tidak semua orang menggunakan Fragmen. Ada banyak kode lama yang ditulis jauh sebelum Fragmen menjadi pilihan.
Scott Biggs
14
@ScottBiggs Fragmen tersedia melalui perpustakaan dukungan sepanjang perjalanan kembali ke Android 1.6. Dan bisakah Anda memberikan contoh beberapa kode lama yang masih aktif digunakan yang akan kesulitan memanfaatkan fragmen pustaka pendukung? Karena saya jujur ​​tidak berpikir itu masalah.
Alex Lockwood
4
@tactoth Saya tidak merasa perlu untuk mengatasi masalah ini dalam jawaban saya, karena 99,9% orang tidak lagi menggunakan TabActivity. Sejujurnya, saya tidak yakin mengapa kita berbicara tentang ini ... semua orang setuju bahwa Fragmentini adalah cara untuk pergi. :)
Alex Lockwood
2
Bagaimana jika AsyncTask harus dipanggil dari Fragmen bersarang?
Eduardo Naveda
3
@AlexLockwood - "semua orang setuju bahwa Fragmen adalah cara untuk pergi.". Para Dev di Squared tidak akan setuju!
JBeckton
36

Saya biasanya menyelesaikan ini dengan meminta AsyncTasks saya menyalakan Intents broadcast di .onPostExecute (), jadi mereka tidak mengubah Kegiatan yang memulai secara langsung. Kegiatan mendengarkan siaran ini dengan BroadcastReceivers dinamis dan bertindak sesuai.

Dengan cara ini, AsyncTasks tidak perlu peduli dengan instance Activity spesifik yang menangani hasilnya. Mereka hanya "berteriak" ketika mereka selesai, dan jika suatu Kegiatan ada di sekitar waktu itu (aktif dan fokus / dalam keadaan dilanjutkan) yang tertarik dengan hasil tugas, maka itu akan ditangani.

Ini melibatkan sedikit lebih banyak overhead, karena runtime perlu menangani siaran, tetapi saya biasanya tidak keberatan. Saya pikir menggunakan LocalBroadcastManager daripada lebar sistem default yang mempercepat semuanya.

Zsombor Erdődy-Nagy
sumber
6
jika Anda dapat menambahkan contoh pada jawaban itu akan lebih membantu
Sankar V
1
Saya pikir ini adalah solusi yang menawarkan lebih sedikit hubungan antara aktivitas dan fragmen
Roger Garzon Nieto
7
Ini mungkin menjadi bagian dari solusi tetapi sepertinya tidak akan menyelesaikan masalah AsyncTask yang diciptakan kembali setelah perubahan orientasi.
miguel
4
Bagaimana jika Anda kurang beruntung dan tidak ada aktivitas di sekitar selama siaran? (mis. Anda sedang putar tengah)
Sam
24

Berikut adalah contoh lain dari AsyncTask yang menggunakan a Fragmentuntuk menangani perubahan konfigurasi runtime (seperti ketika pengguna memutar layar) setRetainInstance(true). Bilah kemajuan yang ditentukan (diperbarui secara berkala) juga ditunjukkan.

Contohnya sebagian didasarkan pada dokumen resmi, Mempertahankan Objek Selama Perubahan Konfigurasi .

Dalam contoh ini pekerjaan yang membutuhkan utas latar belakang adalah hanya memuat gambar dari internet ke UI.

Alex Lockwood tampaknya benar bahwa ketika menangani perubahan konfigurasi runtime dengan AsyncTasks menggunakan "Retained Fragment" adalah praktik terbaik. onRetainNonConfigurationInstance()akan ditinggalkan di Lint, di Android Studio. Dokumen resmi memperingatkan kami untuk tidak menggunakan android:configChanges, dari Menangani Konfigurasi, Ubah Diri , ...

Menangani perubahan konfigurasi sendiri dapat membuatnya jauh lebih sulit untuk menggunakan sumber daya alternatif, karena sistem tidak secara otomatis menerapkannya untuk Anda. Teknik ini harus dianggap sebagai upaya terakhir ketika Anda harus menghindari restart karena perubahan konfigurasi dan tidak dianjurkan untuk sebagian besar aplikasi.

Lalu ada masalah apakah seseorang harus menggunakan AsyncTask sama sekali untuk utas latar belakang.

The referensi resmi untuk AsyncTask memperingatkan ...

AsyncTasks idealnya harus digunakan untuk operasi pendek (paling banyak beberapa detik.) Jika Anda perlu menjaga agar thread tetap berjalan untuk jangka waktu yang lama, sangat disarankan Anda menggunakan berbagai API yang disediakan oleh pacakge java.util.concurrent seperti Pelaksana, ThreadPoolExecutor dan FutureTask.

Atau orang dapat menggunakan layanan, loader (menggunakan CursorLoader atau AsyncTaskLoader), atau penyedia konten untuk melakukan operasi asinkron.

Saya memecah sisa posting menjadi:

  • Prosedur; dan
  • Semua kode untuk prosedur di atas.

Prosedur

  1. Mulailah dengan AsyncTask dasar sebagai kelas dalam dari suatu kegiatan (tidak perlu menjadi kelas dalam tetapi mungkin akan nyaman untuk dilakukan). Pada tahap ini AsyncTask tidak menangani perubahan konfigurasi 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. Tambahkan kelas bersarang RetainedFragment yang memperluas kelas Fragement dan tidak memiliki UI sendiri. Tambahkan setRetainInstance (true) ke acara onCreate dari fragmen ini. Berikan prosedur untuk mengatur dan mendapatkan data Anda.

    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. Di onCreate kelas aktivitas terluar () menangani RetainedFragment: Referensi itu jika sudah ada (dalam hal aktivitas me-restart); buat dan tambahkan jika tidak ada; Kemudian, jika sudah ada, dapatkan data dari RetainedFragment dan atur UI Anda dengan data itu.

    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. Memulai AsyncTask dari UI

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Tambahkan dan beri kode bilah kemajuan yang ditentukan:

    • Tambahkan bilah kemajuan ke tata letak UI;
    • Dapatkan referensi untuknya di Activity oncreate ();
    • Jadikan itu terlihat dan tidak terlihat pada awal dan akhir proses;
    • Tetapkan progres untuk melaporkan ke UI di onProgressUpdate.
    • Ubah parameter Generik AsyncTask ke-2 dari Void ke tipe yang dapat menangani pembaruan kemajuan (mis. Integer).
    • publikasikan Kemajuan pada titik reguler di doInBackground ().

Semua kode untuk prosedur di atas

Tata Letak Aktivitas.

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

Aktivitas dengan: subclassed AsyncTask inner class; kelas dalam RetainedFragment subclass yang menangani perubahan konfigurasi runtime (misalnya ketika pengguna memutar layar); dan pembaruan progress bar yang ditentukan secara berkala. ...

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

Dalam contoh ini fungsi perpustakaan (direferensikan di atas dengan awalan paket eksplisit com.example.standardapplibrary.android.Network) yang melakukan pekerjaan nyata ...

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

Tambahkan izin apa pun yang diperlukan tugas latar belakang Anda ke AndroidManifest.xml ...

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

Tambahkan aktivitas Anda ke 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>
John Bentley
sumber
Bagus. Anda harus menulis blog tentang ini.
Akh
2
@ Ah. Apakah Anda bermaksud menyarankan jawaban saya memakan terlalu banyak ruang di Stackoverflow?
John Bentley
1
Saya pikir dia hanya berarti Anda memiliki jawaban yang luar biasa & Anda harus menulis blog! =) @JohnBentley
Sandy D.
@ SandyD. Kemarin Terima kasih atas interpretasi positif. Saya berharap dia atau dia yang dimaksudkan itu.
John Bentley
Saya juga berpikir itu adalah jawaban yang luar biasa dan saya menafsirkannya seperti itu juga. Jawaban yang sangat lengkap seperti ini luar biasa !!
LeonardoSibela
3

Baru-baru ini, saya menemukan solusi yang bagus di sini . Ini didasarkan pada penyimpanan objek tugas melalui RetainConfiguration. Menurut saya, solusinya sangat elegan dan bagi saya, saya sudah mulai menggunakannya. Anda hanya perlu membuat sarang dari asynctask Anda dari basetask dan itu saja.

Yury
sumber
Terima kasih banyak atas jawaban yang menarik ini. Ini solusi yang bagus selain yang disebutkan dalam pertanyaan terkait.
gak
5
Sayangnya, solusi ini menggunakan metode yang sudah usang.
Damien
3

Berdasarkan jawaban @Alex Lockwood dan pada @William & @quickdraw mcgraw jawaban pada posting ini: Cara menangani pesan Handler ketika aktivitas / fragmen dijeda , saya menulis solusi generik.

Dengan cara ini rotasi ditangani, dan jika aktivitas berjalan ke latar belakang selama pelaksanaan tugas async, aktivitas akan menerima panggilan balik (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) setelah dilanjutkan, sehingga tidak ada IllegalStateException yang akan dilemparkan (lihat Cara menangani Handler pesan saat aktivitas / fragmen dijeda ).

Akan lebih baik memiliki hal yang sama tetapi dengan tipe argumen umum, seperti AsyncTask (misalnya: AsyncTaskFragment <Params, Progress, Result>), tetapi saya tidak berhasil melakukannya dengan cepat dan tidak punya waktu saat ini. Jika ada yang ingin melakukan perbaikan, silakan saja!

Kode:

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

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

Penggunaan sampel:

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

Anda dapat menggunakan Loader untuk ini. Periksa Doc di sini

PPD
sumber
2
Loader sekarang sudah tidak digunakan lagi di Android API 28 (seperti yang diceritakan oleh tautan).
Sumit
Loader tidak ditinggalkan, hanya bagaimana Anda memanggil mereka yang berubah
EdgeDev
2

Bagi mereka yang ingin menghindari Fragmen, Anda dapat mempertahankan AsyncTask berjalan pada perubahan orientasi menggunakan onRetainCustomNonConfigurationInstance () dan beberapa kabel.

(Perhatikan bahwa metode ini adalah alternatif untuk onRetainNonConfigurationInstance () ) yang sudah usang () ).

Sepertinya solusi ini tidak sering disebutkan. Saya menulis contoh menjalankan sederhana untuk menggambarkan.

Bersulang!

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

Saya telah menerapkan perpustakaan yang dapat memecahkan masalah dengan aktivitas jeda dan rekreasi saat tugas Anda sedang dijalankan.

Anda harus menerapkan AsmykPleaseWaitTaskdan AsmykBasicPleaseWaitActivity. Aktivitas aktivitas dan latar belakang Anda akan berfungsi dengan baik bahkan ketika Anda akan memutar layar dan beralih di antara aplikasi

mabramyan
sumber
-9

WORKAROUND CEPAT (tidak direkomendasikan)

Menghindari Aktivitas untuk dihancurkan dan membuat sendiri berarti mendeklarasikan aktivitas Anda dalam file manifes: android: configChanges = "orientasi | keyboardHidden | screenSize

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

Seperti yang disebutkan dalam dokumen

Orientasi layar telah berubah - pengguna telah memutar perangkat.

Catatan: Jika aplikasi Anda menargetkan API level 13 atau lebih tinggi (sebagaimana dideklarasikan oleh atribut minSdkVersion dan targetSdkVersion), maka Anda juga harus mendeklarasikan konfigurasi "screenSize", karena itu juga berubah ketika perangkat beralih antara orientasi potret dan lanskap.

Choletski
sumber
1
Ini sebaiknya dihindari. developer.android.com/guide/topics/resources/… "Catatan: Menangani perubahan konfigurasi sendiri dapat membuatnya jauh lebih sulit untuk menggunakan sumber daya alternatif, karena sistem tidak secara otomatis menerapkannya untuk Anda. Teknik ini harus dianggap sebagai yang terakhir. resor ketika Anda harus menghindari restart karena perubahan konfigurasi dan tidak dianjurkan untuk sebagian besar aplikasi. "
David