Bootstrap

Android碎片事务提交transaction.commit()和transaction.commitnow()的区别以及源码完全解析

先普及一个知识

当你调用了碎片空的构造器的时候,你的碎片并不会执行生命周期的方法,如onCreateView()等


什么时候会执行生命周期呢?

FragmentManager manager = getSupportFragmentManager();

FragmentTransaction transaction = manager.beginTransaction();

transaction.add(mContainerId, mFragment);

transaction.commit();

当你把这个碎片add进fragmentmanager(碎片栈),或者replace(是remove和add的结合体)进去的时候,你才会进入碎片的生命周期。


既然commit就可以进入碎片的生命周期那commitNow又是做什么用的?

顾名思义,commitNow就是立刻提交事务,那也不难推断出commit其实提交后,并没有立刻执行你所期待的操作。


那么什么时候执行呢?他们的具体区别到底在哪呢?我用一个实例分析一下。

NavFragment navFragment = new NavFragment();
addFragment(R.id.fl_nav, navFragment);
navFragment.setup();
如果你的addFragment里的事务提交是commit方法,那么你的执行流程是这样的:虽然你addFragment方法里你commit了,但是commit是会被搁置的,当你活动中接下来的所有初始化代码执行完以后,才会去真正执行把碎片add进fragmentmanager的栈中,并且执行碎片一系列的生命周期操作。

如果你的addFragment里的事务提交是commitNow方法,那你的执行流程是这样的:先把碎片那一系列的生命周期操作执行了,让你的碎片真正被“激活”了,才会按顺序执行你activity中的余下代码。


我们先追溯一下commit的源码,在FragmentTransaction中

/**
 * Schedules a commit of this transaction.  The commit does
 * not happen immediately; it will be scheduled as work on the main thread
 * to be done the next time that thread is ready.
 *
 * <p class="note">A transaction can only be committed with this method
 * prior to its containing activity saving its state.  If the commit is
 * attempted after that point, an exception will be thrown.  This is
 * because the state after the commit can be lost if the activity needs to
 * be restored from its state.  See {@link #commitAllowingStateLoss()} for
 * situations where it may be okay to lose the commit.</p>
 * 
 * @return Returns the identifier of this transaction's back stack entry,
 * if {@link #addToBackStack(String)} had been called.  Otherwise, returns
 * a negative number.
 */
public abstract int commit();

你会惊讶的发现commit是一个抽象方法,并且附上了一段密密麻麻的注释。先不急找到这个方法的实现类,我们采用谷歌翻译对注释进行翻译抽取有用的信息。


计划提交此事务。提交确实不是马上发生;它将被安排在主线程上工作,待下次线程准备完成。

后面是讲到如果是因为你活动需要执行了这个commit,那么你在排队的过程中信息有可能会丢失,但是可以从存储的状态中恢复(没啥用)


经过寻找,BackStackRecord是FragmenTransaction的实现类。我们定位commit的方法

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

继续寻找commitInternal方法

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("  ", null, pw, null);
        pw.close();
    }
    mCommitted = true;
    if (mAddToBackStack) {
        mIndex = mManager.allocBackStackIndex(this);
    } else {
        mIndex = -1;
    }
    mManager.enqueueAction(this, allowStateLoss);
    return mIndex;
}
上面的debug不用看,看一下if语句里的,mIndex = mManager.allocBackStackIndex(this);意思就是取得当前回退栈中的序列号返回,其实也不用多关注,我们需要关注的是mManager.enqueueAction(this, allowStateLoss);方法。


我们再度定位一下enqueueAction方法

/**
 * Adds an action to the queue of pending actions.
 *
 * @param action the action to add
 * @param allowStateLoss whether to allow loss of state information
 * @throws IllegalStateException if the activity has been destroyed
 */
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
    if (!allowStateLoss) {
        checkStateLoss();
    }
    synchronized (this) {
        if (mDestroyed || mHost == null) {
            throw new IllegalStateException("Activity has been destroyed");
        }
        if (mPendingActions == null) {
            mPendingActions = new ArrayList<>();
        }
        mPendingActions.add(action);
        scheduleCommit();
    }
}
看一下传进来的第一个参数,有些人可能很好奇,我们传的是this,就是把BackStackRecord的实例传进来了,怎么回是这样一个对象?难道BackStackRecord实现了这个接口?
final FragmentManagerImpl mManager;

并没有,在那里的上下文是这个mManager,可以倒回去看一下,这个FragmentManager的实现类实现了OpGenerater这个接口。那就是把我们当前的操作传了进来(比如add碎片进栈,remove出栈,detach解除联系之类的操作)


ArrayList<OpGenerator> mPendingActions;

mPendingActions.add(action);
scheduleCommit();
这里第一句话就是把这个action进入等待序列中,其实就是用一个arrayList把操作存进去,等待执行。

