前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >踩坑记-Fragment already added

踩坑记-Fragment already added

作者头像
韦东锏
发布2021-12-06 15:41:48
1.5K0
发布2021-12-06 15:41:48
举报
文章被收录于专栏:Android码农

背景

线上突然报了一个崩溃,而且只出现一次,崩溃log如下

代码语言:javascript
复制
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的一个类,可以定位到具体的崩溃的地方如下

代码语言:javascript
复制
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方法,继续看下这个方法

代码语言:javascript
复制
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,由此可以找到具体异常的代码了

代码语言:javascript
复制
val shareDialog by lazy { 
    ShareDialogFragment() 
}                         
binding.ivShape.setOnClickListener {                     
    shareDialog.show(supportFragmentManager, "share")   
}                                                       

shareDialogshow的时候,发生了崩溃

崩溃分析

首先是复现崩溃,多种方式尝试,包括开启不保留,也都没有复现,说明这个写法本身没有问题,然后继续看下show的内部源码看看,调用堆栈如下

一直调用到了scheduleCommit方法,看下这个方法

代码语言:javascript
复制
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方法

代码语言:javascript
复制
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,导致的崩溃

问题修复

定位到了问题,修复就比较简单了,有两个方法

增加互斥,短时间快速点击,第二次点击当做无效处理
代码语言:javascript
复制
binding.ivShape.setOnClickListener {                            
    if (!EventUtil.isProcessing(500)){                          
        shareDialog.show(supportFragmentManager, "share")      
    }                                                          
}                                                              

比如,500ms内的点击,只有一次点击是有效的

每次都是new一个fragment,避免用同一个fragment
代码语言:javascript
复制
binding.ivShape.setOnClickListener {                      
    val shareDialog = ShareDialogFragment()              
    shareDialog.show(supportFragmentManager, "share")    
}                                                        

这个方案在快速重复点击下,会展示两个一样的fragment

综合考虑,最终选用方案1

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 崩溃定位
  • 崩溃分析
  • 问题修复
    • 增加互斥,短时间快速点击,第二次点击当做无效处理
      • 每次都是new一个fragment,避免用同一个fragment
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档