ViewPager適配器中FragmentManager的選擇
在我們使用ViewPager的過程中都需要傳入一個FragmentManager媳溺,至于FragmentManager該怎么選擇呢,相信大部分人都知道碍讯。在Activity中傳入supportFragmentManager悬蔽,在fragment中則使用childFragmentManager。
有同事不信邪偏偏在fragment中使用時傳入對應的activity的supportFragmentManager捉兴,像下面這樣
mTitles.add("第一頁")
mTitles.add("第二頁")
mFragments.add(NumberFragment())
mFragments.add(NumberFragment())
val adapter = MyAdapter(activity!!.supportFragmentManager)
mViewPager.adapter = adapter
mTabLayout.setupWithViewPager(mViewPager)
inner class MyAdapter(fm: FragmentManager) : FragmentPagerAdapter(
fm,
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {
override fun getItem(position: Int) = mFragments[position]
override fun getCount() = mFragments.size
override fun getPageTitle(position: Int): CharSequence? {
return mTitles[position]
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
}
}
結(jié)果就出現(xiàn)了下面這種情況
為什么會出現(xiàn)這種情況呢蝎困,其實很好理解,主要就是生命周期管理的問題倍啥,傳入了activity的supportFragmentManager就會在fragment創(chuàng)建時為Fragment中的mFragmentManager賦值為supportFragmentManager
本身fragment中嵌套的fragment的生命周期是由上層的fragment管理禾乘,現(xiàn)在變成activity來管理,這就造成了fragment該被銷毀時無法被銷毀逗栽。
從下面的log日志中我們也能看出viewpager中的fragment沒有被正常銷毀,onDestroyView沒有被正常調(diào)用
2020-04-10 13:40:36.725 17630-17630/com.rxy.selfapp E/Peter: NumberFragment load onCreateView
2020-04-10 13:40:36.728 17630-17630/com.rxy.selfapp E/Peter: NumberFragment load onCreateView
那為什么沒有被銷毀就會出現(xiàn)這種情況呢,通過跟蹤源碼我們可以發(fā)現(xiàn)每次重新切回fragment時ViewPager的setAdapter方法就會被調(diào)用
/**
* Set a PagerAdapter that will supply views for this pager as needed.
*
* @param adapter Adapter to use
*/
public void setAdapter(@Nullable PagerAdapter adapter) {
........省略
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
.......省略
}
最終都會走到
void populate(int newCurrentItem){
........省略
if (curItem == null && N > 0) {
curItem = addNewItem(mCurItem, curIndex);
}
.......省略
}
下面是addNewItem方法的具體實現(xiàn)
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
最終會走到adapter中的instantiateItem()方法中
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
fragment.setUserVisibleHint(false);
}
}
return fragment;
}
而通過對比我們會發(fā)現(xiàn)當adapter傳入activity的supportFragmentManager時 mFragmentManager.findFragmentByTag(name)不為空會走 mCurTransaction.attach()方法失暂,而傳入fragment的childFragmentManager時mFragmentManager.findFragmentByTag(name)為空會走 mCurTransaction.add()方法彼宠,那這兩種方法有什么區(qū)別呢鳄虱?
看源碼我們會發(fā)現(xiàn),當FragmentManager調(diào)用beginTransaction方法時會返回一個BackStackRecord對象凭峡,而BackStackRecord則是FragmentTransaction的一個子類拙已,而無論是attach方法還是add方法最終都會走到BackStackRecord中的executeOps方法。
/**
* Executes the operations contained within this transaction. The Fragment states will only
* be modified if optimizations are not allowed.
*/
void executeOps() {
....省略若干
switch (op.mCmd) {
case OP_ADD:
f.setNextAnim(op.mEnterAnim);
mManager.setExitAnimationOrder(f, false);
mManager.addFragment(f);
break;
case OP_ATTACH:
f.setNextAnim(op.mEnterAnim);
mManager.setExitAnimationOrder(f, false);
mManager.attachFragment(f);
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
}
....省略若干
}
我們發(fā)現(xiàn)add和attacth最終都會調(diào)用mManager的addFragment和attachFragment中摧冀,mManager則是我們傳入的FragmentManager倍踪,下面我們看看attachFragment的具體實現(xiàn),方法在FragmentManager中
void attachFragment(@NonNull Fragment fragment) {
if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "attach: " + fragment);
if (fragment.mDetached) {
fragment.mDetached = false;
if (!fragment.mAdded) {
mFragmentStore.addFragment(fragment);
if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "add from attach: " + fragment);
if (isMenuAvailable(fragment)) {
mNeedMenuInvalidate = true;
}
}
}
}
只有當fragment的mDetached為true時才會把fagment加入到mFragmentStore中索昂,那么mDetached又是什么呢建车?mDetached定義在Fragment中
// Set to true when the app has requested that this fragment be deactivated.
boolean mDetached;
當fragment無效時定義為true,看到這里相信大家已經(jīng)理解了椒惨,當我們傳入activity的supportFragmentManager時切換頁面后由于生命周期沒有正確的管理缤至,使得fragment還是有效的,所以mDetached扔然是false康谆,最后經(jīng)過一系列的方法走到FragmentManager的attachFragment方法最終無法添加到mFragmentStore里面领斥,如果繼續(xù)跟源碼我們會發(fā)現(xiàn)ViewPager的populate方法中有這么一段代碼
final int childCount = getChildCount();
最終會導致ViewPager的child數(shù)為0,所以會出現(xiàn)一片空白的情況
當然如果我們在activity中使用的話這種方式就沒為題沃暗,在framment中使用時一定得注意需要用到FragmentManager的時候要用childFragmentManager而不是對應的activity的supportFragmentManager月洛。
下面是我畫的關于本次問題的一個簡單的流程圖,便于大家理解