线上突然报了一个崩溃,而且只出现一次,崩溃log如下
Caused by: java.lang.IllegalStateException: Fragment already added: d{f6ae815} (92a5a4f9-b403-4c4b-880a-d4c09bf076f6 tag=share)
at d.n.a.h0.a(FragmentStore.java:7)
at d.n.a.a0.a(FragmentManager.java:6)
at d.n.a.a.m(BackStackRecord.java:26)
at d.n.a.a0.E(FragmentManager.java:57)
at d.n.a.a0.a0(FragmentManager.java:10)
at d.n.a.a0.C(FragmentManager.java:14)
at d.n.a.a0$g.run(FragmentManager.java:1)
at android.os.Handler.handleCallback(Handler.java:808)
可以发现,这个崩溃是系统的Hander触发add fragment而导致的崩溃,调用堆栈都是系统方法,无法直接定位
先看下崩溃的地方,崩溃的类是FragmentStore
,这个是Android X的一个类,可以定位到具体的崩溃的地方如下
void addFragment(@NonNull Fragment fragment) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
synchronized (mAdded) {
mAdded.add(fragment);
}
fragment.mAdded = true;
}
说明崩溃原因是add了一个已经被added的fragment,另外通过自定义的上报信息,定位到了崩溃的页面,是DetailActivity
由于崩溃的时候,同时打印了fragment的信息,其实打印的就是fragment的toString方法,继续看下这个方法
public String toString() {
StringBuilder sb = new StringBuilder(128);
Class<?> cls = getClass();
sb.append(cls.getSimpleName());
sb.append("{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append("}");
sb.append(" (");
sb.append(mWho);
if (mFragmentId != 0) {
sb.append(" id=0x");
sb.append(Integer.toHexString(mFragmentId));
}
if (mTag != null) {
sb.append(" tag=");
sb.append(mTag);
}
sb.append(")");
return sb.toString();
}
fragment的基本信息都打印出来了,而且可以看到fragment的tag也打印了,是share
,由此可以找到具体异常的代码了
val shareDialog by lazy {
ShareDialogFragment()
}
binding.ivShape.setOnClickListener {
shareDialog.show(supportFragmentManager, "share")
}
是shareDialog
show的时候,发生了崩溃
首先是复现崩溃,多种方式尝试,包括开启不保留,也都没有复现,说明这个写法本身没有问题,然后继续看下show的内部源码看看,调用堆栈如下
一直调用到了scheduleCommit
方法,看下这个方法
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();
}
}
}
可以知道,真正的add fragment的行为,是用handler的post方法实现,是异步执行,post后的执行调用栈如下
最终调用到了addFragment方法
void addFragment(@NonNull Fragment fragment) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
synchronized (mAdded) {
mAdded.add(fragment);
}
fragment.mAdded = true;
}
这个方法,也就是线上崩溃的地方,通过代码,很容易知道,崩溃的原因是,这个方法被执行了两次,为什么会执行两次,因为真正执行的add行为,是handler的post方法去执行,猜测是用户在短时间内快速点击,触发了重复执行show
方法
通过快速点击,也顺利复现了这个崩溃,崩溃原因确定
快速重复点击,触发重复add同个fragment,导致的崩溃
定位到了问题,修复就比较简单了,有两个方法
binding.ivShape.setOnClickListener {
if (!EventUtil.isProcessing(500)){
shareDialog.show(supportFragmentManager, "share")
}
}
比如,500ms内的点击,只有一次点击是有效的
binding.ivShape.setOnClickListener {
val shareDialog = ShareDialogFragment()
shareDialog.show(supportFragmentManager, "share")
}
这个方案在快速重复点击下,会展示两个一样的fragment
综合考虑,最终选用方案1