Фрагменты Android.Сохранение AsyncTask во время поворота экрана или изменения конфигурации.

StackOverflow https://stackoverflow.com/questions/8417885

Вопрос

Я работаю над приложением для смартфона/планшета, использую только один APK и загружаю ресурсы по мере необходимости в зависимости от размера экрана. Кажется, лучшим выбором дизайна было использование фрагментов через ACL.

До сих пор это приложение работало нормально и основывалось только на активности.Это макет того, как я обрабатываю AsyncTasks и ProgressDialogs в Activity, чтобы они работали даже при повороте экрана или изменении конфигурации в процессе связи.

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

public class Login extends Activity {

    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.login);
        //SETUP UI OBJECTS
        restoreAsyncTask();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (pd != null) pd.dismiss();
        if (asyncLoginThread != null) return (asyncLoginThread);
        return super.onRetainNonConfigurationInstance();
    }

    private void restoreAsyncTask();() {
        pd = new ProgressDialog(Login.this);
        if (getLastNonConfigurationInstance() != null) {
            asyncLoginThread = (AsyncTask<String, Void, Boolean>) getLastNonConfigurationInstance();
            if (asyncLoginThread != null) {
                if (!(asyncLoginThread.getStatus()
                        .equals(AsyncTask.Status.FINISHED))) {
                    showProgressDialog();
                }
            }
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... args) {
            try {
                //Connect to WS, recieve a JSON/XML Response
                //Place it somewhere I can use it.
            } catch (Exception e) {
                return true;
            }
            return true;
        }

        protected void onPostExecute(Boolean result) {
            if (result) {
                pd.dismiss();
                //Handle the response. Either deny entry or launch new Login Succesful Activity
            }
        }
    }
}

Этот код работает нормально, у меня около 10 000 пользователей не жалуются, поэтому казалось логичным просто скопировать эту логику в новый дизайн на основе фрагментов, но, конечно, он не работает.

Вот LoginFragment:

public class LoginFragment extends Fragment {

    FragmentActivity parentActivity;
    static ProgressDialog pd;
    AsyncTask<String, Void, Boolean> asyncLoginThread;

    public interface OnLoginSuccessfulListener {
        public void onLoginSuccessful(GlobalContainer globalContainer);
    }

    public void onSaveInstanceState(Bundle outState){
        super.onSaveInstanceState(outState);
        //Save some stuff for the UI State
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setRetainInstance(true);
        //If I setRetainInstance(true), savedInstanceState is always null. Besides that, when loading UI State, a NPE is thrown when looking for UI Objects.
        parentActivity = getActivity();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            loginSuccessfulListener = (OnLoginSuccessfulListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnLoginSuccessfulListener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        RelativeLayout loginLayout = (RelativeLayout) inflater.inflate(R.layout.login, container, false);
        return loginLayout;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //SETUP UI OBJECTS
        if(savedInstanceState != null){
            //Reload UI state. Im doing this properly, keeping the content of the UI objects, not the object it self to avoid memory leaks.
        }
    }

    public class LoginThread extends AsyncTask<String, Void, Boolean> {
            @Override
            protected Boolean doInBackground(String... args) {
                try {
                    //Connect to WS, recieve a JSON/XML Response
                    //Place it somewhere I can use it.
                } catch (Exception e) {
                    return true;
                }
                return true;
            }

            protected void onPostExecute(Boolean result) {
                if (result) {
                    pd.dismiss();
                    //Handle the response. Either deny entry or launch new Login Succesful Activity
                }
            }
        }
    }
}

я не могу использовать onRetainNonConfigurationInstance() поскольку его нужно вызывать из действия, а не из фрагмента, то же самое относится и к getLastNonConfigurationInstance().Я прочитал несколько подобных вопросов здесь, но без ответа.

Я понимаю, что может потребоваться некоторая работа, чтобы правильно организовать этот материал по фрагментам, но, как говорится, я хотел бы сохранить ту же базовую логику проектирования.

