ViewPager 使用时遇到了一个坑

本文由 请手下留情 授权投稿
原文链接:https://www.jianshu.com/p/6b028cfe8ec6

在我们使用ViewPager的过程中都需要传入一个FragmentManager,至于FragmentManager该怎么选择呢,相信大部分人都知道。在Activity中传入supportFragmentManager,在fragment中则使用childFragmentManager。

有同事不信邪偏偏在fragment中使用时传入对应的activity的supportFragmentManager,像下面这样

 1mTitles.add("第一页")
 2mTitles.add("第二页")
 3mFragments.add(NumberFragment())
 4mFragments.add(NumberFragment())
 5val adapter = MyAdapter(activity!!.supportFragmentManager)
 6mViewPager.adapter = adapter
 7mTabLayout.setupWithViewPager(mViewPager)
 8
 9 inner class MyAdapter(fm: FragmentManager) : FragmentPagerAdapter(
10        fm,
11        BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
12    ) {
13        override fun getItem(position: Int) = mFragments[position]
14
15        override fun getCount() = mFragments.size
16
17        override fun getPageTitle(position: Int): CharSequence? {
18            return mTitles[position]
19        }
20
21        override fun destroyItem(container: ViewGroup, position: Int, object: Any) {
22            super.destroyItem(container, position, object)
23        }
24
25    }

结果就出现了下面这种情况


为什么会出现这种情况呢,其实很好理解,主要就是生命周期管理的问题,传入了activity的supportFragmentManager就会在fragment创建时为Fragment中的mFragmentManager赋值为supportFragmentManager

本身fragment中嵌套的fragment的生命周期是由上层的fragment管理,现在变成activity来管理,这就造成了fragment该被销毁时无法被销毁。

从下面的log日志中我们也能看出viewpager中的fragment没有被正常销毁,onDestroyView没有被正常调用

12020-04-10 13:40:36.725 17630-17630/com.rxy.selfapp E/Peter: NumberFragment load onCreateView
22020-04-10 13:40:36.728 17630-17630/com.rxy.selfapp E/Peter: NumberFragment load onCreateView

那为什么没有被销毁就会出现这种情况呢,通过跟踪源码我们可以发现每次重新切回fragment时ViewPager的setAdapter方法就会被调用

 1/**
 2     * Set a PagerAdapter that will supply views for this pager as needed.
 3     *
 4     * @param adapter Adapter to use
 5     */
 6    public void setAdapter(@Nullable PagerAdapter adapter) {
 7       ........省略
 8        final PagerAdapter oldAdapter = mAdapter;
 9        mAdapter = adapter;
10        mExpectedAdapterCount = 0;
11
12        if (mAdapter != null) {
13            if (mObserver == null) {
14                mObserver = new PagerObserver();
15            }
16            mAdapter.setViewPagerObserver(mObserver);
17            mPopulatePending = false;
18            final boolean wasFirstLayout = mFirstLayout;
19            mFirstLayout = true;
20            mExpectedAdapterCount = mAdapter.getCount();
21        .......省略
22    }

最终都会走到

1void populate(int newCurrentItem){
2........省略
3 if (curItem == null && N > 0) {
4    curItem = addNewItem(mCurItem, curIndex);
5}
6.......省略
7}

下面是addNewItem方法的具体实现

 1ItemInfo addNewItem(int position, int index) {
 2        ItemInfo ii = new ItemInfo();
 3        ii.position = position;
 4        ii.object = mAdapter.instantiateItem(this, position);
 5        ii.widthFactor = mAdapter.getPageWidth(position);
 6        if (index < 0 || index >= mItems.size()) {
 7            mItems.add(ii);
 8        } else {
 9            mItems.add(index, ii);
10        }
11        return ii;
12    }

最终会走到adapter中的instantiateItem()方法中

 1    @NonNull
 2    @Override
 3    public Object instantiateItem(@NonNull ViewGroup container, int position) {
 4    if (mCurTransaction == null) {
 5        mCurTransaction = mFragmentManager.beginTransaction();
 6    }
 7
 8        final long itemId = getItemId(position);
 9
10        // Do we already have this fragment?
11        String name = makeFragmentName(container.getId(), itemId);
12        Fragment fragment = mFragmentManager.findFragmentByTag(name);
13        if (fragment != null) {
14            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
15            mCurTransaction.attach(fragment);
16        } else {
17            fragment = getItem(position);
18            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
19            mCurTransaction.add(container.getId(), fragment,
20                    makeFragmentName(container.getId(), itemId));
21        }
22        if (fragment != mCurrentPrimaryItem) {
23            fragment.setMenuVisibility(false);
24            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
25                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
26            } else {
27                fragment.setUserVisibleHint(false);
28            }
29        }
30
31        return fragment;
32    }

