原有函数:
private var tag = false
private var backMusicDialogFragment: BackMusicDialog? = null // DialogFragment
private fun addFragment() {
supportFragmentManager.let {
if (null == backMusicDialogFragment) {
backMusicDialogFragment = BackMusicDialog().newInstance()
}
if (backMusicDialogFragment?.isAdded == false) {
backMusicDialogFragment?.show(it)
}
}
}
手动模拟,连续调用两次 addFragment 函数,则曝出异常。而实际上,在show的时候,判断 isAdded,而这个判断并没有生效。于是看源码,找到:
// androidx.fragment.app.FragmentManager
private Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions(true);
}
};
void scheduleCommit() {
synchronized (mPendingActions) {
boolean postponeReady =
mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions.size() == 1;
if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
updateOnBackPressedCallbackEnabled();
}
}
}
这就找到原因了:
第一次:addFragment时,isAdd 为false,往下面执行,之后通过 handler 调用一次,排队进入Looper队列。Runnable 中的代码等待被执行
第二次:addFragment时,Runnable中的代码并未被执行,isAdd 依旧为 false,往下面执行,再通过 handler 调用一次。
Runnable 中的代码于是被调用了两次,第二次被调用时,崩溃了。崩溃的位置:
// androidx.fragment.app.FragmentStore
void addFragment(@NonNull Fragment fragment) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
synchronized (mAdded) {
mAdded.add(fragment);
}
fragment.mAdded = true;
}
解决方案:
private var tag = false
private var backMusicDialogFragment: BackMusicDialog? = null
private fun addFragment() {
if (tag) {
return
}
tag = true
supportFragmentManager.let {
if (null == backMusicDialogFragment) {
backMusicDialogFragment = BackMusicDialog().newInstance()
}
if (backMusicDialogFragment?.isAdded == false) {
backMusicDialogFragment?.show(it)
}
}
Handler().post {
tag = false
}
}
核心原理:对于同一个线程【这里是主线程】,Looper 中的 队列默认是排队执行的。也就是说,FragmentManager 中的runnable 调用完成之后,才会执行自己的 Handler().post 的方法。因此,保证了 show 整个流程结束,才会执行下一个show 流程。
show 的整个流程分为:同步调用 + handler 排队的调用