Каков был бы правильный способ сохранить AsyncTask во время изменения конфигурации и, если он все еще работает, показать прогрессДиалог, принимая во внимание, что AsyncTask является внутренним классом фрагмента, и именно фрагмент вызывает AsyncTask.execute ()?

Это было полезно?

Решение

Фрагменты могут на самом деле сделать это намного проще. Просто используйте метод Fragment.SetretainInstance (Boolean) , чтобы иметь свой фрагмент Экземпляр сохраняется через изменения конфигурации. Обратите внимание, что это рекомендуемая замена для Activity.onretainnonconfigurationSStanceSstance () в Документы.

Если по какой-то причине вы действительно не хотите использовать сохраненный фрагмент, есть другие подходы, которые вы можете взять. Обратите внимание, что каждый фрагмент имеет уникальный идентификатор, возвращаемый Фрагмент. GTEDID () Вы также можете выяснить, будет ли фрагмент разорвана для изменения конфигурации через фрагмент . взаимодействие (). IsChangingConfigurations () . Итак, в точке, где вы решили остановить свою асинктюзную (в OnStop () или OnDestroy (), скорее всего), вы могли бы, например, проверять, изменяется ли конфигурация, и если настолько придерживаться ее в статическом спасерам под идентификатором фрагмента, А потом в вашем oncreate () или OnStart () посмотрите, если у вас есть асинктяза в доступе.

Другие советы

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

  1. Вращение работает, и диалог сохраняется.
  2. Вы можете отменить задачу и диалог, нажав кнопку «Назад» (если вы хотите такого поведения).
  3. Он использует фрагменты.
  4. Расположение фрагмента под действием меняется правильно при вращении устройства.
  5. Существует полная загрузка исходного кода и предварительно скомпилированный APK-файл чтобы вы могли увидеть, соответствует ли поведение тому, что вы хотите.

Редактировать

По просьбе Брэда Ларсона я воспроизвел большую часть связанного решения ниже.Кроме того, с тех пор, как я это опубликовал, на меня указали AsyncTaskLoader.Я не уверен, что это полностью применимо к тем же проблемам, но вам все равно стоит это проверить.

С использованием AsyncTask с диалогами прогресса и вращением устройства.

Рабочее решение!

У меня наконец-то все заработало.Мой код имеет следующие особенности:

  1. А Fragment макет которого меняется в зависимости от ориентации.
  2. Ан AsyncTask в котором вы можете выполнить некоторую работу.
  3. А DialogFragment который показывает ход выполнения задачи в индикаторе выполнения (а не просто в неопределенном счетчике).
  4. Поворот работает без прерывания задачи или закрытия диалога.
  5. Кнопка «Назад» закрывает диалоговое окно и отменяет задачу (хотя вы можете довольно легко изменить это поведение).

Я не думаю, что такое сочетание работоспособности можно найти где-нибудь еще.

Основная идея заключается в следующем.Eсть MainActivity класс, который содержит один фрагмент - MainFragment. MainFragment имеет разные макеты для горизонтальной и вертикальной ориентации, а также setRetainInstance() имеет значение false, поэтому макет может измениться.Это означает, что при изменении ориентации устройства оба MainActivity и MainFragment полностью разрушаются и воссоздаются.

Отдельно у нас есть MyTask (расширено с AsyncTask), который делает всю работу.Мы не можем хранить его в MainFragment потому что оно будет уничтожено, а Google больше не поддерживает использование чего-либо вроде setRetainNonInstanceConfiguration().В любом случае это не всегда доступно и в лучшем случае является уродливым хаком.Вместо этого мы будем хранить MyTask в другом фрагменте DialogFragment называется TaskFragment. Этот фрагмент воля иметь setRetainInstance() установлено значение true, чтобы при вращении устройства этот фрагмент не уничтожался и MyTask сохраняется.

Наконец, нам нужно рассказать TaskFragment кому сообщить, когда работа будет завершена, и мы делаем это с помощью setTargetFragment(<the MainFragment>) когда мы его создаем.Когда устройство поворачивается и MainFragment уничтожается и создается новый экземпляр, мы используем метод FragmentManager чтобы найти диалог (на основе его тега) и сделать setTargetFragment(<the new MainFragment>).Вот и все.

