Это иерархия моего приложения:
3 фрагмента внутри ViewPager содержат FrameLayout:
- Счетчик загрузки, видимый при поиске данных для заполнения ListView.
- LinearLayout, показывающий сообщение, видимое, когда данные не найдены.
- И ListView.
Первый и второй фрагменты получают свои данные от ContentProvider, используя CursorLoader. И все работает нормально, за исключением случаев, когда возникает следующая ситуация:
- Остановите приложение, нажав кнопку «Домой» в ландшафтном режиме.
- переключитесь в портретный режим и снова запустите приложение (на самом деле, если я останусь в альбомной ориентации, ошибка все еще существует, потому что, когда я возобновляю работу приложения, Android воссоздает действие в портретном режиме, а затем поворачивает его. Но поскольку я собираюсь показать вам LOG, давайте не будем регистрировать один дополнительный жизненный цикл).
Когда произойдет предыдущая ситуация. Первый и второй фрагмент остаются в loadSpinner, никогда не показывают список. Давайте посмотрим код Fragment1 (второй фрагмент почти такой же):
public class FragmentOne extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private LinearLayout emptyMsgContainer;
private ListView listView;
private ProgressBar loadingSpinner;
private Details mActivity;
FragmentOneListAdapter mAdapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("onCreateView()", "FragmentOne");
View view = inflater.inflate(R.layout.fragment_one_list_fragment, container, false);
emptyMsgContainer = (LinearLayout)view.findViewById(R.id.empty_message_container_1);
listView = (ListView)view.findViewById(R.id.listView_1);
loadingSpinner = (ProgressBar)view.findViewById(R.id.loading_spinner_1);
return view;
}
@Override
public void onActivityCreated(Bundle icicle) {
super.onActivityCreated(icicle);
Log.d("onActivityCreated()", "FragmentOne");
mAdapter = new FragmentOneListAdapter(getActivity(), null, 0);
listView.setAdapter(mAdapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(mListListener);
// executes initLoader and logs at the same time
Log.d("onActivityCreated()", getActivity().getSupportLoaderManager().initLoader(1, null, this).toString());
}
@Override
public void onResume() {
super.onResume();
Log.d("onResume()", "FragmentOne");
// used to communicate direclty with the MainActivity
MainFragment parentFragment = (MainFragment)
getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
mActivity = (Details)parentFragment.getDetailsListener();
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri uri = ...;
String selection = "...";
String[] selectionArgs = new String[] { ... };
CursorLoader loader = new CursorLoader(getActivity(), uri, null, selection, selectionArgs, null);
Log.d("onCreateLoader()", loader.toString());
return loader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.d("onLoadFinished()", loader.toString());
if(data.getCount() == 0) {
loadingSpinner.setVisibility(View.GONE);
listView.setVisibility(View.GONE);
emptyMsgContainer.setVisibility(View.VISIBLE);
} else {
mAdapter.swapCursor(data);
myCursor = data;
loadingSpinner.setVisibility(View.GONE);
listView.setVisibility(View.VISIBLE);
emptyMsgContainer.setVisibility(View.GONE);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
myCursor = null;
}
private OnItemClickListener mListListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mActivity.ShowStoredDetails(position, 1);
}
};
}
Я зарегистрировал обратные вызовы жизненного цикла Activity и Fragment, onCreateLoader и onLoadFinished, чтобы попытаться выяснить, что происходит. Сначала давайте откроем приложение в портретной ориентации:
23:04:00.089: D/onCreate()(8240): <!> ... 21<!> MainActivity
23:04:00.139: D/onAttach()(8240): <!> ... 61<!> MainFragment
23:04:00.139: D/onCreate()(8240): <!> ... 68<!> MainFragment
23:04:00.139: D/onCreateView()(8240): <!> ... 75<!> MainFragment
23:04:00.169: D/onActivityCreated()(8240): <!> ... 95<!> MainFragment
23:04:00.169: D/onStart()(8240): <!> ... 101<!> MainFragment
23:04:00.169: D/onStart()(8240): <!> ... 31<!> MainActivity
23:04:00.169: D/onResume()(8240): <!> ... 41<!> MainActivity
23:04:00.169: D/onResume()(8240): <!> ... 107<!> MainFragment
23:04:00.179: D/onattach()(8240): <!> ... 51<!> FragmentOne
23:04:00.189: D/onCreate()(8240): <!> ... 57<!> FragmentOne
23:04:00.189: D/onCreateView()(8240): <!> ... 62<!> FragmentOne
23:04:00.199: D/onActivityCreated()(8240): <!> ... 74<!> FragmentOne
23:04:00.199: D/onCreateLoader()(8240): <!> ... 146<!> CursorLoader{405c0e50 id=0}
23:04:00.209: D/onActivityCreated()(8240): <!> ... 83<!> CursorLoader{405c0e50 id=1}
23:04:00.209: D/onStart()(8240): <!> ... 90<!> FragmentOne
23:04:00.209: D/onResume()(8240): <!> ... 96<!> FragmentOne
23:04:00.219: D/onattach()(8240): <!> ... 50<!> FragmentTwo
23:04:00.219: D/onCreate()(8240): <!> ... 56<!> FragmentTwo
23:04:00.219: D/onCreateView()(8240): <!> ... 61<!> FragmentTwo
23:04:00.229: D/onActivityCreated()(8240): <!> ... 73<!> FragmentTwo
23:04:00.239: D/onStart()(8240): <!> ... 88<!> FragmentTwo
23:04:00.259: D/onResume()(8240): <!> ... 94<!> FragmentTwo
23:04:00.479: D/onLoadFinished()(8240): <!> ... 153<!> CursorLoader{405c0e50 id=1}
Загрузчик с id=1 не существует, он создан. вызывается onCreateLoader() и после этого onLoadFinished(). ListView заполнен и работает нормально. Теперь давайте повернем в ландшафт:
23:04:26.999: D/onSaveInstanceState()(8240): <!> ... 113<!> MainFragment
23:04:27.009: D/onSaveInstanceState()(8240): <!> ... 105<!> FragmentOne
23:04:27.009: D/onSaveInstanceState()(8240): <!> ... 103<!> FragmentTwo
23:04:27.009: D/onPause()(8240): <!> ... 111<!> FragmentOne
23:04:27.009: D/onPause()(8240): <!> ... 109<!> FragmentTwo
23:04:27.009: D/onPause()(8240): <!> ... 119<!> MainFragment
23:04:27.009: D/onPause()(8240): <!> ... 46<!> MainActivity
23:04:27.019: D/onStop()(8240): <!> ... 117<!> FragmentOne
23:04:27.019: D/onStop()(8240): <!> ... 115<!> FragmentTwo
23:04:27.019: D/onStop()(8240): <!> ... 125<!> MainFragment
23:04:27.019: D/onStop()(8240): <!> ... 51<!> MainActivity
23:04:27.019: D/onDestroyView()(8240): <!> ... 123<!> FragmentOne
23:04:27.019: D/onDestroyView()(8240): <!> ... 121<!> FragmentTwo
23:04:27.029: D/onDestroyView()(8240): <!> ... 131<!> MainFragment
23:04:27.029: D/onDetach()(8240): <!> ... 143<!> MainFragment
23:04:27.039: D/onDestroy()(8240): <!> ... 56<!> MainActivity
23:04:27.069: D/onAttach()(8240): <!> ... 61<!> MainFragment
23:04:27.079: D/onCreate()(8240): <!> ... 21<!> MainActivity
23:04:27.169: D/onCreateView()(8240): <!> ... 75<!> MainFragment
23:04:27.199: D/onActivityCreated()(8240): <!> ... 95<!> MainFragment
23:04:27.199: D/onCreateView()(8240): <!> ... 62<!> FragmentOne
23:04:27.209: D/onActivityCreated()(8240): <!> ... 74<!> FragmentOne
23:04:27.209: D/onActivityCreated()(8240): <!> ... 83<!> CursorLoader{405c0e50 id=1}
23:04:27.219: D/onCreateView()(8240): <!> ... 61<!> FragmentTwo
23:04:27.239: D/onActivityCreated()(8240): <!> ... 73<!> FragmentTwo
23:04:27.239: D/onStart()(8240): <!> ... 101<!> MainFragment
23:04:27.239: D/onStart()(8240): <!> ... 90<!> FragmentOne
23:04:27.249: D/onStart()(8240): <!> ... 88<!> FragmentTwo
23:04:27.249: D/onLoadFinished()(8240): <!> ... 153<!> CursorLoader{405c0e50 id=1}
23:04:27.249: D/onStart()(8240): <!> ... 31<!> MainActivity
23:04:27.259: D/onResume()(8240): <!> ... 41<!> MainActivity
23:04:27.259: D/onResume()(8240): <!> ... 107<!> MainFragment
23:04:27.259: D/onResume()(8240): <!> ... 96<!> FragmentOne
23:04:27.259: D/onResume()(8240): <!> ... 94<!> FragmentTwo
MainActivity уничтожается, а также просматривается иерархия фрагментов, но экземпляры фрагментов остаются прежними (MainFragment использует setRetainInstance(true)). Активность MainActivity воссоздается, к ней прикрепляется MainFragment, снова создается иерархия представлений FragmentOne, а ListView заполняется тем же идентификатором загрузчика = 1, он уже существует, поэтому вызывается только onLoadFinished(). Теперь давайте остановим приложение, нажав кнопку «Домой»:
23:04:59.639: D/onSaveInstanceState()(8240): <!> ... 113<!> MainFragment
23:04:59.649: D/onSaveInstanceState()(8240): <!> ... 105<!> FragmentOne
23:04:59.649: D/onSaveInstanceState()(8240): <!> ... 103<!> FragmentTwo
23:04:59.649: D/onPause()(8240): <!> ... 111<!> FragmentOne
23:04:59.649: D/onPause()(8240): <!> ... 109<!> FragmentTwo
23:04:59.649: D/onPause()(8240): <!> ... 119<!> MainFragment
23:04:59.659: D/onPause()(8240): <!> ... 46<!> MainActivity
23:05:00.059: D/onStop()(8240): <!> ... 117<!> FragmentOne
23:05:00.069: D/onStop()(8240): <!> ... 115<!> FragmentTwo
23:05:00.069: D/onStop()(8240): <!> ... 125<!> MainFragment
23:05:00.069: D/onStop()(8240): <!> ... 51<!> MainActivity
Все остановлено. Наконец, давайте возобновим работу приложения:
23:05:47.489: D/onDestroyView()(8240): <!> ... 123<!> FragmentOne
23:05:47.489: D/onDestroyView()(8240): <!> ... 121<!> FragmentTwo
23:05:47.499: D/onDestroyView()(8240): <!> ... 131<!> MainFragment
23:05:47.499: D/onDetach()(8240): <!> ... 143<!> MainFragment
23:05:47.499: D/onDestroy()(8240): <!> ... 56<!> MainActivity
23:05:47.509: D/onAttach()(8240): <!> ... 61<!> MainFragment
23:05:47.509: D/onCreate()(8240): <!> ... 21<!> MainActivity
23:05:47.539: D/onCreateView()(8240): <!> ... 75<!> MainFragment
23:05:47.569: D/onActivityCreated()(8240): <!> ... 95<!> MainFragment
23:05:47.569: D/onCreateView()(8240): <!> ... 62<!> FragmentOne
23:05:47.579: D/onActivityCreated()(8240): <!> ... 74<!> FragmentOne
23:05:47.579: D/onCreateLoader()(8240): <!> ... 146<!> CursorLoader{40540548 id=0}
23:05:47.579: D/onActivityCreated()(8240): <!> ... 83<!> CursorLoader{40540548 id=0}
23:05:47.579: D/onCreateView()(8240): <!> ... 61<!> FragmentTwo
23:05:47.589: D/onActivityCreated()(8240): <!> ... 73<!> FragmentTwo
23:05:47.589: D/onStart()(8240): <!> ... 101<!> MainFragment
23:05:47.589: D/onStart()(8240): <!> ... 90<!> FragmentOne
23:05:47.599: D/onStart()(8240): <!> ... 88<!> FragmentTwo
23:05:47.599: D/onStart()(8240): <!> ... 31<!> MainActivity
23:05:47.599: D/onResume()(8240): <!> ... 41<!> MainActivity
23:05:47.599: D/onResume()(8240): <!> ... 107<!> MainFragment
23:05:47.599: D/onResume()(8240): <!> ... 96<!> FragmentOne
23:05:47.599: D/onResume()(8240): <!> ... 94<!> FragmentTwo
Жизненный цикл активности и фрагментов завершен. Активность создается заново, к ней присоединяется MainFragment. Иерархия представлений FragmentOne создана. Но на этот раз LoaderManager больше не содержит загрузчика с id=1. Когда initLoader выполняется, вызывается onCreateLoader() (параметр "id", который он получает, равен 1), но onLoadFinished() не вызывается, и загрузкаSpinner остается видимой.
Из журнала вы можете сравнить первый запуск приложения (идентификатор загрузчика = 1 не существует)...
onActivityCreated()(8240): <!> ... 83<!> CursorLoader{405c0e50 id=1}
onCreateLoader()(8240): <!> ... 146<!> CursorLoader{405c0e50 id=0}
onLoadFinished()(8240): <!> ... 153<!> CursorLoader{405c0e50 id=1}
... во второй раз идентификатор загрузчика = 1 не существует:
onActivityCreated()(8240): <!> ... 83<!> CursorLoader{40540548 id=0}
onCreateLoader()(8240): <!> ... 146<!> CursorLoader{40540548 id=0}
В первый раз onActivityCreated (initLoader) возвращает загрузчик с id=1, а во второй раз возвращает id=0. Я могу только предположить, что по этой причине onLoadFinished() не вызывается во второй раз. Насколько я знаю, LoaderManager должен сохранять свое состояние при изменении ориентации. Любые идеи о том, что здесь происходит?
ИЗМЕНИТЬ
Я должен был упомянуть, что использую библиотеку поддержки:
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;