Погрузчик не может сохранить себя во время определенного изменения конфигурации

Согласно http://developer.android.com/guide/components/loaders.html , одна из приятных вещей о загрузчике заключается в том, что она может сохранять свои данные во время изменения конфигурации.

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

Однако во всех сценариях это не работает.

Я делаю следующий простой пример. Это FragmentActivity , в котором размещен Fragment . Сам Fragment принадлежит AsyncTaskLoader .

Следующие 3 сценария работают очень хорошо.

Во время первого запуска (ОК)

1, и loadInBackground выполняется один раз.

Во время простого вращения (OK)

Никакой новый загрузчик не создается и loadInBackground не запускается.

Запускается дочерняя операция, а кнопка «Назад» нажата (ОК)

Никакой новый загрузчик не создается и loadInBackground не запускается.

Однако в следующем сценарии.

Запускается дочерняя активность -> Вращение -> нажата кнопка «Назад» («Неверно»)

В это время onReset старого загрузчика. Старый погрузчик будет уничтожен. Будет создан новый загрузчик и loadInBackground будет loadInBackground новый loader loadInBackground .

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

Код, связанный с загрузчиком, следующий. Я запускаю код под эмулятором Android 4.1.

 package com.example.bug; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class MainFragment extends Fragment implements LoaderManager.LoaderCallbacks<Integer> { private static class IntegerArrayLoader extends AsyncTaskLoader<Integer> { private Integer result = null; public IntegerArrayLoader(Context context) { super(context); Log.i("CHEOK", "IntegerArrayLoader created!"); } @Override public Integer loadInBackground() { Log.i("CHEOK", "Time consuming loadInBackground!"); this.result = 123456; return result; } /** * Handles a request to cancel a load. */ @Override public void onCanceled(Integer integer) { super.onCanceled(integer); } /** * Handles a request to stop the Loader. * Automatically called by LoaderManager via stopLoading. */ @Override protected void onStopLoading() { // Attempt to cancel the current load task if possible. cancelLoad(); } /** * Handles a request to start the Loader. * Automatically called by LoaderManager via startLoading. */ @Override protected void onStartLoading() { if (this.result != null) { deliverResult(this.result); } if (takeContentChanged() || this.result == null) { forceLoad(); } } /** * Handles a request to completely reset the Loader. * Automatically called by LoaderManager via reset. */ @Override protected void onReset() { super.onReset(); // Ensure the loader is stopped onStopLoading(); // At this point we can release the resources associated with 'apps' // if needed. this.result = null; } } @Override public Loader<Integer> onCreateLoader(int arg0, Bundle arg1) { Log.i("CHEOK", "onCreateLoader being called"); return new IntegerArrayLoader(this.getActivity()); } @Override public void onLoadFinished(Loader<Integer> arg0, Integer arg1) { result = arg1; } @Override public void onLoaderReset(Loader<Integer> arg0) { // TODO Auto-generated method stub } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_main, container, false); return v; } // http://stackoverflow.com/questions/11293441/android-loadercallbacks-onloadfinished-called-twice @Override public void onResume() { super.onResume(); if (result == null) { // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } else { // Restore from previous state. Perhaps through long pressed home // button. } } private Integer result; } 

Полный исходный код можно загрузить с https://www.dropbox.com/s/n2jee3v7cpwvedv/loader_bug.zip

Это может быть связано с 1 неизвестной ошибкой Android: https://code.google.com/p/android/issues/detail?id=20791&can=5&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

https://groups.google.com/forum/?fromgroups=#!topic/android-developers/DbKL6PVyhLI

Мне было интересно, есть ли хорошие способы обхода этой ошибки?

Solutions Collecting From Web of "Погрузчик не может сохранить себя во время определенного изменения конфигурации"

Мой ответ довольно прямолинейный. Не используйте AsyncTaskLoaders. Из-за нескольких ошибок в отношении AsyncTaskLoaders вы уже это знали.

Хорошая комбинация будет сохраняемой (setRetainInstance (true) в onActivityCreated ()) фрагменте с AsyncTask. Работает точно так же. Просто нужно немного перестроить код.


Сообщение от OP

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

 @Override public void onActivityCreated(Bundle savedInstanceState) { ... dataRetainedFragment = (DataRetainedFragment)fm.findFragmentByTag(DATE_RETAINED_FRAGMENT); // dataRetainedFragment can be null still... } @Override public void onResume() { ... if (this.data == null) { if (dataRetainedFragment != null) { // Re-use! onLoadFinished(null, dataRetainedFragment); } else { // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } } else { } } @Override public void onLoadFinished(Loader<Data> arg0, Data data) { this.data = data; if (this.dataRetainedFragment == null) { this.dataRetainedFragment = DataRetainedFragment.newInstance(this.data); FragmentManager fm = getFragmentManager(); fm.beginTransaction().add(this.dataRetainedFragment, DATE_RETAINED_FRAGMENT).commitAllowingStateLoss(); } 

Попытайтесь изменить,

  @Override public void onResume() { super.onResume(); if (result == null) { // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } else { // Restore from previous state. Perhaps through long pressed home // button. } } 

в

  @Override public void onResume() { super.onResume(); Loader loader = getLoaderManager().getLoader(0); if ( loader != null && loader.isReset() ) { getLoaderManager().restartLoader(0, getArguments(), this); } else { getLoaderManager().initLoader(0, getArguments(), this); } } 

Если вы используете метод фрагмента замены FragmentManager, эта проблема будет происходить.

Когда вы заменяете / удаляете фрагмент, фрагмент отсоединяется от действия и, поскольку загрузчики привязаны к активности, загрузчики будут воссозданы во время изменения ориентации.

Попробуйте использовать технику скрыть / показать FragmentManager. Может быть, это поможет вам.

У меня был успех подклассификации AsyncTaskLoader и внесение нескольких настроек в его методы.

 public class FixedAsyncTaskLoader<D> extends AsyncTaskLoader<D> { private D result; public FixedAsyncTaskLoader(Context context) { super(context); } @Override protected void onStartLoading() { if (result != null) { deliverResult(result); } else { forceLoad(); } } @Override public void deliverResult(T data) { result = data; if (isStarted()) { super.deliverResult(result); } } @Override protected void onReset() { super.onReset(); onStopLoading(); result = null; } @Override protected void onStopLoading() { cancelLoad(); } }