Мне нужно было сделать еще две вещи:сначала отмените задачу, когда диалоговое окно закрывается, а затем установите для сообщения об отклонении значение null, иначе диалоговое окно будет странным образом закрываться при повороте устройства.

Код

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

Основная деятельность

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

public class MainActivity extends Activity implements MainFragment.Callbacks
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public void onTaskFinished()
    {
        // Hooray. A toast to our success.
        Toast.makeText(this, "Task finished!", Toast.LENGTH_LONG).show();
        // NB: I'm going to blow your mind again: the "int duration" parameter of makeText *isn't*
        // the duration in milliseconds. ANDROID Y U NO ENUM? 
    }
}

ГлавныйФрагмент

Это долго, но оно того стоит!

public class MainFragment extends Fragment implements OnClickListener
{
    // This code up to onDetach() is all to get easy callbacks to the Activity. 
    private Callbacks mCallbacks = sDummyCallbacks;

    public interface Callbacks
    {
        public void onTaskFinished();
    }
    private static Callbacks sDummyCallbacks = new Callbacks()
    {
        public void onTaskFinished() { }
    };

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        if (!(activity instanceof Callbacks))
        {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }
        mCallbacks = (Callbacks) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        mCallbacks = sDummyCallbacks;
    }

    // Save a reference to the fragment manager. This is initialised in onCreate().
    private FragmentManager mFM;

    // Code to identify the fragment that is calling onActivityResult(). We don't really need
    // this since we only have one fragment to deal with.
    static final int TASK_FRAGMENT = 0;

    // Tag so we can find the task fragment again, in another instance of this fragment after rotation.
    static final String TASK_FRAGMENT_TAG = "task";

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // At this point the fragment may have been recreated due to a rotation,
        // and there may be a TaskFragment lying around. So see if we can find it.
        mFM = getFragmentManager();
        // Check to see if we have retained the worker fragment.
        TaskFragment taskFragment = (TaskFragment)mFM.findFragmentByTag(TASK_FRAGMENT_TAG);

        if (taskFragment != null)
        {
            // Update the target fragment so it goes to this fragment instead of the old one.
            // This will also allow the GC to reclaim the old MainFragment, which the TaskFragment
            // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
            // use weak references. To be sure you aren't leaking, you may wish to make your own
            // setTargetFragment() which does.
            taskFragment.setTargetFragment(this, TASK_FRAGMENT);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState)
    {
        super.onViewCreated(view, savedInstanceState);

        // Callback for the "start task" button. I originally used the XML onClick()
        // but it goes to the Activity instead.
        view.findViewById(R.id.taskButton).setOnClickListener(this);
    }

    @Override
    public void onClick(View v)
    {
        // We only have one click listener so we know it is the "Start Task" button.

        // We will create a new TaskFragment.
        TaskFragment taskFragment = new TaskFragment();
        // And create a task for it to monitor. In this implementation the taskFragment
        // executes the task, but you could change it so that it is started here.
        taskFragment.setTask(new MyTask());
        // And tell it to call onActivityResult() on this fragment.
        taskFragment.setTargetFragment(this, TASK_FRAGMENT);

        // Show the fragment.
        // I'm not sure which of the following two lines is best to use but this one works well.
        taskFragment.show(mFM, TASK_FRAGMENT_TAG);
//      mFM.beginTransaction().add(taskFragment, TASK_FRAGMENT_TAG).commit();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        if (requestCode == TASK_FRAGMENT && resultCode == Activity.RESULT_OK)
        {
            // Inform the activity. 
            mCallbacks.onTaskFinished();
        }
    }

