Framgent中使用FragmentManager遇到的问题
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 = adaptermTabLayout.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.sizeoverride fun getPageTitle(position: Int): CharSequence? {return mTitles[position]}override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {super.destroyItem(container, position, `object`)}}
结果就出现了下面这种情况
文章图片
image 为什么会出现这种情况呢,其实很好理解,主要就是生命周期管理的问题,传入了activity的supportFragmentManager就会在fragment创建时为Fragment中的mFragmentManager赋值为supportFragmentManager
本身fragment中嵌套的fragment的生命周期是由上层的fragment管理,现在变成activity来管理,这就造成了fragment该被销毁时无法被销毁。
【Framgent中使用FragmentManager遇到的问题】从下面的log日志中我们也能看出viewpager中的fragment没有被正常销毁,onDestroyView没有被正常调用
2020-04-10 13:40:36.725 17630-17630/com.rxy.selfapp E/Peter: NumberFragment load onCreateView2020-04-10 13:40:36.728 17630-17630/com.rxy.selfapp E/Peter: NumberFragment load onCreateView
那为什么没有被销毁就会出现这种情况呢,通过跟踪源码我们可以发现每次重新切回fragment时ViewPager的setAdapter方法就会被调用
/*** 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方法的具体实现
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@Overridepublic 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;
}
而通过对比我们会发现当adapter传入activity的supportFragmentManager时 mFragmentManager.findFragmentByTag(name)不为空会走 mCurTransaction.attach()方法,而传入fragment的childFragmentManager时mFragmentManager.findFragmentByTag(name)为空会走 mCurTransaction.add()方法,那这两种方法有什么区别呢?
看源码我们会发现,当FragmentManager调用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);
}....省略若干}
我们发现add和attacth最终都会调用mManager的addFragment和attachFragment中,mManager则是我们传入的FragmentManager,下面我们看看attachFragment的具体实现,方法在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,看到这里相信大家已经理解了,当我们传入activity的supportFragmentManager时切换页面后由于生命周期没有正确的管理,使得fragment还是有效的,所以mDetached扔然是false,最后经过一系列的方法走到FragmentManager的attachFragment方法最终无法添加到mFragmentStore里面,如果继续跟源码我们会发现ViewPager的populate方法中有这么一段代码
final int childCount = getChildCount();
最终会导致ViewPager的child数为0,所以会出现一片空白的情况
当然如果我们在activity中使用的话这种方式就没为题,在framment中使用时一定得注意需要用到FragmentManager的时候要用childFragmentManager而不是对应的activity的supportFragmentManager。
下面是我画的关于本次问题的一个简单的流程图,便于大家理解
文章图片
image
推荐阅读
- 热闹中的孤独
- Shell-Bash变量与运算符
- JS中的各种宽高度定义及其应用
- 2021-02-17|2021-02-17 小儿按摩膻中穴-舒缓咳嗽
- 深入理解Go之generate
- 由浅入深理解AOP
- 异地恋中,逐渐适应一个人到底意味着什么()
- 【译】20个更有效地使用谷歌搜索的技巧
- 我眼中的佛系经纪人
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售