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 = 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`)

        }

    }

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

image

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

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

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

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

那为什么没有被销毁就会出现这种情况呢,通过跟踪源码我们可以发现每次重新切回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

    @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;

    }

而通过对比我们会发现当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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容