然后下一行就是规划这个action的执行时间了。


继续追溯scheduleCommit()

/**
 * Schedules the execution when one hasn't been scheduled already. This should happen
 * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
 * a postponed transaction has been started with
 * {@link Fragment#startPostponedEnterTransition()}
 */
private 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);
        }
    }
}
Handler可以用来更新UI,也可以用来发送消息、处理消息。


道理我们都懂,那么mExecCommit的具体代码又是怎样的?

Runnable mExecCommit = new Runnable() {
    @Override
    public void run() {
        execPendingActions();
    }
};
这就很清晰了,开了一个子线程来执行等待队列里的操作。我们就是传一个子线程的实现给handler的post方法。


追溯post方法,这里就进入到我们熟悉的领域了

/**
 * Causes the Runnable r to be added to the message queue.
 * The runnable will be run on the thread to which this handler is 
 * attached. 
 *  
 * @param r The Runnable that will be executed.
 * 
 * @return Returns true if the Runnable was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
所以我们线程在这里被执行


追溯sendMessageDelayed

/**
 * Enqueue a message into the message queue after all pending messages
 * before (current time + delayMillis). You will receive it in
 * {@link #handleMessage}, in the thread attached to this handler.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

然后就把东西放到消息队列里,在设定的时间间隔后开始执行。不讲了,又是一系列出入队列的操作。


下面看看commitNow的源码,明白和commit的区别

* Commits this transaction synchronously. Any added fragments will be
* initialized and brought completely to the lifecycle state of their host
* and any removed fragments will be torn down accordingly before this
* call returns
翻译:同步执行这个事务,就是立刻执行,所有被加入的碎片都会被立刻完成生命周期状态,所以移除的碎片都会被撕碎。


* <p>Transactions committed in this way may not be added to the
* FragmentManager's back stack, as doing so would break other expected
* ordering guarantees for other asynchronously committed transactions.
翻译:以这种方式提交的交易可能不会被添加到FragmentManager的回退栈,这样做会破坏其他想要异步提交的事务(指代的就是commit,异步提交的事务)


追溯commitNow

@Override
public void commitNow() {
    disallowAddToBackStack();
    mManager.execSingleAction(this, false);
}
disallowAddToBackStack();佐证了观点上面翻译里的观点,不允许添加到回退栈中。

所以我们要执行execSingleAction,开始主线程里跑这个事务提交了


追溯execSingleAction

public void execSingleAction(OpGenerator action, boolean allowStateLoss) {
    ensureExecReady(allowStateLoss);
    if (action.generateOps(mTmpRecords, mTmpIsPop)) {
        mExecutingActions = true;
        try {
            optimizeAndExecuteOps(mTmpRecords, mTmpIsPop);
        } finally {
            cleanupExec();
        }
    }

    doPendingDeferredStart();
}

一开始我被误导了,以为optimizeAndExecuteOps才是执行commitNow的方法,其实这个方法是进行commitNow完成后的扫尾操作。因为commitNow直接在主线程提交的事务,所以是一种线程不安全的操作,并且影响了其他的transaction,所以后面的都是对其进行扫尾和优化的工作。


真正的执行

action.generateOps(mTmpRecords, mTmpIsPop)

所以是在这个接口的实现类里跑的。

/**
 * An add or pop transaction to be scheduled for the UI thread.
 */
interface OpGenerator {
    /**
     * Generate transactions to add to {@code records} and whether or not the transaction is
     * an add or pop to {@code isRecordPop}.
     *
     * records and isRecordPop must be added equally so that each transaction in records
     * matches the boolean for whether or not it is a pop in isRecordPop.
     *
     * @param records A list to add transactions to.
     * @param isRecordPop A list to add whether or not the transactions added to records is
     *                    a pop transaction.
     * @return true if something was added or false otherwise.
     */
    boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop);
}

具体实现是在哪呢?我也不知道啊,继承还能找找,这个我上哪去找啊。反正就是在这里执行的commitNow是跑不了的。


最后我找到了这个接口的实现,是在BackStackRecord中重写的。

/**
 * Implementation of {@link FragmentManagerImpl.OpGenerator}.
 * This operation is added to the list of pending actions during {@link #commit()}, and
 * will be executed on the UI thread to run this FragmentTransaction.
 *
 * @param records Modified to add this BackStackRecord
 * @param isRecordPop Modified to add a false (this isn't a pop)
 * @return true always because the records and isRecordPop will always be changed
 */
@Override
public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
    if (FragmentManagerImpl.DEBUG) {
        Log.v(TAG, "Run: " + this);
    }

    records.add(this);
    isRecordPop.add(false);
    if (mAddToBackStack) {
        mManager.addBackStackState(this);
    }
    return true;
}

;