TaskFragment

    // This and the other inner class can be in separate files if you like.
    // There's no reason they need to be inner classes other than keeping everything together.
    public static class TaskFragment extends DialogFragment
    {
        // The task we are running.
        MyTask mTask;
        ProgressBar mProgressBar;

        public void setTask(MyTask task)
        {
            mTask = task;

            // Tell the AsyncTask to call updateProgress() and taskFinished() on this fragment.
            mTask.setFragment(this);
        }

        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);

            // Retain this instance so it isn't destroyed when MainActivity and
            // MainFragment change configuration.
            setRetainInstance(true);

            // Start the task! You could move this outside this activity if you want.
            if (mTask != null)
                mTask.execute();
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState)
        {
            View view = inflater.inflate(R.layout.fragment_task, container);
            mProgressBar = (ProgressBar)view.findViewById(R.id.progressBar);

            getDialog().setTitle("Progress Dialog");

            // If you're doing a long task, you probably don't want people to cancel
            // it just by tapping the screen!
            getDialog().setCanceledOnTouchOutside(false);

            return view;
        }

        // This is to work around what is apparently a bug. If you don't have it
        // here the dialog will be dismissed on rotation, so tell it not to dismiss.
        @Override
        public void onDestroyView()
        {
            if (getDialog() != null && getRetainInstance())
                getDialog().setDismissMessage(null);
            super.onDestroyView();
        }

        // Also when we are dismissed we need to cancel the task.
        @Override
        public void onDismiss(DialogInterface dialog)
        {
            super.onDismiss(dialog);
            // If true, the thread is interrupted immediately, which may do bad things.
            // If false, it guarantees a result is never returned (onPostExecute() isn't called)
            // but you have to repeatedly call isCancelled() in your doInBackground()
            // function to check if it should exit. For some tasks that might not be feasible.
            if (mTask != null) {
                mTask.cancel(false);
            }

            // You don't really need this if you don't want.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_CANCELED, null);
        }

        @Override
        public void onResume()
        {
            super.onResume();
            // This is a little hacky, but we will see if the task has finished while we weren't
            // in this activity, and then we can dismiss ourselves.
            if (mTask == null)
                dismiss();
        }

        // This is called by the AsyncTask.
        public void updateProgress(int percent)
        {
            mProgressBar.setProgress(percent);
        }

        // This is also called by the AsyncTask.
        public void taskFinished()
        {
            // Make sure we check if it is resumed because we will crash if trying to dismiss the dialog
            // after the user has switched to another app.
            if (isResumed())
                dismiss();

            // If we aren't resumed, setting the task to null will allow us to dimiss ourselves in
            // onResume().
            mTask = null;

            // Tell the fragment that we are done.
            if (getTargetFragment() != null)
                getTargetFragment().onActivityResult(TASK_FRAGMENT, Activity.RESULT_OK, null);
        }
    }

Мое задание

    // This is a fairly standard AsyncTask that does some dummy work.
    public static class MyTask extends AsyncTask<Void, Void, Void>
    {
        TaskFragment mFragment;
        int mProgress = 0;

        void setFragment(TaskFragment fragment)
        {
            mFragment = fragment;
        }

        @Override
        protected Void doInBackground(Void... params)
        {
            // Do some longish task. This should be a task that we don't really
            // care about continuing
            // if the user exits the app.
            // Examples of these things:
            // * Logging in to an app.
            // * Downloading something for the user to view.
            // * Calculating something for the user to view.
            // Examples of where you should probably use a service instead:
            // * Downloading files for the user to save (like the browser does).
            // * Sending messages to people.
            // * Uploading data to a server.
            for (int i = 0; i < 10; i++)
            {
                // Check if this has been cancelled, e.g. when the dialog is dismissed.
                if (isCancelled())
                    return null;

                SystemClock.sleep(500);
                mProgress = i * 10;
                publishProgress();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Void... unused)
        {
            if (mFragment == null)
                return;
            mFragment.updateProgress(mProgress);
        }

        @Override
        protected void onPostExecute(Void unused)
        {
            if (mFragment == null)
                return;
            mFragment.taskFinished();
        }
    }
}

Скачать пример проекта

Здесь исходный код и АПК.Извините, ADT настоял на добавлении библиотеки поддержки, прежде чем я смогу создать проект.Я уверен, что вы сможете удалить его.

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

TL;DR – использовать хост AsyncTask внутри Fragment, вызов setRetainInstance(true) на Fragment, и сообщите AsyncTaskпрогресс/результаты вернуться к нему Activity (или это цель Fragment, если вы решите использовать подход, описанный @Timmmm) через сохраненный Fragment.

