Fragment源码分析及fragment操作类的封装

Fragment我想大家肯定定不会陌生的,也是我们经常会在项目中经常用到的,我们也知道如何将fragment添加到Activity中。

首先我们先看一下我们的布局:很简单,FrameLayout+(一个第三方的bottomBar用法很简单)。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="moocollege.cn.fragmentanalysis.MainActivity">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottom_bar" />

    <com.ashokvarma.bottomnavigation.BottomNavigationBar
        android:id="@id/bottom_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:bnbActiveColor="#0eb5c5"
        android:layout_alignParentBottom="true" />

</RelativeLayout>
设置bottombar

      mBottomBar = (BottomNavigationBar) findViewById(R.id.bottom_bar);
        //mBottomBar一些基础配置 模式 添加内容 默认选中 添加监听

        mBottomBar.setMode(BottomNavigationBar.MODE_FIXED);
        mBottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC);
        mBottomBar.addItem(new BottomNavigationItem(R.mipmap.tab_ic_home_pressed, "首页").setInactiveIconResource(R.mipmap.tab_ic_home_default))
                .addItem(new BottomNavigationItem(R.mipmap.tab_ic_dynamic_pressed, "动态").setInactiveIconResource(R.mipmap.tab_ic_dynamic_default))
                .addItem(new BottomNavigationItem(R.mipmap.tab_ic_practice_pressed, "实习").setInactiveIconResource(R.mipmap.tab_ic_practice_default))
                .addItem(new BottomNavigationItem(R.mipmap.tab_ic_message_pressed, "消息").setInactiveIconResource(R.mipmap.tab_ic_message_default))
                .setFirstSelectedPosition(0)
                .initialise();
        mBottomBar.setTabSelectedListener(mBottomBarSelectedListener);

监听:

    private BottomNavigationBar.OnTabSelectedListener mBottomBarSelectedListener = new BottomNavigationBar.OnTabSelectedListener() {
        @Override
        public void onTabSelected(int position) {
            switchTo(position);
        }

        @Override
        public void onTabUnselected(int position) {

        }


        @Override
        public void onTabReselected(int position) {

        }
    };

Fragment切换之replace方法:

    /**
     * fragment的切换之replace方法
     *
     * @param position
     */
    private void switchTo(int position) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        switch (position) {
            case 0:
                if (mFragmentHome == null) {
                    mFragmentHome = new FragmentHome();
                }
                fragmentTransaction.replace(R.id.container, mFragmentHome);
                fragmentTransaction.commit();
                break;
            case 1:
                if (mFragmentDynamic == null) {
                    mFragmentDynamic = new FragmentDynamic();
                }
                fragmentTransaction.replace(R.id.container, mFragmentDynamic);
                fragmentTransaction.commit();
                break;
            case 2:
                if (mFragmentPractice == null) {
                    mFragmentPractice = new FragmentPractice();
                }
                fragmentTransaction.replace(R.id.container, mFragmentPractice);
                fragmentTransaction.commit();
                break;
            case 3:
                if (mFragmentMessage == null) {
                    mFragmentMessage = new FragmentMessage();
                }
                fragmentTransaction.replace(R.id.container, mFragmentMessage);
                fragmentTransaction.commit();
                break;
        }
    }

我们来看一下效果:

你发现什么了吗,仔细看,我们切换到实习模块的时候,我在顶部做了个标记,然后我把实习当前页面画到最底部,我们看到了红色字体的实习了吧,然后我们切换到别的模块去,然后我们再切换到实习模块回来,看到了吧,我们页面又回到顶部啦,是的,没错他就是回到了顶部,这样可不是很好的体验。

Fragment添加(初始化第一个fragment)

       //加载第一个fragment
        //getSupportFragmentManager()可以兼容11以下的版本
        FragmentManager fragmentManager = getSupportFragmentManager();
        //开启事物
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        //把第一个fragment加进来
        mFragmentHome = new FragmentHome();
        fragmentTransaction.add(R.id.container, mFragmentHome);
        //提交
        fragmentTransaction.commit();

那我们的add方法干了些什么呢?

我们进入到源码发现add方法是一个抽象方法,FragmentTransaction是一个抽象类,FragmentManager同样是一个抽象类,接着我们来看看

getSupportFragmentManager()干了些什么。

 return mHost.getFragmentManagerImpl();

看到这里我们猜一猜FragmentManagerImpl是不是FragmentManager的子类呢?不出意外应该是的吧,点进去果然是的:

FragmentManagerImpl中实现了beginTransaction方法,该方法返回一个

return new BackStackRecord(this);

