Fragment 源码解析add()和replace()方法

1.有问题的代码:

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private HomeFragment mHomeFragment;
    private FindFragment mFindFragment;
    private NewFragment mNewFragment;
    private MessageFragment mMessageFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.home_rb).setOnClickListener(this);
        findViewById(R.id.find_rb).setOnClickListener(this);
        findViewById(R.id.new_rb).setOnClickListener(this);
        findViewById(R.id.message_rb).setOnClickListener(this);

        // 默认一进入页面就添加主Fragment
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        mHomeFragment = new HomeFragment();
        fragmentTransaction.add(R.id.main_tab_fl, mHomeFragment);
        // 最后记得提交
        fragmentTransaction.commit();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.home_rb:
                homeRbClick();
                break;
            case R.id.find_rb:
                findRbClick();
                break;
            case R.id.new_rb:
                newRbClick();
                break;
            case R.id.message_rb:
                messageRbClick();
                break;
        }
    }


    private void homeRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        // 替换成当前页面
        fragmentTransaction.replace(R.id.main_tab_fl, mHomeFragment);
        fragmentTransaction.commit();
    }


    private void findRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (mFindFragment == null) {
            mFindFragment = new FindFragment();
        }
        fragmentTransaction.replace(R.id.main_tab_fl, mFindFragment);
        fragmentTransaction.commit();
    }


    private void newRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (mNewFragment == null) {
            mNewFragment = new NewFragment();
        }
        fragmentTransaction.replace(R.id.main_tab_fl, mNewFragment);
        fragmentTransaction.commit();
    }

    private void messageRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (mMessageFragment == null) {
            mMessageFragment = new MessageFragment();
        }
        fragmentTransaction.replace(R.id.main_tab_fl, mMessageFragment);
        fragmentTransaction.commit();
    }
}

目前的效果是这个样子的,看似没有任何的问题,这个也是最简单的方式

别的思路问题:一般的思路我们会换实现方法,当然其他方式肯定也可以实现如ViewPager+Fragment但是我们需要预加载要不然也会出问题,一旦预加载就需要去访问网络,即使用户可能不切换Fragment就退出App了这个时候其实加载了所有Fragment的数据,而且主页一旦复杂有可能会崩溃或造成内存溢出的问题。

2.源码分析:

add方法其实只是设置了一些必要参数,并没有做任何的处理,这也是说google为什么一定要我们不要忘记commit()的原因:

final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
public FragmentTransaction add(int containerViewId, Fragment fragment) {
        doAddOp(containerViewId, fragment, null, OP_ADD);
        return this;
    }

    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
        fragment.mFragmentManager = mManager;

        // tag可以说是唯一标识我们可以通过它从FragmentManager中找到对应的Fragment
        if (tag != null) {
            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
                throw new IllegalStateException("Can't change tag of fragment "
                        + fragment + ": was " + fragment.mTag
                        + " now " + tag);
            }
            fragment.mTag = tag;
        }

        if (containerViewId != 0) {
            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                throw new IllegalStateException("Can't change container ID of fragment "
                        + fragment + ": was " + fragment.mFragmentId
                        + " now " + containerViewId);
            }
            // 把Fragment的ContainerId和FragmentId指定为我们传递过来的布局中的ViewGroup的id。
            fragment.mContainerId = fragment.mFragmentId = containerViewId;
        }

        // 见名思意 Op是什么?就当是一些基本参数吧
        Op op = new Op();
        op.cmd = opcmd;
        op.fragment = fragment;
        addOp(op);
    }

    void addOp(Op op) {
        if (mHead == null) {
            mHead = mTail = op;
        } else {
            op.prev = mTail;
            mTail.next = op;
            mTail = op;
        }
        op.enterAnim = mEnterAnim;
        op.exitAnim = mExitAnim;
        op.popEnterAnim = mPopEnterAnim;
        op.popExitAnim = mPopExitAnim;
        mNumOp++;
    }

既然add方法只是设置了一些参数而已,那么肯定就在commit()中做了些什么,找啊找啊找啊找,找到这么个方法(有些代码我就省略):

