这两天开始在改OSChina的开源android客户端,打算用Fragment来分离Main这个Activity里的功能。用Fragment嵌套ViewPager+Fragment的时候发现问题。
红色框的是主Fragment,蓝色框是主Fragment内嵌的ViewPager+Fragment。
例如当”资讯“切换到”问答“的时候,”问答“内的ViewPager+Fragment显示不符合预期,因为里面的Fragment错位了,前面几个显示的是”资讯“里面的Fragment。
而且有些显示Fragment显示空白。检查了下没问题,查看源代码发现是创建FragmentPagerAdapter时用getFragmentManager()传入的FragmentManager都是获取自Activity的同一个FragmentManager。
FragmentManager里用ArrayList自动缓存了Fragment,如果两个主Fragment用同样的布局ID会使得缓存的tag相同,结果会导致子Fragment互相替换。
FragmentPagerAdapter里的源代码:
1 @Override
2 public Object instantiateItem(ViewGroup container, int position) {
3 if (mCurTransaction == null) {
4 mCurTransaction = mFragmentManager.beginTransaction();
5 }
6
7 final long itemId = getItemId(position);
8
9 // Do we already have this fragment?
10 String name = makeFragmentName(container.getId(), itemId);
11 Fragment fragment = mFragmentManager.findFragmentByTag(name);
12 if (fragment != null) {
13 if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
14 mCurTransaction.attach(fragment);
15 } else {
16 fragment = getItem(position);
17 if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
18 mCurTransaction.add(container.getId(), fragment,
19 makeFragmentName(container.getId(), itemId));
20 }
21 if (fragment != mCurrentPrimaryItem) {
22 fragment.setMenuVisibility(false);
23 fragment.setUserVisibleHint(false);
24 }
25
26 return fragment;
27 }
还有Fragment显示空白的问题,打印所有Fragment的生命周期发现,当”资讯“切换到”问答“的时候主Fragment会执行onCreate到onResume,但是ViewPager里的Fragment却没动静。
调用 notifyDataSetChanged()也无法刷新子Fragment。查看源代码后发现使用getChildFragmentManager()来替代getFragmentManager()获取FragmentManager能解决问题,
因为getChildFragmentManager()会为本Fragment创建一个私有的FragmentManager。
1 /**
2 * Return the FragmentManager for interacting with fragments associated
3 * with this fragment's activity. Note that this will be non-null slightly
4 * before {@link #getActivity()}, during the time from when the fragment is
5 * placed in a {@link FragmentTransaction} until it is committed and
6 * attached to its activity.
7 *
8 * <p>If this Fragment is a child of another Fragment, the FragmentManager
9 * returned here will be the parent's {@link #getChildFragmentManager()}.
10 */
11 final public FragmentManager getFragmentManager() {
12 return mFragmentManager;
13 }
14
15 /**
16 * Return a private FragmentManager for placing and managing Fragments
17 * inside of this Fragment.
18 */
19 final public FragmentManager getChildFragmentManager() {
20 if (mChildFragmentManager == null) {
21 instantiateChildFragmentManager();
22 if (mState >= RESUMED) {
23 mChildFragmentManager.dispatchResume();
24 } else if (mState >= STARTED) {
25 mChildFragmentManager.dispatchStart();
26 } else if (mState >= ACTIVITY_CREATED) {
27 mChildFragmentManager.dispatchActivityCreated();
28 } else if (mState >= CREATED) {
29 mChildFragmentManager.dispatchCreate();
30 }
31 }
32 return mChildFragmentManager;
33 }
1 void instantiateChildFragmentManager() {
2 mChildFragmentManager = new FragmentManagerImpl();
3 mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() {
4 @Override
5 public View findViewById(int id) {
6 if (mView == null) {
7 throw new IllegalStateException("Fragment does not have a view");
8 }
9 return mView.findViewById(id);
10 }
11 }, this);
12 }
同时还会根据本Fragment现在所处的状态来更新私有FragmentManager里所缓存的Fragment。
1 ArrayList<Fragment> mActive;
2 ArrayList<Fragment> mAdded;
3 ArrayList<Integer> mAvailIndices;
4 ArrayList<BackStackRecord> mBackStack;
5 ArrayList<Fragment> mCreatedMenus;
6
7 public void dispatchStart() {
8 mStateSaved = false;
9 moveToState(Fragment.STARTED, false);
10 }
11
12 public void dispatchResume() {
13 mStateSaved = false;
14 moveToState(Fragment.RESUMED, false);
15 }
16
17 public void dispatchPause() {
18 moveToState(Fragment.STARTED, false);
19 }
20
21 public void dispatchStop() {
22 // See saveAllState() for the explanation of this. We do this for
23 // all platform versions, to keep our behavior more consistent between
24 // them.
25 mStateSaved = true;
26
27 moveToState(Fragment.STOPPED, false);
28 }
29
30 public void dispatchReallyStop() {
31 moveToState(Fragment.ACTIVITY_CREATED, false);
32 }
33
34 public void dispatchDestroyView() {
35 moveToState(Fragment.CREATED, false);
36 }
37
38 public void dispatchDestroy() {
39 mDestroyed = true;
40 execPendingActions();
41 moveToState(Fragment.INITIALIZING, false);
42 mActivity = null;
43 mContainer = null;
44 mParent = null;
45 }
46
47 void moveToState(int newState, int transit, int transitStyle, boolean always) {
48 if (mActivity == null && newState != Fragment.INITIALIZING) {
49 throw new IllegalStateException("No activity");
50 }
51
52 if (!always && mCurState == newState) {
53 return;
54 }
55
56 mCurState = newState;
57 if (mActive != null) {
58 boolean loadersRunning = false;
59 for (int i=0; i<mActive.size(); i++) {
60 Fragment f = mActive.get(i);
61 if (f != null) {
62 moveToState(f, newState, transit, transitStyle, false); //更新Fragment
63 if (f.mLoaderManager != null) {
64 loadersRunning |= f.mLoaderManager.hasRunningLoaders(); //是否在装载中
65 }
66 }
67 }
68
69 if (!loadersRunning) {
70 startPendingDeferredFragments(); //不是的话启动更新
71 }
72
73 if (mNeedMenuInvalidate && mActivity != null && mCurState == Fragment.RESUMED) {
74 mActivity.supportInvalidateOptionsMenu(); // 刷新选项菜单
75 mNeedMenuInvalidate = false;
76 }
77 }
78 }
79
80 void startPendingDeferredFragments() {
81 if (mActive == null) return;
82
83 for (int i=0; i<mActive.size(); i++) {
84 Fragment f = mActive.get(i);
85 if (f != null) {
86 performPendingDeferredStart(f);
87 }
88 }
89 }
90
91
根据Fragment所处的状态,启动和恢复Fragment的视图。
1 void moveToState(Fragment f, int newState, int transit, int transitionStyle,
2 boolean keepActive) {
3 // Fragments that are not currently added will sit in the onCreate() state.
4 if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
5 newState = Fragment.CREATED;
6 }
7 if (f.mRemoving && newState > f.mState) {
8 // While removing a fragment, we can't change it to a higher state.
9 newState = f.mState;
10 }
11 // Defer start if requested; don't allow it to move to STARTED or higher
12 // if it's not already started.
13 if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
14 newState = Fragment.STOPPED;
15 }
16 if (f.mState < newState) {
17 // For fragments that are created from a layout, when restoring from
18 // state we don't want to allow them to be created until they are
19 // being reloaded from the layout.
20 if (f.mFromLayout && !f.mInLayout) {
21 return;
22 }
23 if (f.mAnimatingAway != null) {
24 // The fragment is currently being animated... but! Now we
25 // want to move our state back up. Give up on waiting for the
26 // animation, move to whatever the final state should be once
27 // the animation is done, and then we can proceed from there.
28 f.mAnimatingAway = null;
29 moveToState(f, f.mStateAfterAnimating, 0, 0, true);
30 }
31 switch (f.mState) {
32 case Fragment.INITIALIZING:
33 if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
34 if (f.mSavedFragmentState != null) {
35 f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
36 FragmentManagerImpl.VIEW_STATE_TAG);
37 f.mTarget = getFragment(f.mSavedFragmentState,
38 FragmentManagerImpl.TARGET_STATE_TAG);
39 if (f.mTarget != null) {
40 f.mTargetRequestCode = f.mSavedFragmentState.getInt(
41 FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
42 }
43 f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
44 FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
45 if (!f.mUserVisibleHint) {
46 f.mDeferStart = true;
47 if (newState > Fragment.STOPPED) {
48 newState = Fragment.STOPPED;
49 }
50 }
51 }
52 f.mActivity = mActivity;
53 f.mParentFragment = mParent;
54 f.mFragmentManager = mParent != null
55 ? mParent.mChildFragmentManager : mActivity.mFragments;
56 f.mCalled = false;
57 f.onAttach(mActivity);
58 if (!f.mCalled) {
59 throw new SuperNotCalledException("Fragment " + f
60 + " did not call through to super.onAttach()");
61 }
62 if (f.mParentFragment == null) {
63 mActivity.onAttachFragment(f);
64 }
65
66 if (!f.mRetaining) {
67 f.performCreate(f.mSavedFragmentState);
68 }
69 f.mRetaining = false;
70 if (f.mFromLayout) {
71 // For fragments that are part of the content view
72 // layout, we need to instantiate the view immediately
73 // and the inflater will take care of adding it.
74 f.mView = f.performCreateView(f.getLayoutInflater(
75 f.mSavedFragmentState), null, f.mSavedFragmentState);
76 if (f.mView != null) {
77 f.mInnerView = f.mView;
78 f.mView = NoSaveStateFrameLayout.wrap(f.mView);
79 if (f.mHidden) f.mView.setVisibility(View.GONE);
80 f.onViewCreated(f.mView, f.mSavedFragmentState);
81 } else {
82 f.mInnerView = null;
83 }
84 }
85 case Fragment.CREATED:
86 if (newState > Fragment.CREATED) {
87 if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
88 if (!f.mFromLayout) {
89 ViewGroup container = null;
90 if (f.mContainerId != 0) {
91 container = (ViewGroup)mContainer.findViewById(f.mContainerId);
92 if (container == null && !f.mRestored) {
93 throwException(new IllegalArgumentException(
94 "No view found for id 0x"
95 + Integer.toHexString(f.mContainerId) + " ("
96 + f.getResources().getResourceName(f.mContainerId)
97 + ") for fragment " + f));
98 }
99 }
100 f.mContainer = container;
101 f.mView = f.performCreateView(f.getLayoutInflater(
102 f.mSavedFragmentState), container, f.mSavedFragmentState);
103 if (f.mView != null) {
104 f.mInnerView = f.mView;
105 f.mView = NoSaveStateFrameLayout.wrap(f.mView);
106 if (container != null) {
107 Animation anim = loadAnimation(f, transit, true,
108 transitionStyle);
109 if (anim != null) {
110 f.mView.startAnimation(anim);
111 }
112 container.addView(f.mView);
113 }
114 if (f.mHidden) f.mView.setVisibility(View.GONE);
115 f.onViewCreated(f.mView, f.mSavedFragmentState);
116 } else {
117 f.mInnerView = null;
118 }
119 }
120
121 f.performActivityCreated(f.mSavedFragmentState);
122 if (f.mView != null) {
123 f.restoreViewState(f.mSavedFragmentState);
124 }
125 f.mSavedFragmentState = null;
126 }
127 case Fragment.ACTIVITY_CREATED:
128 case Fragment.STOPPED:
129 if (newState > Fragment.STOPPED) {
130 if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
131 f.performStart();
132 }
133 case Fragment.STARTED:
134 if (newState > Fragment.STARTED) {
135 if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);