Fragment事务流程分析

Fragment事务流程分析

简言

简单的事务使用流程代码

getSupportFragmentManager().beginTransaction().add(R.id.fl, ftMain).commit();

使用的方法很简单,但是Activity是如何实现事务的管理的呢?

我们先上一个简单的类图
image-20200404103145645

这个里面FragementManagerImpl持有mHost,一开始不知道是如何设置的,后来才发现是通过attachHost方法进行设置的。

我们一步步去分析这个UML的视图是如何建立的

getSupportFragmentManager() 来看一下是如何获取的

getSupportFragmentManager()

// FragmentActivity.java
  final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
   public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }
//FragmentController.java
    //实际是HostCallbacks对象。
    private final FragmentHostCallback<?> mHost;

    public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
        return new FragmentController(callbacks);
    }

    private FragmentController(FragmentHostCallback<?> callbacks) {
        mHost = callbacks;
    }

    public FragmentManager getSupportFragmentManager() {
        return mHost.getFragmentManagerImpl();
    }

这里可以看到我们的Activity持有FragmentControl对象。而FragmentControl持有了HostCallbacks对象,而HostCallbacks是继承自FragmentHostCallback的。

    //FragmentHostCallback.java
    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
    FragmentManagerImpl getFragmentManagerImpl() {
        return mFragmentManager;
    }

可以看到这里HostCallback是持有FragmenManagerImpl的。而FragmenManagerImpl继承FragmentManager

所以其实我们的第一步骤getSupportFragmentManager()是获取到了一个FragmenManagerImpl对象。

beginTransaction

//FragmentManager.java
public FragmentTransaction beginTransaction() {
    return new BackStackRecord(this);
}

可以看到这里创建了一个BackStackRecord对象,对象持有了FragmentManager。这里的BackStackRecord是继承自FragmentTransaction类的。

到这里我们是获取获取到了一个事务记录类。也就是BackStackRecord

add

当有了事务 记录类之后,就可以进行各种事务的记录信息,add,remove,replace等等。我们这里只是跟踪我们的测试代码,其他的是有相同的处理机制。

//BackStackRecord.java
public FragmentTransaction add(int containerViewId, Fragment fragment) {
    doAddOp(containerViewId, fragment, null, OP_ADD);
    return this;
}

public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
    doAddOp(containerViewId, fragment, tag, OP_ADD);
    return this;
}

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    fragment.mFragmentManager = mManager;
    //一系列检测
    ...
    //将当前的操作添加到队列中
    addOp(new Op(opcmd, fragment));
}
//增加到处理队列中
void addOp(Op op) {
    mOps.add(op);
    //动画的处理
    op.enterAnim = mEnterAnim;
    op.exitAnim = mExitAnim;
    op.popEnterAnim = mPopEnterAnim;
    op.popExitAnim = mPopExitAnim;
}

可以看到,其实BackStackRecord内部维护了一个要执行的队列,当进行事务的提交时,肯定是需要挨个将这个队列进行去除执行的。

commit

现在我们已经准备好了一个队列了,当所有的事务内部要执行的操作处理完后,回通过commit()或者commitNowAllowingStateLoss(),commitNow()来进行提交。我们这里跟踪commit()的执行,剩下的留以后再慢慢分析。

//BackStackRecord.java
    public int commit() {
        return commitInternal(false);
    }
    int commitInternal(boolean allowStateLoss) {
        //防止重复提交
        if (mCommitted) {
            throw new IllegalStateException("commit already called");
        }
        ...
        mCommitted = true;
        if (mAddToBackStack) {//如果执行了addToBackStack,mAddToBackStack才会为true
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        //调用manager的入队操作
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

commit()主要执行了入队的操作。

#FragmentManager.java
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        //加锁
        synchronized (this) {
            ...
            mPendingActions.add(action);
            scheduleCommit();
        }
    }
    private void scheduleCommit() {
        synchronized (this) {
            ...
            //这里的mHost是Fragment中的HostCallBack,这里的getHandler获取到的是主线程Handler
            mHost.getHandler().removeCallbacks(mExecCommit);
            mHost.getHandler().post(mExecCommit);
        }
    }
    
    Runnable mExecCommit = new Runnable() {
        @Override
        public void run() {
            execPendingActions();
        }
    };

所以这里的是将要执行的BackStackRecord的记录信息存放到了mPendingActions队列之中,然后通过主线程的Looper机制来进行调用执行execPendingActions()方法。

execPendingActions

#FragmentManager.java
//只能从主线程调用
public boolean execPendingActions() {
    //进行一些线程的检测,是否销毁等的检测操作
    ensureExecReady(true);
    boolean didSomething = false;
    //generateOpsForPendingActions会将我们的PendingActions中的数据取出来,然后存放到临时的mTmpRecords中
    while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
        mExecutingActions = true;
        try {
            removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
        } finally {
            cleanupExec();
        }
        didSomething = true;
    }

    doPendingDeferredStart();
    burpActive();

    return didSomething;
}