而通过对比我们会发现当adapter传入activity的supportFragmentManager时 mFragmentManager.findFragmentByTag(name)不为空会走 mCurTransaction.attach()方法,而传入fragment的childFragmentManager时mFragmentManager.findFragmentByTag(name)为空会走 mCurTransaction.add()方法,那这两种方法有什么区别呢?

看源码我们会发现,当FragmentManager调用beginTransaction方法时会返回一个BackStackRecord对象,而BackStackRecord则是FragmentTransaction的一个子类,而无论是attach方法还是add方法最终都会走到BackStackRecord中的executeOps方法。

 1/**
 2     * Executes the operations contained within this transaction. The Fragment states will only
 3     * be modified if optimizations are not allowed.
 4     */
 5    void executeOps() {
 6        ....省略若干
 7            switch (op.mCmd) {
 8                case OP_ADD:
 9                    f.setNextAnim(op.mEnterAnim);
10                    mManager.setExitAnimationOrder(f, false);
11                    mManager.addFragment(f);
12                    break;
13                case OP_ATTACH:
14                    f.setNextAnim(op.mEnterAnim);
15                    mManager.setExitAnimationOrder(f, false);
16                    mManager.attachFragment(f);
17                    break;
18                default:
19                    throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
20            }
21            ....省略若干
22    }

我们发现add和attacth最终都会调用mManager的addFragment和attachFragment中,mManager则是我们传入的FragmentManager,下面我们看看attachFragment的具体实现,方法在FragmentManager中

 1void attachFragment(@NonNull Fragment fragment) {
 2        if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "attach: " + fragment);
 3        if (fragment.mDetached) {
 4            fragment.mDetached = false;
 5            if (!fragment.mAdded) {
 6                mFragmentStore.addFragment(fragment);
 7                if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "add from attach: " + fragment);
 8                if (isMenuAvailable(fragment)) {
 9                    mNeedMenuInvalidate = true;
10                }
11            }
12        }
13    }

只有当fragment的mDetached为true时才会把fagment加入到mFragmentStore中,那么mDetached又是什么呢?mDetached定义在Fragment中

1// Set to true when the app has requested that this fragment be deactivated.
2boolean mDetached;

当fragment无效时定义为true,看到这里相信大家已经理解了,当我们传入activity的supportFragmentManager时切换页面后由于生命周期没有正确的管理,使得fragment还是有效的,所以mDetached扔然是false,最后经过一系列的方法走到FragmentManager的attachFragment方法最终无法添加到mFragmentStore里面,如果继续跟源码我们会发现ViewPager的populate方法中有这么一段代码

1 final int childCount = getChildCount();

最终会导致ViewPager的child数为0,所以会出现一片空白的情况
当然如果我们在activity中使用的话这种方式就没为题,在framment中使用时一定得注意需要用到FragmentManager的时候要用childFragmentManager而不是对应的activity的supportFragmentManager。

下面是我画的关于本次问题的一个简单的流程图,便于大家理解

推荐阅读
写给Java程序员看的多线程学习指南
编程真相:程序员干到最后,到底需要什么技能?
深夜,分享一个Git小技巧
编程·思维·职场
欢迎扫码关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值