void moveToState(Fragment f, int newState, int transit, 
    int transitionStyle, boolean keepActive){
                // ... 省略部分代码
                f.onAttach(mHost.getContext());
                // 这个方法一调用就会执行Fragment的onAttach(Activity activity)这个生命周期方法
                if (f.mParentFragment == null) {
                    mHost.onAttachFragment(f);
                }

                if (!f.mRetaining) {
                    f.performCreate(f.mSavedFragmentState);
                    // 执行生命周期onCreate(savedInstanceState);
                }
                f.mRetaining = false;
                if (f.mFromLayout) {
                    ViewGroup container = null;
                    if (f.mContainerId != 0) {
                         //从activity中找到我们需要存放Fragment的ViewGroup布局
                         container = (ViewGroup)mContainer.onFindViewById(f.mContainerId);
                         if (container == null && !f.mRestored) {
                              throwException(new IllegalArgumentException(
                                   "No view found for id 0x"
                                   + Integer.toHexString(f.mContainerId) + " ("
                                   + f.getResources().getResourceName(f.mContainerId)
                                   + ") for fragment " + f));
                        }
                    }
                    // For fragments that are part of the content view
                    // layout, we need to instantiate the view immediately
                    // and the inflater will take care of adding it.
                    f.mView = f.performCreateView(f.getLayoutInflater(
                        f.mSavedFragmentState), null, f.mSavedFragmentState);
                    // 这个方法过后会执行onCreateView()生命周期且f.mView就是我们自己覆盖Fragment返回的View
                    if (f.mView != null) {
                        f.mInnerView = f.mView;
                        // v4包兼容11以下的版本我还是没说错啊
                        if (Build.VERSION.SDK_INT >= 11) {
                            ViewCompat.setSaveFromParentEnabled(f.mView, false);
                        } else {
                            f.mView = NoSaveStateFrameLayout.wrap(f.mView);
                        }
                        
                        if (container != null) {
                            Animation anim = loadAnimation(f, transit, true,
                            transitionStyle);
                            if (anim != null) {
                                  setHWLayerAnimListenerIfAlpha(f.mView, anim);
                                  f.mView.startAnimation(anim);
                            }
                            // 如果ViewGroup不等于null就把从onCreateView()生命周期中获得的View添加到该布局中
                            // 最主要的就是这个方法,其实我们可以把Fragment理解成一个自定义的类
                            // 通过onCreateView()获取的到View添加到一个FragmentActivity的一个ViewGroup中
                            // 只不过它有自己的生命周期而已......
                            container.addView(f.mView);
                        }
                        // 如果是隐藏那就设置为不可见
                        if (f.mHidden) f.mView.setVisibility(View.GONE);
                        // 执行onViewCreated()生命周期方法
                        f.onViewCreated(f.mView, f.mSavedFragmentState);
                    } else {
                        f.mInnerView = null;
                    }
        
                    f.performActivityCreated(f.mSavedFragmentState);
                    if (f.mView != null) {
                        f.restoreViewState(f.mSavedFragmentState);
                    }
                    f.mSavedFragmentState = null;
                }
                // 代码省略......
         }
}
// 后面的我们就不看了,这上面的代码我自己做了一些整合,把它连贯起来了
// 因为我们把add方法写在了Activity中的onCreate()方法中所以做了一些处理......

到这里应该能够了解Fragment的工作流程了吧,接下来我们看replace方法中究竟做了?其实和add差不多只是把int opcmd变成了OP_REPLACE替换操作:

public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
        if (containerViewId == 0) {
            throw new IllegalArgumentException("Must use non-zero containerViewId");
        }

        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
        return this;
    }
这个时候去commit会调用mManager.removeFragment(old, transition, transitionStyle)方法把原来的移除,然后把当前的Fragment添加进去,那岂不是每点击一个上一就被销毁了,那之前动画到哪里来了做了写什么事都被干掉重新创建了
if (mManager.mAdded != null) {
    for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
        Fragment old = mManager.mAdded.get(i);
        if (old.mContainerId == containerId) {
             if (old == f) {
                 op.fragment = f = null;
             } else {
                if (op.removed == null) {
                    op.removed = new ArrayList<Fragment>();
                }
                op.removed.add(old);
                old.mNextAnim = exitAnim;
                if (mAddToBackStack) {
                    old.mBackStackNesting += 1;
                }
                mManager.removeFragment(old, transition, transitionStyle);
            }
        }
    }
}
if (f != null) {
    f.mNextAnim = enterAnim;
    mManager.addFragment(f, false);
}
这里写图片描述
 