我们看一下generateOpsForPendingActions是如何将数据获取并存放到临时list中的

#FragmentManager.java
    //遍历循环,将我们的PendingActions中的数据取出来,然后存放到临时的mTmpRecords队列中
    private boolean generateOpsForPendingActions(ArrayList<BackStackRecord> records,ArrayList<Boolean> isPop) {
    boolean didSomething = false;
    synchronized (this) {
        final int numActions = mPendingActions.size();
        for (int i = 0; i < numActions; i++) {
            didSomething |= mPendingActions.get(i).generateOps(records, isPop);
        }
        mPendingActions.clear();
        //当队列中的数据已经保存到临时的records中以后,移除回调
        mHost.getHandler().removeCallbacks(mExecCommit);
    }
    return didSomething;
}

所以是通过循环,调用了generateOps方法

//BackStackRecord.java
    public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Run: " + this);
        }
        //直接添加到list中
        records.add(this);
        //记录当前是非pop操作
        isRecordPop.add(false);
        if (mAddToBackStack) {//记录是否是入栈操作
            mManager.addBackStackState(this);
        }
        return true;
    }

其实这里面的存放到临时list中的操作相对来说比较简单。我们回到主线去看看将数据存放到临时list中以后,又对这些事务做了什么操作。也就是removeRedundantOperationsAndExecute()函数的作用。

removeRedundantOperationsAndExecute

//FragmentManager.java
//这里进行了事务的优化操作,事务的最终执行也由executeOpsTogether来执行的
    private void removeRedundantOperationsAndExecute(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {

        //完成以前被推迟但现在已经准备好的事务的执行。
        executePostponedTransaction(records, isRecordPop);
        final int numRecords = records.size();
        int startIndex = 0;
        for (int recordNum = 0; recordNum < numRecords; recordNum++) {
            //是否允许重新排序
            final boolean canReorder = records.get(recordNum).mReorderingAllowed;
            if (!canReorder) {//如果不允许重排序,那么会获取这个位置之后的连续不允许排序的队列然后批量执行
                //执行之前的所有操作
                if (startIndex != recordNum) {
                    //如果不允许重排序,那么就将之前的所有的操作批量执行
                    executeOpsTogether(records, isRecordPop, startIndex, recordNum);
                }
                //reorderingEnd会从当前不能重排序的位置开始,遍历搜索,寻找到连续的不能重排序的pop操作的位置
                //然后将recordNum到reorderingEnd的位置进行批量执行
                int reorderingEnd = recordNum + 1;
                if (isRecordPop.get(recordNum)) {
                    //当前是个pop操作,那么会一直搜索到下一个非pop的位置,然后记录。然后会将这些所有的pop操作批量执行
                    while (reorderingEnd < numRecords && isRecordPop.get(reorderingEnd) && !records.get(reorderingEnd).mReorderingAllowed) {
                        //是pop操作,不允许排序
                        reorderingEnd++;
                    }
                }
                executeOpsTogether(records, isRecordPop, recordNum, reorderingEnd);
                //设置下一个批量执行的起始位置
                startIndex = reorderingEnd;
                recordNum = reorderingEnd - 1;
            }
        }
        if (startIndex != numRecords) {
            executeOpsTogether(records, isRecordPop, startIndex, numRecords);
        }
    }

这里,会将我们的队列进行切割拆分,将连续的允许重新排序和不允许重新排序的分别切割出来,然后通过executeOpsTogether对队列进行一个批量的操作。我们看看如何批量处理的。

//执行BackStackRecords列表的一个子集集合,所有这些子集合要么都允许重新排序,要么都不允许排序。
private void executeOpsTogether(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
    //获取当前批次是否允许重排序
    final boolean allowReordering = records.get(startIndex).mReorderingAllowed;
    boolean addToBackStack = false;
    if (mTmpAddedFragments == null) {
        mTmpAddedFragments = new ArrayList<>();
    } else {
        mTmpAddedFragments.clear();
    }
    //保存已经添加的fragment临时快照
    mTmpAddedFragments.addAll(mAdded);
    //获取当前栈顶的fragment
    Fragment oldPrimaryNav = getPrimaryNavigationFragment();
    for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
        //获取事务
        final BackStackRecord record = records.get(recordNum);
        //判断事务是否为pop
        final boolean isPop = isRecordPop.get(recordNum);
        if (!isPop) {//重点方法   不是pop,那么就对事务中的操作进行优化
            oldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav);
        } else {//如果是pop操作,那么就执行反向操作。也就是add的反向操作和remove的反向操作
            record.trackAddedFragmentsInPop(mTmpAddedFragments);
        }
        addToBackStack = addToBackStack || record.mAddToBackStack;
    }
    //这里直接clear了是什么鬼?难道只是为了进行优化用的临时表??
    mTmpAddedFragments.clear();
    if (!allowReordering) {//不允许排序
        //开始转换
        FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
                false);
    }
    //重点方法  ,最重要的地方。是commit操作的最终执行的地方
    executeOps(records, isRecordPop, startIndex, endIndex);
    int postponeIndex = endIndex;
    if (allowReordering) {
        ArraySet<Fragment> addedFragments = new ArraySet<>();
        addAddedFragments(addedFragments);
        postponeIndex = postponePostponableTransactions(records, isRecordPop,startIndex, endIndex, addedFragments);
        makeRemovedFragmentsInvisible(addedFragments);
    }

    if (postponeIndex != startIndex && allowReordering) {
        // need to run something now
        FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
                postponeIndex, true);
        moveToState(mCurState, true);
    }

    for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
        final BackStackRecord record = records.get(recordNum);
        final boolean isPop = isRecordPop.get(recordNum);
        if (isPop && record.mIndex >= 0) {
            freeBackStackIndex(record.mIndex);
            record.mIndex = -1;
        }
        record.runOnCommitRunnables();
    }

    if (addToBackStack) {
        reportBackStackChanged();
    }
}

