Лучшая практика: AsyncTask во время смены ориентации

151

AsyncTask Отличная вещь для запуска сложных задач в другом потоке.

Но когда происходит изменение ориентации или другое изменение конфигурации во время AsyncTaskработы, ток Activityуничтожается и перезапускается. И когда экземпляр AsyncTaskсвязан с этим действием, он завершается неудачно и вызывает окно сообщения «принудительное закрытие».

Итак, я ищу какую-то «лучшую практику», чтобы избежать этих ошибок и предотвратить сбой AsyncTask.

То, что я видел до сих пор:

  • Отключите изменения ориентации. (Конечно, не так, как вы должны это делать.)
  • Позволить задаче выжить и обновить ее новым экземпляром действия через onRetainNonConfigurationInstance
  • Просто отмените задачу, когда Activityобъект уничтожен, и перезапустите его, когда объект Activityбудет создан снова.
  • Связывание задачи с классом приложения вместо экземпляра действия.
  • Какой-то метод, используемый в проекте "shelves" (через onRestoreInstanceState)

Некоторые примеры кода:

Android AsyncTasks во время поворота экрана, часть I и часть II

ShelvesActivity.java

Можете ли вы помочь мне найти лучший подход, который наилучшим образом решает проблему и также прост в реализации? Сам код также важен, так как я не знаю, как решить это правильно.

каркать
источник
Есть дубликат, проверьте это stackoverflow.com/questions/4584015/… .
TeaCupApp
Это из блога Марка Мерфи ... AsyncTask и ScreenRotation могут помочь ... ссылка
Gopal
Хотя это старый пост, но это IMO, гораздо проще (и лучше?) Подход.
DroidDev
Мне просто интересно, почему документация не говорит о таких очень тривиальных ситуациях.
Sreekanth Karumanaghat

Ответы:

140

Как НЕ использовать android:configChangesдля решения этой проблемы. Это очень плохая практика.

Как НЕ использовать Activity#onRetainNonConfigurationInstance()либо. Это менее модульный и не очень подходящий для Fragmentприложений на основе.

Вы можете прочитать мою статью, описывающую, как обрабатывать изменения конфигурации, используя сохраненные Fragments. Это решает проблему сохранения AsyncTaskизменения поворота поперек. Вы в основном нужно для размещения AsyncTaskвнутри Fragment, вызов setRetainInstance(true)на Fragment, и сообщить о AsyncTask«s прогресса / результаты обратно это Activityчерез стопорное Fragment.

Алекс Локвуд
источник
26
Хорошая идея, но не все используют фрагменты. Существует много унаследованного кода, написанного задолго до того, как фрагменты были опцией.
Скотт Биггс
14
@ScottBiggs Фрагменты доступны через библиотеку поддержки вплоть до Android 1.6. И не могли бы вы привести пример некоего устаревшего кода, который все еще активно используется, и который затруднит использование фрагментов библиотеки поддержки? Потому что я, честно говоря, не думаю, что это проблема.
Алекс Локвуд
4
@tactoth Я не чувствовал необходимости решать эти проблемы в своем ответе, так как 99,9% людей больше не используют TabActivity. Честно говоря, я не уверен, почему мы даже говорим об этом ... все согласны Fragmentс тем, что это путь. :)
Алекс Локвуд
2
Что если AsyncTask должен вызываться из вложенного фрагмента?
Эдуардо Наведа
3
@AlexLockwood - «все согласны с тем, что фрагменты - это путь». Разработчики в Squared не согласны!
JBeckton
36

Обычно я решаю эту проблему, когда мои AsyncTasks запускают Intents в обратном вызове .onPostExecute (), чтобы они не модифицировали Activity, которая их запускала напрямую. Действия слушают эти трансляции с динамическими BroadcastReceivers и действуют соответственно.

Таким образом, AsyncTasks не нужно заботиться о конкретном экземпляре Activity, который обрабатывает их результат. Они просто «кричат», когда они закончили, и если в это время активность (активная и сфокусированная / находится в возобновленном состоянии), которая заинтересована в результатах задачи, она будет обработана.

Это включает в себя немного больше накладных расходов, поскольку среда выполнения должна обрабатывать трансляцию, но я обычно не против. Я думаю, что использование LocalBroadcastManager вместо общесистемной системы по умолчанию немного ускоряет процесс.

Жомбор Эрдёди-Надь
источник
6
если бы вы могли добавить пример к ответу, это было бы более полезно
Sankar V
1
Я думаю, что это решение, которое предлагает меньше связи между действиями и фрагментами
Роджер Гарзон Ньето
7
Это может быть частью решения, но не похоже, что оно решит проблему воссоздания AsyncTask после изменения ориентации.
Мигель
4
Что делать, если вам не повезло, и во время трансляции не было никакой активности? (то есть вы в середине поворота)
Сэм
24

