• 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


  • 相关阅读:
    LightOj 1016
    uva 127 "Accordian" Patience 简单模拟
    hdu 1180 诡异的楼梯 BFS + 优先队列
    UVALive 3907 Puzzle AC自动机+DP
    HDU 4001 To Miss Our Children Time DP
    HDU 4000 Fruit Ninja 树状数组
    hdu_1021_Fibonacci Again_201310232237
    hdu_1005_Number Sequence_201310222120
    hdu_1029-Ignatius and the Princess IV_201310180916
    hdu_1020_Encoding_201310172120
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/8135137.html
Copyright © 2020-2023  润新知