这个方法里面有两个需要关注的重点。

  1. expandOps()函数,对执行的事务会进行一个优化。
  2. executeOps()函数,用来执行最终的commit操作,将fragment和Activity进行关联绑定以及生命周期的同步。

这里我们可以简单看一下expandOps()函数中优化的逻辑。

//BackStackRecords.java
//对事务中的操作进行优化处理
Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
    for (int opNum = 0; opNum < mOps.size(); opNum++) {
        final Op op = mOps.get(opNum);
        switch (op.cmd) {
            case OP_ADD:
            case OP_ATTACH://如果是add或者attach,将当前fragment放入到added中
                added.add(op.fragment);
                break;
            case OP_REMOVE:
            case OP_DETACH: {//如果是移除操作,那么从added队列中移除fragment
                added.remove(op.fragment);
                if (op.fragment == oldPrimaryNav) {//如果移除的fragment是当前的oldPrimaryNav,需要做一个特殊处理,这里有点不太明白,以后再研究
                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));
                    opNum++;
                    oldPrimaryNav = null;
                }
            }
            break;
            case OP_REPLACE: {//如果是replace操作,那么就将对应的mContainerId中的fragment移除,然后再添加当前的fragment
                final Fragment f = op.fragment;
                final int containerId = f.mContainerId;//获取到当前fragment占用的containerId信息
                boolean alreadyAdded = false;
                for (int i = added.size() - 1; i >= 0; i--) {
                    final Fragment old = added.get(i);
                    if (old.mContainerId == containerId) {//如果已经添加的fragment的containerId和当前containerId是相同的,那么说明其占用的是同一个containerId,需要将added里面的移除
                        if (old == f) {//如果这个fragment已经在added列表中,存在,设置一个标记位。
                            alreadyAdded = true;
                        } else {//将所有其他使用这个containerId的fragment都执行一个remove操作。这里是通过向mOps中增加新的操作来进行处理的,而不是直接移除。是因为在遍历列表的时候,不能执行移除操作
                            // This is duplicated from above since we only make
                            // a single pass for expanding ops. Unset any outgoing primary nav.
                            if (old == oldPrimaryNav) {
                                mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
                                opNum++;
                                oldPrimaryNav = null;
                            }
                            final Op removeOp = new Op(OP_REMOVE, 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) {//表明这个opNum位置的fragment已经在add列表中存在了,那么其实不需要再执行这个replace命令了,直接将这个replace操作移除掉,这样可以达到优化效果
                    mOps.remove(opNum);
                    opNum--;
                } else {//如果不存在,证明这个fragment之前是不存在的,需要对它执行变为一个add操作
                    op.cmd = OP_ADD;
                    added.add(f);
                }
            }
            break;
            case OP_SET_PRIMARY_NAV: {
                // It's ok if this is null, that means we will restore to no active
                // primary navigation fragment on a pop.
                mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
                opNum++;
                // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
                oldPrimaryNav = op.fragment;
            }
            break;
        }
    }
    return oldPrimaryNav;
}

