Найкраща практика: AsyncTask під час зміни орієнтації


151

AsyncTask це чудова річ, щоб запустити складні завдання в інший потік.

Але коли є зміна орієнтації або інша зміна конфігурації, поки AsyncTaskфункція все ще працює, струм Activityзнищується та перезапускається. Оскільки екземпляр AsyncTaskз’єднаний з цією діяльністю, він виходить з ладу і викликає вікно повідомлення "примусово закрити".

Отже, я шукаю якусь "найкращу практику", щоб уникнути цих помилок і не допустити відмови AsyncTask.

Що я бачив поки що:

  • Вимкнути зміни орієнтації. (Напевно, не так, як слід поводитися з цим.)
  • Дати завданням вижити та оновити його новим екземпляром діяльності через onRetainNonConfigurationInstance
  • Просто скасовуючи завдання, коли Activityзнищується, і перезавантажуєте його, коли Activityстворюється знову.
  • Прив’язування завдання до класу програми замість екземпляра діяльності.
  • Деякий метод, що використовується в проекті "полички" (через onRestoreInstanceState)

Деякі приклади коду:

Android AsyncTasks під час обертання екрана, частина I та частина II

ShelvesActivity.java

Чи можете ви допомогти мені знайти найкращий підхід, який найкраще вирішує цю проблему і простий у здійсненні? Сам код також важливий, оскільки я не знаю, як правильно це вирішити.


Дублікат є, перевірте цей stackoverflow.com/questions/4584015/… .
TeaCupApp

Це з блогу Марка Мерфі ... AsyncTask і ScreenRotation можуть допомогти ... посилання
Gopal

Хоча це старий пост, але цей ІМО - набагато простіший (і кращий?) Підхід.
DroidDev

Мені просто цікаво, чому документація не говорить про такі дуже тривіальні ситуації.
Sreekanth Karumanaghat

Відповіді:


140

Як НЕ використовувати android:configChangesдля вирішення цієї проблеми. Це дуже погана практика.

Як НЕ використовувати Activity#onRetainNonConfigurationInstance()небудь. Це менш модульно і не дуже підходить для Fragmentдодатків на базі даних.

Ви можете прочитати мою статтю, що описує, як обробляти зміни конфігурації за допомогою збережених Fragments. Це вирішує проблему збереження AsyncTaskзміни повороту на належному рівні. По суті, вам потрібно розмістити AsyncTaskвнутрішній запис Fragment, подзвонити setRetainInstance(true)на нього Fragmentта повідомити про AsyncTaskйого хід / результати назад Activityчерез збережене Fragment.


26
Хороша ідея, але не всі використовують фрагменти. Існує багато застарілих кодів, написаних задовго до того, як фрагменти були опцією.
Скотт Біггс

14
Фрагменти @ScottBiggs доступні через бібліотеку підтримки аж до Android 1.6. І чи можете ви навести приклад якогось застарілого коду, який все ще активно використовується, який може мати проблеми з використанням фрагментів бібліотеки підтримки? Тому що я, чесно кажучи, не думаю, що це питання.
Алекс Локвуд

4
@tactoth Я не відчував необхідності вирішувати ці питання у своїй відповіді, оскільки 99,9% людей більше не користуються TabActivity. Якщо чесно, я не впевнений, чому ми навіть говоримо про це ... всі згодні, що Fragmentце шлях. :)
Алекс Локвуд

2
Що робити, якщо AsyncTask потрібно викликати з вкладеного фрагмента?
Едуардо Наведа

3
@AlexLockwood - "всі згодні, що Фрагменти - це шлях". Дев’ї на Квадрат не погодилися б!
JBeckton

36

Зазвичай я вирішую це шляхом передачі інтенсив пожежної програми AsyncTasks у зворотній виклик .onPostExecute (), тому вони не змінюють активність, яка їх безпосередньо запустила. Діяльність слухає ці трансляції з динамічними широкомовними приймачами та діє відповідно.

Таким чином, AsyncTasks не повинні дбати про конкретний екземпляр Activity, який обробляє їх результат. Вони просто "кричать", коли вони закінчаться, і якщо Діяльність буде приблизно в цей час (активна і зосереджена / перебуває в її відновленому стані), яка зацікавлена ​​в результатах завдання, тоді вона буде оброблена.

Це пов'язано з трохи більшими накладними витратами, оскільки час виконання повинен обробляти трансляцію, але я зазвичай не проти. Я думаю, що використання LocalBroadcastManager замість системи за замовчуванням широка система трохи пришвидшує.


