美文网首页安卓
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