Мое первое предложение состоит в том, чтобы избегайте внутренних AsyncTasks, вы можете прочитать вопрос, который я задал по этому поводу, и ответы: Андроид:Рекомендации AsyncTask:частный класс или общественный класс?

После этого я начал использовать невнутренние и...теперь я вижу МНОГО преимуществ.

Во-вторых, сохраните ссылку на запущенную AsyncTask в файле Application Сорт - http://developer.android.com/reference/android/app/Application.html

Каждый раз, когда вы запускаете AsyncTask, установите его в приложении, а когда оно завершится, установите для него значение null.

Когда запускается фрагмент/действие, вы можете проверить, запущена ли какая-либо AsyncTask (проверив, является ли она нулевой или нет в приложении), а затем установить ссылку внутри на все, что вы хотите (активность, фрагмент и т. д., чтобы вы могли выполнять обратные вызовы).

Это решит вашу проблему:Если в любое определенное время у вас работает только 1 AsyncTask, вы можете добавить простую ссылку:

AsyncTask<?,?,?> asyncTask = null;

В противном случае в приложении есть HashMap со ссылками на них.

Диалог прогресса может следовать тому же принципу.

Я придумал методом использования асинктворчателей для этого. Это довольно легко в использовании и требует меньше наверху ..

в основном вы создаете Asynclaskloader, как это:

public class MyAsyncTaskLoader extends AsyncTaskLoader {
    Result mResult;
    public HttpAsyncTaskLoader(Context context) {
        super(context);
    }

    protected void onStartLoading() {
        super.onStartLoading();
        if (mResult != null) {
            deliverResult(mResult);
        }
        if (takeContentChanged() ||  mResult == null) {
            forceLoad();
        }
    }

    @Override
    public Result loadInBackground() {
        SystemClock.sleep(500);
        mResult = new Result();
        return mResult;
    }
}
.

Затем в вашей деятельности, которая использует вышеупомянутый asynctaskloader, когда нажата кнопка:

public class MyActivityWithBackgroundWork extends FragmentActivity implements LoaderManager.LoaderCallbacks<Result> {

    private String username,password;       
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mylayout);
        //this is only used to reconnect to the loader if it already started
        //before the orientation changed
        Loader loader = getSupportLoaderManager().getLoader(0);
        if (loader != null) {
            getSupportLoaderManager().initLoader(0, null, this);
        }
    }

    public void doBackgroundWorkOnClick(View button) {
        //might want to disable the button while you are doing work
        //to prevent user from pressing it again.

        //Call resetLoader because calling initLoader will return
        //the previous result if there was one and we may want to do new work
        //each time
        getSupportLoaderManager().resetLoader(0, null, this);
    }   


    @Override
    public Loader<Result> onCreateLoader(int i, Bundle bundle) {
        //might want to start a progress bar
        return new MyAsyncTaskLoader(this);
    }


    @Override
    public void onLoadFinished(Loader<LoginResponse> loginLoader,
                               LoginResponse loginResponse)
    {
        //handle result
    }

    @Override
    public void onLoaderReset(Loader<LoginResponse> responseAndJsonHolderLoader)
    {
        //remove references to previous loader resources

    }
}
.

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

Несколько вещей, чтобы примечание:

  1. Если в oncreate вы присоединитесь к Asynctaskloader, вы получите обратно в onloadfineed () с предыдущим результатом (даже если вам уже сказали, что запрос был завершен). Это на самом деле хорошее поведение большую часть времени, но иногда может быть сложно справиться. Хотя я представляю, что есть много способов справиться с этим, что я сделал, я позвонил Loader.abandon () в onloadfineed. Затем я добавил проверку в OnCreate только прикреплению только к загрузчику, если бы не было уже заброшено. Если вам нужно снова полученные данные, вы не захотите это сделать. В большинстве случаев вы хотите данные.

    У меня есть более подробные сведения об использовании этого для HTTP-вызовов здесь