6
якщо ви можете додати приклад до відповіді, було б корисніше
Sankar V

1
Я думаю, що це рішення, яке пропонує менший зв’язок між діяльністю та фрагментами
Роджер Гарсон Нієто

7
Це може бути частиною рішення, але, схоже, це не вирішило б проблему відновлення AsyncTask після зміни орієнтації.
miguel

4
Що робити, якщо вам не пощастило, і жодна активність не відбувається під час трансляції? (тобто ви в середині обертання)
Сем

24

Ось ще один приклад AsyncTask, який використовує a Fragmentдля обробки змін конфігурації виконання (як коли користувач обертає екран) за допомогоюsetRetainInstance(true) . Також показана визначена (регулярно оновлювана) смужка прогресу.

Приклад частково базується на офіційних документах, що зберігають об’єкт під час зміни конфігурації .

У цьому прикладі робота, що вимагає фонової нитки, - це просто завантаження зображення з Інтернету в інтерфейс користувача.

Алекс Локвуд, здається, має рацію, що, якщо мова йде про обробку змін конфігурації виконання за допомогою AsyncTasks, використовуючи "Збережений фрагмент", це найкраща практика. onRetainNonConfigurationInstance()стає застарілим у Lint, в Android Studio. Офіційні документи попереджають нас від використання android:configChanges, від Handling зміни конфігурації Самостійно ...

Самі поводження із зміною конфігурації може значно ускладнити використання альтернативних ресурсів, оскільки система автоматично не застосовує їх до вас. Цю методику слід вважати крайнім засобом, коли потрібно уникати перезапуску через зміну конфігурації і не рекомендується для більшості програм.

Тоді виникає питання, чи варто взагалі використовувати AsyncTask для фонового потоку.

Офіційна посилання на AsyncTask попереджає ...

AsyncTasks в ідеалі слід використовувати для коротких операцій (максимум декілька секунд.) Якщо вам потрібно тримати потоки протягом тривалих періодів часу, настійно рекомендується використовувати різні API, надані java.util.concurrent pacakge, наприклад Виконавець, ThreadPoolExecutor та FutureTask.

Можна також скористатися сервісом, завантажувачем (за допомогою CursorLoader або AsyncTaskLoader) або постачальником вмісту для виконання асинхронних операцій.

Я розбиваю решту публікації на:

  • Порядок; і
  • Весь код вищевказаної процедури.

Порядок

  1. Почніть з базового AsyncTask як внутрішнього класу діяльності (він не повинен бути внутрішнім класом, але це, мабуть, буде зручно). На цьому етапі AsyncTask не обробляє зміни конфігурації часу виконання.

    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. Додайте вкладений клас RetainFragment, який розширює клас Fragement і не має власного інтерфейсу. Додайте setRetainInstance (true) до події onCreate цього фрагмента. Забезпечте процедури встановлення та отримання ваших даних.

    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. У найвіддаленішому класі активності onCreate () обробляти RetainFragment: посилайтеся на нього, якщо він вже існує (у випадку, якщо активність перезапускається); створити та додати його, якщо його не існує; Потім, якщо він вже існував, дістаньте дані з ReservedFragment і встановіть свій інтерфейс з тими даними.

    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. Запустіть AsyncTask з інтерфейсу користувача

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
  5. Додайте і кодуйте визначений рядок ходу:

    • Додайте до макета інтерфейсу панель прогресу;
    • Отримайте посилання на нього у Activity oncreate ();
    • Зробити це видимим і невидимим на початку та в кінці процесу;
    • Визначте хід звітування до інтерфейсу користувача у onProgressUpdate.
    • Змініть параметр AsyncTask 2nd Generic з Void на тип, який може обробляти оновлення прогресу (наприклад, Integer).
    • опублікувати прогрес у регулярних точках у doInBackground ().

Весь код вищевказаної процедури

Макет діяльності

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

Активність із: підкласовим внутрішнім класом AsyncTask; підклас внутрішнього класу RetainFragment, який обробляє зміни конфігурації виконання (наприклад, коли користувач повертає екран); і визначальне оновлення смуги прогресу, що оновлюється через рівні проміжки часу. ...

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

У цьому прикладі функція бібліотеки (на яку посилалося вище з явним префіксом пакета com.example.standardapplibrary.android.Network), яка виконує справжню роботу ...

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

Додайте до AndroidManifest.xml будь-які дозволи, необхідні для вашого фонового завдання ...

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