3.优化代码:

  到这里源码就以完毕有兴趣的小伙伴可以自己仔细去看看源码,接下来我们就来解决问题,我们肯定在调用replace方法的时候希望它不要移除原来的,那怎么办改Android的底层源码吗?那就只能换方法了,思路就是如果该Fragment不存在FragmentManager中我们就去添加,否则我们把之前的隐藏而不是替换移除,把当前的显示即可,最后代码就是:

private void homeRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        List<Fragment> fragments = fragmentManager.getFragments();
        for (Fragment fragment : fragments) {
            fragmentTransaction.hide(fragment);
        }

        fragmentTransaction.show(mHomeFragment);
        fragmentTransaction.commit();
    }

    @OnClick(R.id.find_rb)
    private void findRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        List<Fragment> fragments = fragmentManager.getFragments();
        for (Fragment fragment : fragments) {
            fragmentTransaction.hide(fragment);
        }

        if(mFindFragment == null){
            mFindFragment = new FindFragment();
            fragmentTransaction.add(R.id.main_tab_fl,mFindFragment);
        }else {
            fragmentTransaction.show(mFindFragment);
        }

        fragmentTransaction.commit();
    }

    @OnClick(R.id.new_rb)
    private void newRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        List<Fragment> fragments = fragmentManager.getFragments();
        for (Fragment fragment : fragments) {
            fragmentTransaction.hide(fragment);
        }

        if(mNewFragment == null){
            mNewFragment = new NewFragment();
            fragmentTransaction.add(R.id.main_tab_fl,mNewFragment);
        }else {
            fragmentTransaction.show(mNewFragment);
        }

        fragmentTransaction.commit();
    }

    @OnClick(R.id.message_rb)
    private void messageRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        List<Fragment> fragments = fragmentManager.getFragments();
        for (Fragment fragment : fragments) {
            fragmentTransaction.hide(fragment);
        }

        if(mMessageFragment == null){
            mMessageFragment = new MessageFragment();
            fragmentTransaction.add(R.id.main_tab_fl,mMessageFragment);
        }else {
            fragmentTransaction.show(mMessageFragment);
        }

        fragmentTransaction.commit();
    }
封装
 1 public class FragmentManagerHelper {
 2     // 管理类FragmentManager
 3     private FragmentManager mFragmentManager;
 4     // 容器布局id containerViewId
 5     private int mContainerViewId;
 6 
 7     /**
 8      * 构造函数
 9      * @param fragmentManager 管理类FragmentManager
10      * @param containerViewId 容器布局id containerViewId
11      */
12     public FragmentManagerHelper(@Nullable FragmentManager fragmentManager, @IdRes int containerViewId) {
13         this.mFragmentManager = fragmentManager;
14         this.mContainerViewId = containerViewId;
15     }
16 
17     /**
18      * 添加Fragment
19      */
20     public void add(Fragment fragment){
21         // 开启事物
22         FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
23         // 第一个参数是Fragment的容器id,需要添加的Fragment
24         fragmentTransaction.add(mContainerViewId, fragment);
25         // 一定要commit
26         fragmentTransaction.commit();
27     }
28 
29     /**
30      * 切换显示Fragment
31      */
32     public void switchFragment(Fragment fragment){
33         // 开启事物
34         FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
35 
36         // 1.先隐藏当前所有的Fragment
37         List<Fragment> childFragments = mFragmentManager.getFragments();
38         for (Fragment childFragment : childFragments) {
39             fragmentTransaction.hide(childFragment);
40         }
41 
42         // 2.如果容器里面没有我们就添加,否则显示
43         if(!childFragments.contains(fragment)){
44             fragmentTransaction.add(mContainerViewId,fragment);
45         }else{
46             fragmentTransaction.show(fragment);
47         }
48 
49         // 替换Fragment
50         // fragmentTransaction.replace(R.id.main_tab_fl,mHomeFragment);
51         // 一定要commit
52         fragmentTransaction.commit();
53     }
54 }
View Code


原文地址:https://www.cnblogs.com/ganchuanpu/p/8135137.html