Вот еще один пример AsyncTask, который использует Fragmentдля обработки изменений конфигурации во время выполнения (например, когда пользователь поворачивает экран) setRetainInstance(true). Определенный (регулярно обновляемый) индикатор выполнения также демонстрируется.

Этот пример частично основан на официальных документах « Сохранение объекта во время изменения конфигурации» .

В этом примере работа, требующая фонового потока, заключается в простой загрузке изображения из Интернета в пользовательский интерфейс.

Алекс Локвуд, по-видимому, прав в том, что когда дело доходит до обработки изменений конфигурации во время выполнения с помощью AsyncTasks, использование «сохраненного фрагмента» является наилучшей практикой. onRetainNonConfigurationInstance()осуждается в Lint, в Android Studio. Официальные документы предупреждают нас, используя android:configChanges, от Обработки Изменения Собственности , ...

Обработка изменений конфигурации самостоятельно может значительно усложнить использование альтернативных ресурсов, поскольку система не применяет их автоматически за вас. Этот метод следует рассматривать как последнее средство, когда необходимо избегать перезапусков из-за изменения конфигурации, и он не рекомендуется для большинства приложений.

Тогда возникает вопрос, следует ли вообще использовать AsyncTask для фонового потока.

Официальная ссылка на AsyncTask предупреждает ...

AsyncTasks в идеале следует использовать для коротких операций (максимум несколько секунд). Если вам нужно, чтобы потоки работали в течение длительных периодов времени, настоятельно рекомендуется использовать различные API, предоставляемые пакетом java.util.concurrent, такие как Executor, 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. Добавьте вложенный класс RetainedFragment, который расширяет класс 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 () внешнего класса Activity обрабатывает RetainedFragment: ссылается на него, если он уже существует (в случае, если Activity перезапускается); создать и добавить его, если он не существует; Затем, если он уже существует, получите данные из RetainedFragment и установите свой интерфейс с этими данными.

    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).
    • publishProgress в обычных точках в 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; вложенный класс RetainedFragment, который обрабатывает изменения конфигурации во время выполнения (например, когда пользователь поворачивает экран); и определенное обновление индикатора на регулярной основе. ...

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.yesterday Спасибо за позитивную интерпретацию. Я надеюсь, что она или он намеревались это.
Джон Бентли
Я также подумал, что это потрясающий ответ, и я тоже так его интерпретировал. Очень полные ответы, такие как это здорово !!
ЛеонардоСибела
3

Недавно я нашел хорошее решение здесь . Он основан на сохранении объекта задачи через RetainConfiguration. На мой взгляд, решение очень элегантное, и я начал его использовать. Вам нужно просто вложить свою асинктаску из базовой задачи, и все.

Юрий
источник
Большое спасибо за этот интересный ответ. Это хорошее решение в дополнение к тем, которые упомянуты в соответствующем вопросе.
Caw
5
К сожалению, это решение использует устаревшие методы.
Дэмьен
3

Основываясь на ответе @Alex Lockwood и ответах @William & @quickdraw mcgraw на этот пост: Как обрабатывать сообщения обработчика, когда действие / фрагмент приостановлен , я написал общее решение.

Таким образом, ротация обрабатывается, и если действие переходит в фоновый режим во время выполнения асинхронной задачи, действие получит обратные вызовы (onPreExecute, onProgressUpdate, onPostExecute & onCancelled) после возобновления, поэтому никакое IllegalStateException не будет выброшено (см. Как обрабатывать обработчик). сообщения, когда активность / фрагмент приостановлен ).

Было бы здорово иметь такие же, но с общими типами аргументов, такими как 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

Вы можете использовать Loaders для этого. Проверьте Док здесь

PPD
источник
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();
    }
}
cgrenzel
источник
0

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

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

mabramyan
источник
-9

БЫСТРАЯ РАБОТА (не рекомендуется)

Чтобы избежать действия, которое нужно уничтожить и создать самостоятельно, нужно объявить свою деятельность в файле манифеста: android: configChanges = "direction | keyboardHidden | screenSize

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

Как уже упоминалось в документах

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

Примечание. Если ваше приложение предназначено для уровня API 13 или выше (как заявлено атрибутами minSdkVersion и targetSdkVersion), вам также следует объявить конфигурацию «screenSize», поскольку она также изменяется, когда устройство переключается между книжной и альбомной ориентациями.

Choletski
источник
1
Этого лучше всего избегать. developer.android.com/guide/topics/resources/… "Примечание. Обработка изменений конфигурации самостоятельно может значительно усложнить использование альтернативных ресурсов, поскольку система не применяет их автоматически для вас. Этот метод следует считать последним прибегайте, когда вам необходимо избегать перезапусков из-за изменения конфигурации и не рекомендуется для большинства приложений. "
Дэвид