Я создал очень крошечную библиотеку фоновых задач с открытым исходным кодом, которая в значительной степени основана на Marshmallow AsyncTask но с дополнительными функциями, такими как:

  1. Автоматическое сохранение задач при изменении конфигурации;
  2. Обратный вызов пользовательского интерфейса (слушатели);
  3. Не перезапускает и не отменяет задачу при вращении устройства (как это делали бы загрузчики).;

Библиотека внутренне использует Fragment без какого-либо пользовательского интерфейса, который сохраняется при изменении конфигурации (setRetainInstance(true)).

Вы можете найти его на GitHub: https://github.com/NeoTech-Software/Android-Retainable-Tasks

Самый простой пример (версия 0.2.0):

Этот пример полностью сохраняет задачу, используя очень ограниченный объем кода.

Задача:

private class ExampleTask extends Task<Integer, String> {

    public ExampleTask(String tag){
        super(tag);
    }

    protected String doInBackground() {
        for(int i = 0; i < 100; i++) {
            if(isCancelled()){
                break;
            }
            SystemClock.sleep(50);
            publishProgress(i);
        }
        return "Result";
    }
}

Активность:

public class Main extends TaskActivityCompat implements Task.Callback {

    @Override
    public void onClick(View view){
        ExampleTask task = new ExampleTask("activity-unique-tag");
        getTaskManager().execute(task, this);
    }

    @Override
    public Task.Callback onPreAttach(Task<?, ?> task) {
        //Restore the user-interface based on the tasks state
        return this; //This Activity implements Task.Callback
    }

    @Override
    public void onPreExecute(Task<?, ?> task) {
        //Task started
    }

    @Override
    public void onPostExecute(Task<?, ?> task) {
        //Task finished
        Toast.makeText(this, "Task finished", Toast.LENGTH_SHORT).show();
    }
}

Мой подход - использовать шаблон дизайна делегирования, в целом, мы можем изолировать фактическую бизнес-логику (чтение данных из Интернета или базы данных или Whatsover) от Asynctask (делегатора) к BusinessDao (делегатом), в вашей ASNCTASK.DoInbackground () Метод, делегируйте фактическую задачу в Businessdao, затем реализовать механизм процесса Singleton в Businessdao, так что множественный звонок для BusinessDao.dosomething () просто запускает одну фактическую задачу каждый раз и ждать результата задачи. Идея сохраняет делегат (I.E. BusinessDao) во время изменения конфигурации вместо делегатора (I.E. Asynckask).

  1. Создать / внедрить наше собственное приложение, цель - создать / инициализировать Businessdao здесь, чтобы наш жизненный цикл Businessdao - это приложение, не имеющее наличия, а не на выплате, обратите внимание, что вам нужно изменить androidmanifest.xml для использования myApplication: .

    public class MyApplication extends android.app.Application {
      private BusinessDAO businessDAO;
    
      @Override
      public void onCreate() {
        super.onCreate();
        businessDAO = new BusinessDAO();
      }
    
      pubilc BusinessDAO getBusinessDAO() {
        return businessDAO;
      }
    
    }
    

  2. Наша существующая деятельность / размкивание в основном неизменных, по-прежнему внедряет асинктюсс в качестве внутреннего класса и включает Asynctask.execute () от активности / разтаскивания, разница в настоящее время представляет собой асинктюссную информацию о фактической задаче в Businessdao, поэтому во время Изменение конфигурации, вторая Asynctask будет инициализирована и выполнена, и вызов BusinessDao.dosomething () второй раз, однако, второй звонок на BusinessDao.dosomething () не будет вызывать новую рабочую задачу, вместо этого, ожидание текущей задачи запуска:

    public class LoginFragment extends Fragment {
      ... ...
    
      public class LoginAsyncTask extends AsyncTask<String, Void, Boolean> {
        // get a reference of BusinessDAO from application scope.
        BusinessDAO businessDAO = ((MyApplication) getApplication()).getBusinessDAO();
    
        @Override
        protected Boolean doInBackground(String... args) {
            businessDAO.doSomething();
            return true;
        }
    
        protected void onPostExecute(Boolean result) {
          //Handle task result and update UI stuff.
        }
      }
    
      ... ...
    }
    

  3. внутри businessdao, внедряет механизм процесса Singleton, например:

    public class BusinessDAO {
      ExecutorCompletionService<MyTask> completionExecutor = new ExecutorCompletionService<MyTask(Executors.newFixedThreadPool(1));
      Future<MyTask> myFutureTask = null;
    
      public void doSomething() {
        if (myFutureTask == null) {
          // nothing running at the moment, submit a new callable task to run.
          MyTask myTask = new MyTask();
          myFutureTask = completionExecutor.submit(myTask);
        }
        // Task already submitted and running, waiting for the running task to finish.
        myFutureTask.get();
      }
    
      // If you've never used this before, Callable is similar with Runnable, with ability to return result and throw exception.
      private class MyTask extends Callable<MyTask> {
        public MyAsyncTask call() {
          // do your job here.
          return this;
        }
      }
    
    }
    

    Я не на 100% уверен, что если это будет работать, более того, фрагмент образца кода должен рассматриваться как псевдокод. Я просто пытаюсь дать вам некоторую подсказку от уровня дизайна. Любые отзывы или предложения приветствуются и оценены.

