这是我的应用的层次结构:
ViewPager中的3个片段包含FrameLayout:
第一个和第二个片段使用CursorLoader从ContentProvider获取数据。一切正常,除非发生以下情况:
当前一种情况发生时。第一个和第二个片段保留在loadingSpinner中,从不显示列表视图。让我们看看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的Loader不存在,它被创建。 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被破坏并且还可以查看Fragments的层次结构,但片段实例保持不变(MainFragment使用setRetainInstance(true))。重新创建MainActivity,附加MainFragment,再次创建FragmentOne的视图层次结构,并使用相同的Loader id = 1填充ListView,它已经存在,因此只调用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的Loader。执行initLoader时,调用onCreateLoader()(它接收的&#34; id&#34;参数为1),但不调用onLoadFinished()并且loadSpinner保持可见。
从日志中可以比较第一次执行应用程序时(Loader id = 1不存在)...
onActivityCreated()(8240): <!> ... 83<!> CursorLoader{405c0e50 id=1}
onCreateLoader()(8240): <!> ... 146<!> CursorLoader{405c0e50 id=0}
onLoadFinished()(8240): <!> ... 153<!> CursorLoader{405c0e50 id=1}
...第二次加载器id = 1不存在:
onActivityCreated()(8240): <!> ... 83<!> CursorLoader{40540548 id=0}
onCreateLoader()(8240): <!> ... 146<!> CursorLoader{40540548 id=0}
onActivityCreated(initLoader)第一次返回id = 1的Loader,但第二次返回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;
答案 0 :(得分:3)
好的,我发现了类似的案例:Loader restarts on orientation change。
它似乎是SupportLibrary中的一个错误,与我对嵌套Fragments的实现有关。
为了使它工作,我不得不将LoaderCallbacks
接口和initLoader()
的位置从FragmentOne和FragmentTwo更改为MainActivity。它有点乱,因为我必须创建一些接口,但它确实有效。
如果有人发现自己处于这种情况,我会解释:
首先,我创建了两个接口:
ListenerFragments
接口,在MainActivity中实现,并在FragmentOne和FragmentTwo中用于在MainActivity中将自己注册为将要使用加载器的片段:
public interface ListenerFragments {
public void setFragmentOne(FragmentsUICallbacks callbacks);
public void setFragmentTwo(FragmentsUICallbacks callbacks);
public void prepareLoader(int id);
}
第二个接口,在FragmentOne和FragmentTwo中实现。并且包含将要更改Fragment UI的方法,交换光标以及使FrameLayout
子项(ListView
,LoadingSpinner
...)可见或不可见。此外,这是我们将传递给MainActivity的setFragmentOne()
和setFragmentTwo()
的界面,因此它可以在调用onLoadFinished()
和onLoaderReset()
时修改UI:
public interface FragmentsUICallbacks {
public void emptyCursor();
public void assignCursor(Cursor data);
public void clearCursorReferences();
}
MainActivity正在实施ListenerFragments
和LoaderCallbacks<Cursor>
接口:
public class MainActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor>, ListenerFragments {
private FragmentsUICallbacks fragmentOneCallbacks;
private FragmentsUICallbacks fragmentTwoCallbacks;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri uri;
String selection;
String[] selectionArgs;
switch(id) {
case 1:
uri = ...;
selection = "...";
selectionArgs = new String[] { ... };
return new CursorLoader(this, uri, null, selection, selectionArgs, null);
case 2:
...
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch(loader.getId()) {
case 1:
if(data.getCount() == 0) {
fragmentOneCallbacks.emptyCursor();
} else {
fragmentOneCallbacks.assignCursor(data);
}
break;
case 2:
...
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch(loader.getId()) {
case 1:
fragmentOneCallbacks.clearCursorReferences();
break;
case 2:
...
}
}
@Override
public void setFragmentOne(FragmentsUICallbacks callbacks) {
if(callbacks != null)
this.fragmentOneCallbacks = callbacks;
}
@Override
public void setFragmentTwo(FragmentsUICallbacks callbacks) {
if(callbacks != null)
this.fragmentTwoCallbacks = callbacks;
}
@Override
public void prepareLoader(int id) {
getSupportLoaderManager().initLoader(id, null, this);
}
}
代码非常简单。棘手的部分来自FragmentOne的onResume()
:
public class FragmentOne extends Fragment implements FragmentsUICallbacks {
...
@Override
public void onResume() {
super.onResume();
MainFragment parentFragment = (MainFragment)
getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
ListenerFragments listenerFragments = (ListenerFragments)parentFragment.getListenerFragments();
listenerFragments.setFragmentOne(this);
listenerFragments.prepareLoader(1);
}
public void emptyCursor() {
loadingSpinner.setVisibility(View.GONE);
listView.setVisibility(View.GONE);
emptyMsgContainer.setVisibility(View.VISIBLE);
}
public void assignCursor(Cursor data) {
mAdapter.swapCursor(data);
myCursor = data;
loadingSpinner.setVisibility(View.GONE);
listView.setVisibility(View.VISIBLE);
emptyMsgContainer.setVisibility(View.GONE);
}
public void clearCursorReferences() {
mAdapter.swapCursor(null);
myCursor = null;
}
}
我们需要获得MainActivity正在实现的ListenerFragment
接口方法的引用,以便通知它FragmentOne将启动一个加载器。我们通过MainFragment获得了这个参考,为什么?因为我们无法直接从FragmentOne.onAttach(Activity activity)
获取它,因为它仅在应用程序第一次启动时调用,并且片段既不会被破坏也不会被分离,当方向更改时,片段从onDestroyView()
变为onCreateView()
。 onAttach()
未被调用。
另一方面,MainFragment也没有被销毁(setRetainInstance(true)
),但它与旧的MainActivity分离,并在方向更改完成时再次附加到新的MainActivity。我们使用onAttach()
来保存引用,并创建一个getter方法,以便ViewPager
中的片段可以获得该引用:
public class MainFragment extends Fragment implements OnClickListener {
private ListenerFragments listenerFragments;
@Override
public void onAttach(Activity myActivity) {
super.onAttach(myActivity);
this.listenerFragments = (ListenerFragments)myActivity;
}
public ListenerFragments getListenerFragments() {
return listenerFragments;
}
}
知道了,我们可以回到FragmentOne.onResume()
,在那里我们得到对MainFragment的引用:
MainFragment parentFragment = (MainFragment)
getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
我们使用我们创建的MainFragment getter方法来获取对MainActivity方法的访问权限:
ListenerFragments listenerFragments = (ListenerFragments)parentFragment.getListenerFragments();
listenerFragments.setFragmentOne(this);
listenerFragments.prepareLoader(1);
基本上就是这样。