Android之FragmentManager add机制的变迁

作者: _xyczero | 来源:发表于2016-06-05 21:56 被阅读1260次

    起因

    最近在把项目support库从23.1.1升级至23.4.0时,突然发现有些地方会莫名的找不到"已经"add的fragment,从而导致了空指针引用异常。郁闷的我就仔细对比了23.1.1和23.4.0源码,发现原来是google悄悄地把FragmentManager add()机制更改了,真是坑爹-,-。

    探究

    如下是一段很平常的代码:

    
    //In Activity.java
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        getSupportFragmentManager()
            .beginTransaction()
            .add(android.R.id.content, fragmentA, fragmentA.TAG)
            .add(android.R.id.content, fragmentB, fragmentB.TAG)
            .commit();
        }
    
    //In FragmentA.java
         @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
                mFragmentB =(FragmentB)getFragmentManager()
                       .findFragmentByTag(FragmentB.TAG);
            mFragmentB.performSomeAction();
           ... ...
        }
    

    在23.1.1之前FragmentManager的add机制:

    //STEP0: 记录已经添加和有效的fragment集合
        ArrayList<Fragment> mAdded;
        ArrayList<Fragment> mActive;
        ... ...
        public void run() {
        ... ...
    //STEP1: 根据之前的add顺序,从mHead中取出fragment并依次放入lastInFragments中,为之后的transition做准备。
        calculateFragments(firstOutFragments,lastInFragments);
    
    //STEP2: 再根据lastInFragments集合统计出fragments的过渡状态(因为仅add操作,所以firstOutFragments为空)
        state=beginTransition(firstOutFragments,lastInFragments, false);
        ... ...
    //STEP3:依次将mHead链表中的fragment加入至mAdded和mActive中,但不会更改fragment的状态(即不会触发fragment的初始化操作)。
        while (op != null) {
                int enterAnim = state != null ? 0 : op.enterAnim;
                int exitAnim = state != null ? 0 : op.exitAnim;
                switch (op.cmd) {
                    case OP_ADD: {
                        Fragment f = op.fragment;
                        f.mNextAnim = enterAnim;
                        mManager.addFragment(f, false);
                    } break;
                    ... ...
                }
        }
        
    //STEP4: 最后更新mActive集合中所有fragment的状态(对于add操作而言即是触发了fragment的初始化操作)
        mManager.moveToState(mManager.mCurState
                                , transition, transitionStyle, true);
        ... ...
    }
    

    而在23.4.0版本的support库中,它初始化fragment的时机发生了变化,主要变化是在上述的STEP1中。
    在23.4.0的STEP1中,除了根据之前的add顺序,从mHead中取出fragment并依次放入lastInFragments中,为之后的transition做准备。并且将新add的fragment放入mActive中,同时更新fragment的状态(即触发了fragment的初始化操作)。

    // 23.1.1版本:
        private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
            if (fragment != null) {
                int containerId = fragment.mContainerId;
                if (containerId != 0) {
                    fragments.put(containerId, fragment);
                }
            }
        }
        
    // 23.4.0版本:
        private void setLastIn(... , Fragment fragment) {
            if (fragment != null) {
                 ... ...
                if (fragment.mState < Fragment.CREATED 
                        && mManager.mCurState >= Fragment.CREATED) {
                    mManager.makeActive(fragment);
                    mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
                }
            }
        }
    

    因为触发fragment初始化时机发生了改变,所以当FragmentB在FragmentA之后add,此时在FragmentA的onCreate方法中通过findFragmentByTag找FragmentB时,必然找到的为空(即mAdded和mActive两个集合中都无法找到),因为此时FragmentB根本还未来得及被添加至mAdded或mActive集合中。

        public Fragment findFragmentByTag(String tag) {
            if (mAdded != null && tag != null) {
                // First look through added fragments.
                for (int i=mAdded.size()-1; i>=0; i--) {
                    Fragment f = mAdded.get(i);
                    if (f != null && tag.equals(f.mTag)) {
                        return f;
                    }
                }
            }
            if (mActive != null && tag != null) {
                // Now for any known fragment.
                for (int i=mActive.size()-1; i>=0; i--) {
                    Fragment f = mActive.get(i);
                    if (f != null && tag.equals(f.mTag)) {
                        return f;
                    }
                }
            }
            return null;
         }
    
    

    结语

    所以谨记,如果两个fragment之间有调用关系的话,添加他们时一定要优先add被调用的fragment(即例子中的FragmentB),以方support库升级后引起空指针异常。


    欢迎查看 个人博客.

    相关文章

      网友评论

        本文标题:Android之FragmentManager add机制的变迁

        本文链接:https://www.haomeiwen.com/subject/vsyrdttx.html