Вы можете сделать Asynctask статическое поле.Если вам нужен контекст, вы должны отправить контекст вашего приложения.Это позволит избежать утечек памяти, в противном случае вы будете хранить ссылку на всю вашу активность.

Если кто-нибудь найдет путь к этой теме, то я нашел чистый подход - запустить задачу Async из app.Service (начинается с START_STICKY), а затем при повторном создании перебирает запущенные службы, чтобы выяснить, работает ли служба (и, следовательно, асинхронная задача);

    public boolean isServiceRunning(String serviceClassName) {
    final ActivityManager activityManager = (ActivityManager) Application.getContext().getSystemService(Context.ACTIVITY_SERVICE);
    final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);

    for (RunningServiceInfo runningServiceInfo : services) {
        if (runningServiceInfo.service.getClassName().equals(serviceClassName)){
            return true;
        }
    }
    return false;
 }

Если это так, повторно добавьте DialogFragment (или что-то еще), и если это не так, убедитесь, что диалог закрыт.

Это особенно актуально, если вы используете v4.support.* библиотеки, поскольку (на момент написания) у них есть проблемы с setRetainInstance метод и просмотр страниц. Более того, не сохраняя экземпляр, вы можете воссоздать свою деятельность, используя другой набор ресурсов (т. е.другой макет вида для новой ориентации)

Я пишу код Samepl, чтобы решить эту проблему

Первый шаг - это класс приложения:

public class TheApp extends Application {

private static TheApp sTheApp;
private HashMap<String, AsyncTask<?,?,?>> tasks = new HashMap<String, AsyncTask<?,?,?>>();

@Override
public void onCreate() {
    super.onCreate();
    sTheApp = this;
}

public static TheApp get() {
    return sTheApp;
}

public void registerTask(String tag, AsyncTask<?,?,?> task) {
    tasks.put(tag, task);
}

public void unregisterTask(String tag) {
    tasks.remove(tag);
}

public AsyncTask<?,?,?> getTask(String tag) {
    return tasks.get(tag);
}
}
.

в androidmanifest.xml

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name="com.example.tasktest.TheApp">
.

Код в деятельности:

public class MainActivity extends Activity {

private Task1 mTask1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mTask1 = (Task1)TheApp.get().getTask("task1");

}

/*
 * start task is not running jet
 */
public void handletask1(View v) {
    if (mTask1 == null) {
        mTask1 = new Task1();
        TheApp.get().registerTask("task1", mTask1);
        mTask1.execute();
    } else
        Toast.makeText(this, "Task is running...", Toast.LENGTH_SHORT).show();

}

/*
 * cancel task if is not finished
 */
public void handelCancel(View v) {
    if (mTask1 != null)
        mTask1.cancel(false);
}

public class Task1 extends AsyncTask<Void, Void, Void>{

