Android源码阅读之Fragment Add和Replace

作者: 要拿出真本事了 | 来源:发表于2019-07-28 22:49 被阅读13次

    实习中发现,对于FragmenManager管理Fragment有时候使用add的方式,有时候使用replace的方式。

    遂问大佬这有什么讲究,大佬说replace多用于一次性显示,add和hide用于多次切换

    然而,对于这个结论我依然未能满足,好奇心驱使之下决定探究下这里面的道理

    正文

    Note:研究基于AndroidX

    通常通过FragmentManager获取FragmentTransaction对Fragment进行管理时,可通过add和replace的方式添加Fragment,为探究两者的区别,决定从源码进行分析

    不同版本的实现会有一定出入,但是通过FragmentTransaction定义的行为却是相同的

    FragmentTransaction是一个抽象类,定义了包括add、show、hide、replace等的行为准则

        public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
                @Nullable String tag)  {
        /**
         * Replace an existing fragment that was added to a container.  This is
         * essentially the same as calling {@link #remove(Fragment)} for all
         * currently added fragments that were added with the same containerViewId
         * and then {@link #add(int, Fragment, String)} with the same arguments
         * given here.
          ...
         */
        @NonNull
        public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
                @Nullable String tag)  {
            ...
        }
    
        /**
         * Add a fragment to the activity state.  This fragment may optionally
         * also have its view (if {@link Fragment#onCreateView Fragment.onCreateView}
         * returns non-null) into a container view of the activity.
         ...
         */
        @NonNull
        public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment,
                @Nullable String tag) {
            ...
        }
    
    }
    

    FragmentTransaction定义了add就是直接的添加,而replace是先remove再add

    Fragment类之中包含众多属性,其中与add或replace相关的属性记录如下

    public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
            ViewModelStoreOwner, SavedStateRegistryOwner {
        ...
        // When a fragment is being dynamically added to the view hierarchy, this
        // is the identifier of the parent container it is being added to.
        int mContainerId;
    
        // Set to true when the app has requested that this fragment be hidden
        // from the user.
        boolean mHidden;
        ...
    }
    

    Fragment中包含的mContainerId是作为其父容器的标识

    接下来记录FragmentManager的实现链路

    AppCompatActivity 
      - FragmentActivity
        - FragmentController
          - FragmentHostCallback
            - FragmentManagerImpl
              - BackStackRecord
    

    基本上关于FragmentManager中的操作都在FragmentManagerImpl类中提供实现方法,但是关于其管理,还是封装在了BackStackRecord中

    FragmentManagerImpl

    final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
        ...
        final ArrayList<Fragment> mAdded = new ArrayList<>();
        ArrayList<BackStackRecord> mBackStack;
        ...
    }
    

    添加的Fragment会添加到mAdded之中

    BackStackRecord

    Note:展示关键性代码,关于add、remove、hide、show和replace的,这个判断分支并不都在一个方法中

                switch (op.mCmd) {
                    case OP_ADD:
                        f.setNextAnim(op.mEnterAnim);
                        mManager.addFragment(f, false);
                        break;
                    case OP_REMOVE:
                        f.setNextAnim(op.mExitAnim);
                        mManager.removeFragment(f);
                        break;
                    case OP_HIDE:
                        f.setNextAnim(op.mExitAnim);
                        mManager.hideFragment(f);
                        break;
                    case OP_SHOW:
                        f.setNextAnim(op.mEnterAnim);
                        mManager.showFragment(f);
                        break;
                ...
                    case OP_REPLACE: {
                        final Fragment f = op.mFragment;
                        final int containerId = f.mContainerId;
                        boolean alreadyAdded = false;
                        for (int i = added.size() - 1; i >= 0; i--) {
                            final Fragment old = added.get(i);
                            if (old.mContainerId == containerId) {
                                if (old == f) {
                                    alreadyAdded = true;
                                } else {
                                    // This is duplicated from above since we only make
                                    // a single pass for expanding ops. Unset any outgoing primary nav.
                                    if (old == oldPrimaryNav) {
                                        mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
                                        opNum++;
                                        oldPrimaryNav = null;
                                    }
                                    final Op removeOp = new Op(OP_REMOVE, old);
                                    removeOp.mEnterAnim = op.mEnterAnim;
                                    removeOp.mPopEnterAnim = op.mPopEnterAnim;
                                    removeOp.mExitAnim = op.mExitAnim;
                                    removeOp.mPopExitAnim = op.mPopExitAnim;
                                    mOps.add(opNum, removeOp);
                                    added.remove(old);
                                    opNum++;
                                }
                            }
                        }
                        if (alreadyAdded) {
                            mOps.remove(opNum);
                            opNum--;
                        } else {
                            op.mCmd = OP_ADD;
                            added.add(f);
                        }
                    }
                    break;
                 ...
    

    add、remove的实现封装在FragmentManagerImpl类的addFragment和removeFragment方法中,其核心实现就是往mAdded中add或remove当前Fragment

    hdie、show的实现封装在FragmentManagerImpl类的hideFragment和showFragment方法中,其核心实现是修改Fragment的显示状态,通过修改Fragment的mHidden字段

    replace的实现封装在BackStackRecord中expandOps方法的switch的OP_REPLACE分支中,其实现为:

    • 从后往前遍历ArrayList<Fragment>中的Fragment,查找是否有mContainerId与添加Fragment一致的
    • 若没有,则直接在ArrayList<Fragment>中add
    • 若有mContainerId相同
      1 有相同实例,移除mOps中的该实例
      2 若实例不相同,则添加到mOps中,同时remove ArrayList<Fragment>中旧的Fragment再add新的Fragment

    总结

    add + hide + show的实现是往ArrayList<Fragment>中add Fragment以及修改Fragment中的字段修改可见属性

    replace的实现是把具有相同父容器id中的不同Fragment实例先remove再add,相同父容器中的相同实例则并没有对ArrayList<Fragment>进行任何操作,而是通过修改mOps (定义于Fragment中的ArrayList<Op> mOps = new ArrayList<>();) 来实现操作

    总的来说就是,Add相当于Activtiy的标准模式,总会添加新的实例,replace的核心判断条件是父容器ID,相当于Activity的栈,遍历移除所有非当前实例的Fragment,但是被移除的Fragment会加入mOps记录(暂时作用不明),有点像Activity的栈内单例模式(不完全一样)

    举个例子便于理解就是,假设一个容器ID中有5个Fragment各不相同,向其中add5个fragment中的一个就会有两个相同的fragment共六个实例

    而replace则会把其他四个移除(添加到mOps中),只保留要replace的那个实例,关键在于,它没有添加新的实例

    Note:目前只是分析理解了add和replace过程对Fragment容器List的管理,还没有解析FragmentManager怎么把List中的Fragment显示或加载的,后续还需要学习

    这是目前的理解,随着不断学习,随时有可能修改!

    相关文章

      网友评论

        本文标题:Android源码阅读之Fragment Add和Replace

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