这里注释很详细,那么我们回到主线,看一下executeOps()函数的执行。

 //FragmentManager.java
 //执行操作
private static void executeOps(ArrayList<BackStackRecord> records,ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
    for (int i = startIndex; i < endIndex; i++) {
        final BackStackRecord record = records.get(i);
        final boolean isPop = isRecordPop.get(i);
        if (isPop) {//如果是pop操作
            //所有的管理的fragment后面的堆栈计数器-1
            record.bumpBackStackNesting(-1);
            // Only execute the add operations at the end of
            // all transactions.
            boolean moveToState = i == (endIndex - 1);
            //执行pop操作
            record.executePopOps(moveToState);
        } else {//如果是非pop操作
            //所有的管理的fragment后面的堆栈计数器+1
            record.bumpBackStackNesting(1);
            //执行ops操作
            record.executeOps();
        }
    }
}

这里先对fragment中的表明后面的片段计数器进行了更新。然后根据是否是pop,分别执行了executePopOpsexecuteOps方法,我们这里只先跟踪一下executeOps()这个方法的执行。

//BackStackRecords.java
//执行事务包含的操作,只有不允许优化的时候,fragment的状态才会被改变
void executeOps() {
    final int numOps = mOps.size();
    for (int opNum = 0; opNum < numOps; opNum++) {
        final Op op = mOps.get(opNum);
        final Fragment f = op.fragment;
        if (f != null) {//设置动画
            f.setNextTransition(mTransition, mTransitionStyle);
        }
        switch (op.cmd) {//通过manager来进行fragment的管理工作
            case OP_ADD:
                f.setNextAnim(op.enterAnim);
                mManager.addFragment(f, false);//这里我们队addFragment来进行一个跟踪处理
                break;
            case OP_REMOVE:
                f.setNextAnim(op.exitAnim);
                mManager.removeFragment(f);
                break;
            case OP_HIDE:
                f.setNextAnim(op.exitAnim);
                mManager.hideFragment(f);
                break;
            case OP_SHOW:
                f.setNextAnim(op.enterAnim);
                mManager.showFragment(f);
                break;
            case OP_DETACH:
                f.setNextAnim(op.exitAnim);
                mManager.detachFragment(f);
                break;
            case OP_ATTACH:
                f.setNextAnim(op.enterAnim);
                mManager.attachFragment(f);
                break;
            case OP_SET_PRIMARY_NAV:
                mManager.setPrimaryNavigationFragment(f);
                break;
            case OP_UNSET_PRIMARY_NAV:
                mManager.setPrimaryNavigationFragment(null);
                break;
            default:
                throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
        }
        if (!mReorderingAllowed && op.cmd != OP_ADD && f != null) {
            mManager.moveFragmentToExpectedState(f);
        }
    }
    if (!mReorderingAllowed) {
        // Added fragments are added at the end to comply with prior behavior.
        //对FragmentManager所有管理的fragment进行一次生命周期的同步
        mManager.moveToState(mManager.mCurState, true);
    }
}

这里是通过FragmentManager来对事务中的fragment来进行了管理,然后最后通过状态的同步,将本次事务的所有变化的fragment的生命周期和绑定的activity的生命周期进行一次同步。

总结

每一次学习都要有新的收获,这次通过学习fragment的事务执行的方法学到了新的知识点

  1. 事务的提交并不是直接就执行,而是通过主线程的Handler机制来执行的。我们经常遇到的add去显示的时候,有一个isAdd的崩溃,哪怕我们先进行了isAdd的判断,也拦截不住,就是因为Handler执行时,从队列取出消息,如果这时候还没有执行,那么isAdd就仍然是空。遇到过一种解决方案就是glide的,在add之前,将创建的fragment存放到缓存中,然后commit之后,再发送一个handler消息,从缓存中移除即可。
  2. 事务的执行是有优化的,会将一些能够优化的地方进行处理。
  3. 对于fragment的管理,是通过FragmentManager来统一进行的。
  4. 当有多个事务时,会将事务按照是否能够优化和是否为pop来进行分批,进行分批处理。

本篇文章由一文多发平台ArtiPub自动发布

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容