美文网首页安卓
Fragment源码解析

Fragment源码解析

作者: android源码探索 | 来源:发表于2020-01-19 14:55 被阅读0次

    欢迎大家下载我个人开发的app[安琪花园](https://www.jianshu.com/p/ce12fcfb15de

    1. fragment 是如何添加到界面上的
    2. fragment 的源码是怎么实现的

    带着上面的两个问题对fragment的源码进行了阅读

    为什么我会去研究fragment的源码


    device-2020-01-20-164643.png

    当时我在项目中有这样一个需求, 要在首页的第一个tab的右下角添加悬浮按钮(不是整个Activity).
    当前是VIP 在第一tab. 对于我公司的项目,第一个Tab是会经常变化的。有可能下次商城的Tab就放在了最前面。对于这个需求怎么去实现呢?

    面临如下两个问题:

    1. 如果悬浮按钮添加在Activity的布局上面,那么底部几个tab切换也会出现悬浮按钮,显示不符合需求
    2. 如果悬浮按钮添加在fragment上面,那么第一个tab每次切换都需要去改动fragment的布局代码。这太麻烦了

    如何才能实现一次 对后面fragment如何改动都不用去改代码呢? 这就需要我们对fragment是如何添加到屏幕上的要有所了解。

    思路


    1. 自定义一个控件NewsMainRelativeLayout,对于右下角的悬浮按钮的功能全部封装在这一个控件里面
    1. 先看如下的一段代码
            FragmentTransaction ft = fm.beginTransaction();
            ft.replace(R.id.content, fragment);
            ft.commitAllowingStateLoss();
    

    fragment 添加到界面上只需要调用 上面的几句代码。
    通过分析知, fragment里面的oncreateView方法返回的View, 是添加到R.id.content这个控件上面的。

    1. 自定义一个FragmentContentView控件, 这个控件的id就是第二步里面的R.id.content。
      重写该控件的addView方法。
     @Override
        public void addView(View child, int index, ViewGroup.LayoutParams params) {
            if(child instanceof NewsMainRelativeLayout){
                super.addView(child, index, params);
                return;
            }
    
            if(getContext() instanceof MainActivity){
                FragmentManager manager = ((MainActivity) getContext()).getSupportFragmentManager();
                List<Fragment> fragments = manager.getFragments();
                if(fragments != null && fragments.size() > 0){
                    if(child == fragments.get(0).getView()){
                        NewsMainRelativeLayout newsLayout = new NewsMainRelativeLayout(getContext());
                        newsLayout.addView(child);
                        super.addView(newsLayout, index, params);
                    }else{
                        super.addView(child, index, params);
                    }
                }else{
                    super.addView(child, index, params);
                }
            }else{
                super.addView(child, index, params);
            }
        }
    

    其思路就是一种偷天换日的做法, 判断addView方法里面传过来的child是否和 第一个fragment里面的onCreateView方法返回的View相同。 如果相同,则在外面包裹一层NewsMainRelativeLayout,在添加到R.id.content控件上面。这样就达到了产品需求的目的

    fragment是如何添加到界面上的


    首先明白以下三个类

    1. Fragment
    2. FragmentManager
    3. FragmentTransaction

    FragmentManager

    它是act相关联的,而是每个act都有一个自己的FragmentManager,
    内部有自己的状态mCurState,对应外部act的生命周期状态。
    主要提供 Activty 和Fragment交互的api
    

    实现类 FragmentManagerImpl

    FragmentTransaction

    封装了一系列对Fragment的操作, 包括add, remove, replace, show, hide,这些方法都是创建一个Op对象然后保存起来。
    

    实现类 BackStackRecord

    FragmentTransaction提供了如下的api:

      @NonNull
        public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag)  {
            doAddOp(0, fragment, tag, OP_ADD);
            return this;
        }
    
        @NonNull
        public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment) {
            doAddOp(containerViewId, fragment, null, OP_ADD);
            return this;
        }
    等等。。。。
    

    以FragmentTransaction的add方法来分析是如何实现的。

    在FragmentTransaction里面有一个内部类Op, 里面定义一次操作的相关属性, 而add方法就是新创建
    一个Op对象,然后保存起来。

       static final class Op {
            int mCmd;
            Fragment mFragment;
            int mEnterAnim;
            int mExitAnim;
            int mPopEnterAnim;
            int mPopExitAnim;
            Lifecycle.State mOldMaxState;
            Lifecycle.State mCurrentMaxState;
    
            Op() {
            }
    
            Op(int cmd, Fragment fragment) {
                this.mCmd = cmd;
                this.mFragment = fragment;
                this.mOldMaxState = Lifecycle.State.RESUMED;
                this.mCurrentMaxState = Lifecycle.State.RESUMED;
            }
    
            Op(int cmd, @NonNull Fragment fragment, Lifecycle.State state) {
                this.mCmd = cmd;
                this.mFragment = fragment;
                this.mOldMaxState = fragment.mMaxState;
                this.mCurrentMaxState = state;
            }
        }
    

    接下来看最为核心的代码FragmentTransaction.commit方法。

     @Override
        public int commit() {
            return commitInternal(false);
        }
    
     int commitInternal(boolean allowStateLoss) {
            if (mCommitted) throw new IllegalStateException("commit already called");
            if (FragmentManagerImpl.DEBUG) {
                Log.v(TAG, "Commit: " + this);
                LogWriter logw = new LogWriter(TAG);
                PrintWriter pw = new PrintWriter(logw);
                dump("  ", pw);
                pw.close();
            }
            mCommitted = true;
            if (mAddToBackStack) {
                mIndex = mManager.allocBackStackIndex(this);
            } else {
                mIndex = -1;
            }
            mManager.enqueueAction(this, allowStateLoss);
            return mIndex;
    

    最终会执行到上面这句代码,mManager.enqueueAction(this, allowStateLoss);

    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
            if (!allowStateLoss) {
                checkStateLoss();
            }
            synchronized (this) {
                if (mDestroyed || mHost == null) {
                    if (allowStateLoss) {
                        // This FragmentManager isn't attached, so drop the entire transaction.
                        return;
                    }
                    throw new IllegalStateException("Activity has been destroyed");
                }
                if (mPendingActions == null) {
                    mPendingActions = new ArrayList<>();
                }
                mPendingActions.add(action);
                scheduleCommit();
            }
        }
    

    上面这个方法只是入了队列,但是并没有真正的执行,而是调用了scheduleCommit方法,通过Handler来发送一个消息保存到队列里面的, 所以后续的逻辑 就是Handler来执行了

    对Handler不是很了解的可以看我写的 Handler总结

    接下看一下scheduleCommit方法

    void scheduleCommit() {
            synchronized (this) {
                boolean postponeReady =
                        mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
                boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
                if (postponeReady || pendingReady) {
                    mHost.getHandler().removeCallbacks(mExecCommit);
                    mHost.getHandler().post(mExecCommit);
                    updateOnBackPressedCallbackEnabled();
                }
            }
        }
    

    从上面的这个方法里面可以看到这样一句代码, mHost.getHandler().post(mExecCommit);
    mExecCommit就是一个Runnable,说白了就是往Handler里面发送了一条消息。

    通过源码的跟踪 最终会执行BackStackRecord里面的这个方法

     void executeOps() {
            final int numOps = mOps.size();
            for (int opNum = 0; opNum < numOps; opNum++) {
                final Op op = mOps.get(opNum);
                final Fragment f = op.mFragment;
                if (f != null) {
                    f.setNextTransition(mTransition, mTransitionStyle);
                }
                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_DETACH:
                        f.setNextAnim(op.mExitAnim);
                        mManager.detachFragment(f);
                        break;
                    case OP_ATTACH:
                        f.setNextAnim(op.mEnterAnim);
                        mManager.attachFragment(f);
                        break;
                    case OP_SET_PRIMARY_NAV:
                        mManager.setPrimaryNavigationFragment(f);
                        break;
                    case OP_UNSET_PRIMARY_NAV:
                        mManager.setPrimaryNavigationFragment(null);
                        break;
                    case OP_SET_MAX_LIFECYCLE:
                        mManager.setMaxLifecycle(f, op.mCurrentMaxState);
                        break;
                    default:
                        throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
                }
                if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) {
                    mManager.moveFragmentToExpectedState(f);
                }
            }
            if (!mReorderingAllowed) {
                // Added fragments are added at the end to comply with prior behavior.
                mManager.moveToState(mManager.mCurState, true);
            }
        }
    

    这个方法对每一种操作进行会进行处理, OP_ADD 的操作会执行如下的方法 对Fragment的状态进行更改

    public void addFragment(Fragment fragment, boolean moveToStateNow) {
            if (DEBUG) Log.v(TAG, "add: " + fragment);
            makeActive(fragment);
            if (!fragment.mDetached) {
                if (mAdded.contains(fragment)) {
                    throw new IllegalStateException("Fragment already added: " + fragment);
                }
                synchronized (mAdded) {
                    mAdded.add(fragment);
                }
                fragment.mAdded = true;
                fragment.mRemoving = false;
                if (fragment.mView == null) {
                    fragment.mHiddenChanged = false;
                }
                if (isMenuAvailable(fragment)) {
                    mNeedMenuInvalidate = true;
                }
                if (moveToStateNow) {
                    moveToState(fragment);
                }
            }
        }
    
    

    从上面的这一连串的源码分析可以看到 addFragmet的执行流程,

    但是我们并没有看到fragment的onCreateView方法添加到父控件上面。

    这个问题需要从Fragment保存的状态去查找答案。Fragment的状态有如下几种
    static final int INITIALIZING = 0; // Not yet created.
    static final int CREATED = 1; // Created.
    static final int ACTIVITY_CREATED = 2; // Fully created, not started.
    static final int STARTED = 3; // Created and started, not resumed.
    static final int RESUMED = 4;

    而Fragment的状态改变是通过FragmentManagerImpl的moveToState方法去改变的

     void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                         boolean keepActive) {
           **** 省略 部分代码 ***
                switch (f.mState) {
                    case Fragment.INITIALIZING:
                       *** 省略部分代码***
                    case Fragment.CREATED
                        if (newState > Fragment.INITIALIZING) {
                            ensureInflatedFragmentView(f);
                        }
    
                        if (newState > Fragment.CREATED) {
                            if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                            if (!f.mFromLayout) {
                                ViewGroup container = null;
                                if (f.mContainerId != 0) {
                                    if (f.mContainerId == View.NO_ID) {
                                        throwException(new IllegalArgumentException(
                                                "Cannot create fragment "
                                                        + f
                                                        + " for a container view with no id"));
                                    }
                                    container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                                    if (container == null && !f.mRestored) {
                                        String resName;
                                        try {
                                            resName = f.getResources().getResourceName(f.mContainerId);
                                        } catch (Resources.NotFoundException e) {
                                            resName = "unknown";
                                        }
                                        throwException(new IllegalArgumentException(
                                                "No view found for id 0x"
                                                        + Integer.toHexString(f.mContainerId) + " ("
                                                        + resName
                                                        + ") for fragment " + f));
                                    }
                                }
                                f.mContainer = container;
                                f.performCreateView(f.performGetLayoutInflater(
                                        f.mSavedFragmentState), container, f.mSavedFragmentState);
                                if (f.mView != null) {
                                    f.mInnerView = f.mView;
                                    f.mView.setSaveFromParentEnabled(false);
                                    if (container != null) {
                                        container.addView(f.mView);
                                    }
                                    if (f.mHidden) {
                                        f.mView.setVisibility(View.GONE);
                                    }
                                    f.onViewCreated(f.mView, f.mSavedFragmentState);
                                    dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
                                            false);
                                    // Only animate the view if it is visible. This is done after
                                    // dispatchOnFragmentViewCreated in case visibility is changed
                                    f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
                                            && f.mContainer != null;
                                } else {
                                    f.mInnerView = null;
                                }
                            }
    
                            f.performActivityCreated(f.mSavedFragmentState);
                            dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
                            if (f.mView != null) {
                                f.restoreViewState(f.mSavedFragmentState);
                            }
                            f.mSavedFragmentState = null;
                        }
                        // fall through
                  *** 省略部分代码***
        }
    

    当状态为Created的时候 这个时候 就把oncreateView 返回的View添加到了控件上面。
    这就能够解决最开头我项目上面所遇到的问题

    公众号:

    qrcode_for_gh_c78cd816dc53_344.jpg

    相关文章

      网友评论

        本文标题:Fragment源码解析

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