在BackStackRecord中add返回一个FragmentTransaction

    @Override
    public FragmentTransaction add(int containerViewId, Fragment fragment) {
        doAddOp(containerViewId, fragment, null, OP_ADD);
        return this;
    }

doAppOp()干了些什么呢?这其实只是设置一些参数

       private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
        final Class fragmentClass = fragment.getClass();
        final int modifiers = fragmentClass.getModifiers();
        if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
                || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
            throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                    + " must be a public static class to be  properly recreated from"
                    + " instance state.");
        }

        fragment.mFragmentManager = mManager;

        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 (containerViewId == View.NO_ID) {
                throw new IllegalArgumentException("Can't add fragment "
                        + fragment + " with tag " + tag + " to container view with no id");
            }
            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                throw new IllegalStateException("Can't change container ID of fragment "
                        + fragment + ": was " + fragment.mFragmentId
                        + " now " + containerViewId);
            }
            fragment.mContainerId = fragment.mFragmentId = containerViewId;
        }

        Op op = new Op();
        op.cmd = opcmd;
        op.fragment = fragment;
        addOp(op);
    }

接着是commit方法:

    @Override
    public int commit() {
        return commitInternal(false);
    }

经过commint的后面一系列的方法之后,这中间的源码比较多,我就贴出一些方法,愿意看的可以在源码中去找,不愿意看的看主要步骤就可以了

    commitInternal-->mManager.enqueueAction--> scheduleCommit()->mHost.getHandler().post(mExecCommit)->
    execPendingActions()-->doPendingDeferredStart()-->startPendingDeferredFragments()-->performPendingDeferredStart
    -->moveToState(f, mCurState, 0, 0, false)

最终会来FragmentManager的moveToState(f, mCurState, 0, 0, false);

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));
                        }
                    }
                    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;
                }
                // 代码省略......
         }
}

在这里会去回调Fragment的一些生命周期

onAttach

onCreate

onCreateView会返回一个View,就是我们Fragment的布局

然后会去调用container.addView把我们刚才返回的View添加进去到我们的Activity中。


下面看看我们的replace方法干了些什么?

在在BackStackRecord中replace方法干了些什么?

@Override
    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
        return replace(containerViewId, fragment, null);
    }

和add一样返回一个FragmentTransaction

add方法中:

doAddOp(containerViewId, fragment, tag, OP_ADD);

replace方法:

doAddOp(containerViewId, fragment, tag, OP_REPLACE);
两个都是调用先沟通的方法,只是我们的opcmd不同,一个是OP_ADD,一个是OP_REPLACE

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {

当我们的opcmd = OP_REPLACE的时候

                  case OP_REPLACE: {
                    Fragment f = op.fragment;
                    int containerId = f.mContainerId;
                    boolean alreadyAdded = false;
                    for (int i = added.size() - 1; i >= 0; i--) {
                        Fragment old = added.get(i);
                        if (old.mContainerId == containerId) {
                            if (old == f) {
                                alreadyAdded = true;
                            } else {
                                Op removeOp = new Op();
                                removeOp.cmd = OP_REMOVE;
                                removeOp.fragment = old;
                                removeOp.enterAnim = op.enterAnim;
                                removeOp.popEnterAnim = op.popEnterAnim;
                                removeOp.exitAnim = op.exitAnim;
                                removeOp.popExitAnim = op.popExitAnim;
                                mOps.add(opNum, removeOp);
                                added.remove(old);
                                opNum++;
                            }
                        }
                    }
                    if (alreadyAdded) {
                        mOps.remove(opNum);
                        opNum--;
                    } else {
                        op.cmd = OP_ADD;
                        added.add(f);
                    }
                }
                break;

遍历我们的一个fragment集合,把我们老的fragment,之前的fragment移除掉,然后就会把我们新的fragment添加进来,所以就会再次去走我们上面的add方法,fragment的生命周期就会重新再走一遍,这也就解释了我们图里面的现象,切换回来的时候,我们的页面又回到最上面。当然这样的方法体验是很不好的,需要频繁的去创建和销毁fragment,浪费内存,如果有网络请求的话,还会频繁的去请求网络数据,约会消耗流量,也是耗时的操作

所以replace源码看出,就是把之前的移除,会重新执行Fragment生命周期, 会重新绘制界面

那么我们如何解决呢?

大概思路是这样的:如果容器中没有就去添加,否则我们就去显示,显示之前需要把所有已经添加的不显示(隐藏);
    /**
     * fragment的切换之hide和show
     *
     * @param position
     */
    private void switchTo(int position) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        // 开启事物
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        // 获得所有的Fragment
        List<Fragment> childFragments = fragmentManager.getFragments();
        //隐藏所有的Fragment
        for (Fragment childFragment : childFragments) {
            fragmentTransaction.hide(childFragment);
        }
        switch (position) {
            case 0:
                if (mFragmentHome == null) {
                    mFragmentHome = new FragmentHome();
                }
                //如果容器里面没有我们就添加,否则显示
                if (!childFragments.contains(mFragmentHome)) {
                    fragmentTransaction.add(R.id.container, mFragmentHome);
                } else {
                    fragmentTransaction.show(mFragmentHome);
                }
                break;
            case 1:
                if (mFragmentDynamic == null) {
                    mFragmentDynamic = new FragmentDynamic();
                }
                if (!childFragments.contains(mFragmentDynamic)) {
                    fragmentTransaction.add(R.id.container, mFragmentDynamic);
                } else {
                    fragmentTransaction.show(mFragmentDynamic);
                }
                break;
            case 2:
                if (mFragmentPractice == null) {
                    mFragmentPractice = new FragmentPractice();
                }
                if (!childFragments.contains(mFragmentPractice)) {
                    fragmentTransaction.add(R.id.container, mFragmentPractice);
                } else {
                    fragmentTransaction.show(mFragmentPractice);
                }
                break;
            case 3:
                if (mFragmentMessage == null) {
                    mFragmentMessage = new FragmentMessage();
                }
                if (!childFragments.contains(mFragmentMessage)) {
                    fragmentTransaction.add(R.id.container, mFragmentMessage);
                } else {
                    fragmentTransaction.show(mFragmentMessage);
                }
                break;
        }
        fragmentTransaction.commit();
    }