Додайте свою активність до 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>

Чудово. На цьому слід написати блог.
Ах

2
@AKh. Ви хочете сказати, що моя відповідь займає занадто багато місця на Stackoverflow?
Джон Бентлі

1
Я думаю, що він просто означає, що ти маєш приголомшливу відповідь, і ти повинен написати блог! =) @JohnBentley
Сенді Д.

@ SandyD. Вчора Дякую за позитивну інтерпретацію. Я сподіваюся, що він чи він це мали намір.
Джон Бентлі

Я також подумав, що це була дивовижна відповідь, і я її так і інтерпретував. Дуже повні відповіді, як ця, чудові !!
ЛеонардоСібела

3

В останній час , я знайшов гарне рішення тут . Він заснований на збереженні об'єкта завдання за допомогою програми RetainConfiguration. На мій погляд, рішення дуже елегантне, і для мене я почав його використовувати. Вам потрібно просто вкласти свій асинтакт з базових даних, і це все.


Дуже дякую за цю цікаву відповідь. Це гарне рішення на додаток до тих, що згадуються у відповідному питанні.
каре

5
На жаль, це рішення використовує застарілі методи.
Дамьєн

3

На основі відповіді @Alex Lockwood та на @William & @quickdraw mcgraw відповіді на це повідомлення: Як поводитися з повідомленнями обробника, коли діяльність / фрагмент призупинено , я написав загальне рішення.

Таким чином обробляється обертання, і якщо активність переходить на другий план під час виконання завдання асинхронізації, активність отримуватиме зворотні виклики (onPreExecute, onProgressUpdate, onPostExecute & onCancelated) після відновлення, тому не буде викинуто IllegalStateException (див. Як поводитися з Handler повідомлення, коли діяльність / фрагмент призупинено ).

Було б чудово мати те саме, але із загальними типами аргументів, як-от AsyncTask (наприклад: AsyncTaskFragment <Params, Progress, Result>), але мені не вдалося це зробити швидко і не маю часу на даний момент. Якщо хтось хоче зробити покращення, будь ласка, не соромтеся!

Код:

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

Вам знадобиться 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);
}

Використання зразка:

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

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

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

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

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

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

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

                            // Do your async stuff

                            return null;
                        }
                    });

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

    @Override
    public void onPreExecute(String fragmentTag) {}

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

    @Override
    public void onCancelled(String fragmentTag) {}

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

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}

3

Для цього можна використовувати навантажувачі. Перевірте документ тут


2
Навантажувачі тепер застарілі, як для Android API 28 (як вам скаже посилання).
Sumit

Навантажувачі не застаріли, змінилося лише те, як ви їх називаєте
EdgeDev

2

Для тих, хто хоче ухилитися від фрагментів, ви можете зберегти AsyncTask, що працює на змінах орієнтації, використовуючи OnRetainCustomNonConfigurationInstance () та деякі проводки.

(Зауважте, що цей метод є альтернативою застарілому OnRetainNonConfigurationInstance () ).

Схоже, це рішення не часто згадується. Я написав простий запущений приклад для ілюстрації.

Ура!

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

Я впровадив бібліотеку, яка може вирішити проблеми з паузою та відпочинком діяльності, поки ваше завдання виконується.

Ви повинні реалізувати AsmykPleaseWaitTaskі AsmykBasicPleaseWaitActivity. Ваша активність і фонове завдання спрацюють добре, навіть якщо ви будете обертати екран і переключатися між додатками


-9

ШВИДКА РОБОТА (не рекомендується)

Щоб уникнути діяльності, щоб знищити та створити себе - це оголосити свою активність у файлі маніфесту: android: configChanges = "орієнтація | клавіатураHidden | screenSize

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

Як згадується в документах

Орієнтація екрана змінилася - користувач повернув пристрій.

Примітка. Якщо ваша програма націлена на API рівня 13 або вище (як заявлено атрибутами minSdkVersion та targetSdkVersion), вам слід також оголосити конфігурацію "screenSize", оскільки вона також змінюється, коли пристрій перемикається між орієнтацією на портрет та ландшафт.


1
Цього найкраще уникати. developer.android.com/guide/topics/resources/… "Примітка. Самостійне оброблення конфігурації може значно ускладнити використання альтернативних ресурсів, оскільки система автоматично не застосовує їх до вас. Цей прийом слід вважати останнім вдайтеся, коли вам потрібно уникати перезавантажень через зміну конфігурації, і це не рекомендується для більшості програм. "
Девід
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.