    @Override
    protected Void doInBackground(Void... params) {
        try {
            for(int i=0; i<120; i++) {
                Thread.sleep(1000);
                Log.i("tests", "loop=" + i);
                if (this.isCancelled()) {
                    Log.e("tests", "tssk cancelled");
                    break;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onCancelled(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }

    @Override
    protected void onPostExecute(Void result) {
        TheApp.get().unregisterTask("task1");
        mTask1 = null;
    }
}

}
.

Когда ориентация активности изменяет переменную MTASK, иначется из контекста приложения.Когда задача завершается, переменная установлена на NULL и удалить из памяти.

Для меня достаточно.

Посмотрите ниже пример, как использовать сохраненный фрагмент для сохранения фоновой задачи:

public class NetworkRequestFragment extends Fragment {

    // Declare some sort of interface that your AsyncTask will use to communicate with the Activity
    public interface NetworkRequestListener {
        void onRequestStarted();
        void onRequestProgressUpdate(int progress);
        void onRequestFinished(SomeObject result);
    }

    private NetworkTask mTask;
    private NetworkRequestListener mListener;

    private SomeObject mResult;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Try to use the Activity as a listener
        if (activity instanceof NetworkRequestListener) {
            mListener = (NetworkRequestListener) activity;
        } else {
            // You can decide if you want to mandate that the Activity implements your callback interface
            // in which case you should throw an exception if it doesn't:
            throw new IllegalStateException("Parent activity must implement NetworkRequestListener");
            // or you could just swallow it and allow a state where nobody is listening
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Retain this Fragment so that it will not be destroyed when an orientation
        // change happens and we can keep our AsyncTask running
        setRetainInstance(true);
    }

    /**
     * The Activity can call this when it wants to start the task
     */
    public void startTask(String url) {
        mTask = new NetworkTask(url);
        mTask.execute();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // If the AsyncTask finished when we didn't have a listener we can
        // deliver the result here
        if ((mResult != null) && (mListener != null)) {
            mListener.onRequestFinished(mResult);
            mResult = null;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        // We still have to cancel the task in onDestroy because if the user exits the app or
        // finishes the Activity, we don't want the task to keep running
        // Since we are retaining the Fragment, onDestroy won't be called for an orientation change
        // so this won't affect our ability to keep the task running when the user rotates the device
        if ((mTask != null) && (mTask.getStatus == AsyncTask.Status.RUNNING)) {
            mTask.cancel(true);
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();

        // This is VERY important to avoid a memory leak (because mListener is really a reference to an Activity)
        // When the orientation change occurs, onDetach will be called and since the Activity is being destroyed
        // we don't want to keep any references to it
        // When the Activity is being re-created, onAttach will be called and we will get our listener back
        mListener = null;
    }

    private class NetworkTask extends AsyncTask<String, Integer, SomeObject> {

        @Override
        protected void onPreExecute() {
            if (mListener != null) {
                mListener.onRequestStarted();
            }
        }

        @Override
        protected SomeObject doInBackground(String... urls) {
           // Make the network request
           ...
           // Whenever we want to update our progress:
           publishProgress(progress);
           ...
           return result;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            if (mListener != null) {
                mListener.onRequestProgressUpdate(progress[0]);
            }
        }

        @Override
        protected void onPostExecute(SomeObject result) {
            if (mListener != null) {
                mListener.onRequestFinished(result);
            } else {
                // If the task finishes while the orientation change is happening and while
                // the Fragment is not attached to an Activity, our mListener might be null
                // If you need to make sure that the result eventually gets to the Activity
                // you could save the result here, then in onActivityCreated you can pass it back
                // to the Activity
                mResult = result;
            }
        }

    }
}
.

есть взгляд здесь .

есть решение на основе на основе решение Timmmm's .

Но я его улучшил:

    .
  • Теперь решение будет расширяться - вам нужно только расширить FragmentAbleToStartTask

  • Вы можете продолжать управлять несколькими задачами одновременно.

    И на мой взгляд, это так же просто, как startactivityForresult и получить результат

  • Вы также можете остановить запущенную задачу и проверять, работает ли конкретная задача

    Извините за мой английский

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top