Android开发Android笔记整理Android开发经验谈

Android源码阅读之Fragment Add和Replace

2019-07-28  本文已影响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分支中,其实现为:

总结

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显示或加载的,后续还需要学习

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

上一篇 下一篇

猜你喜欢

热点阅读