我们再来看使用hide和show之后的效果:


和第一次比较明显我切换的时候是不会重新绘制界面了,然而我们看到上面的代码,感觉比较冗余,有很多重复代码,于是我准备写一个fragment的操作类:

如下:
public class FragmentManagerOperator {
    private FragmentManager mFragmentManager;
    private int mContainerId;

    /**
     * 构造函数
     * @param fragmentManager 管理类FragmentManager
     * @param containerId 容器布局ID
     */
    public FragmentManagerOperator(@Nullable FragmentManager fragmentManager, @IdRes int containerId) {
        this.mFragmentManager = fragmentManager;
        this.mContainerId = containerId;
    }

    /**
     * 添加fragment
     * @param fragment
     */
    public void add(Fragment fragment){
        //开启事物
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        fragmentTransaction.add(mContainerId, fragment);
        //提交
        fragmentTransaction.commit();
    }

    public void changeFragment(Fragment fragment){
        // 开启事物
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        // 先隐藏当前所有的Fragment
        List<Fragment> childFragments = mFragmentManager.getFragments();
        for (Fragment childFragment : childFragments) {
            fragmentTransaction.hide(childFragment);
        }

        // 2.如果容器里面没有我们就添加,否则显示
        if(!childFragments.contains(fragment)){
            fragmentTransaction.add(mContainerId,fragment);
        }else{
            fragmentTransaction.show(fragment);
        }
        // 一定要commit
        fragmentTransaction.commit();
    }

}
使用Fragment的操作类后:
在第一次初始化的时候
    private void initData() {
        mFragmentManagerOperator = new FragmentManagerOperator(getSupportFragmentManager(), R.id.container);
        mFragmentHome = new FragmentHome();
        mFragmentManagerOperator.add(mFragmentHome);
    }

切换fragment的时候:
    /**
     * 封装后
     *
     * @param position
     */
    private void switchTo(int position) {

        switch (position) {
            case 0:
                if (mFragmentHome == null) {
                    mFragmentHome = new FragmentHome();
                }
                mFragmentManagerOperator.changeFragment(mFragmentHome);
                break;
            case 1:
                if (mFragmentDynamic == null) {
                    mFragmentDynamic = new FragmentDynamic();
                }
                mFragmentManagerOperator.changeFragment(mFragmentDynamic);
                break;
            case 2:
                if (mFragmentPractice == null) {
                    mFragmentPractice = new FragmentPractice();
                }
                mFragmentManagerOperator.changeFragment(mFragmentPractice);
                break;
            case 3:
                if (mFragmentMessage == null) {
                    mFragmentMessage = new FragmentMessage();
                }
                mFragmentManagerOperator.changeFragment(mFragmentMessage);
                break;
        }
    }

我们在别的项目中如果要操作frament直接把该类拷过去就是,很方便。
 
操作类地址: 点击打开链接





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值