Android稳定性:Looper兜底框架实现线上容灾(二)

背景

此前分享过相关内容,见: Android稳定性:可远程配置化的Looper兜底框架

App Crash对于用户来讲是一种最糟糕的体验,它会导致流程中断、app口碑变差、app卸载、用户流失、订单流失等。相关数据显示,当Android App的崩溃率超过0.4%的时候,活跃用户有明显下降态势。

项目思路来源于一次提问:有没有办法打造一个永不崩溃的app?

继续深挖这个问题后,我们其实有几个问题需要考虑:

  • 如何打造永不崩溃的 app
  • 当这样做了之后,app 还能正常运行吗?
  • 怎么才能在吃掉异常的同事,让主线程继续运行?
  • 异常被吃掉之后会有什么影响?
  • 到底什么异常需要被吃掉或者说可以吃掉?
  • 吃掉异常能给带来什么好处?是否能对线上问题进行容灾?

这些问题在 Android稳定性:可远程配置化的Looper兜底框架 都被一一解答了。

如何实现、如何更好的实现

实现代码参考 demo 项目: scuzoutao/AndroidCrashProtect

主要的核心代码就两部分:

  1. 按配置判断是否需要保护
kotlin 复制代码
fun needBandage(throwable: Throwable): Boolean {                                                                            
    if (crashPortrayConfig.isNullOrEmpty()) {                                                                               
        return false                                                                                                        
    }                                                                                                                       
                                                                                                                            
    val config: List<CrashPortray>? = crashPortrayConfig                                                                    
    if (config.isNullOrEmpty()) {                                                                                           
        return false                                                                                                        
    }                                                                                                                       
    for (i in config.indices) {                                                                                             
        val crashPortray = config[i]                                                                                        
        if (!crashPortray.valid()) {                                                                                        
            continue                                                                                                        
        }                                                                                                                   
                                                                                                                            
        //1. app 版本号                                                                                                        
        if (crashPortray.appVersion.isNotEmpty()                                                                            
            && !crashPortray.appVersion.contains(actionImpl.getVersionName(application))                                    
        ) {                                                                                                                 
            continue                                                                                                        
        }                                                                                                                   
                                                                                                                            
        //2. os_version                                                                                                     
        if (crashPortray.osVersion.isNotEmpty()                                                                             
            && !crashPortray.osVersion.contains(Build.VERSION.SDK_INT)                                                      
        ) {                                                                                                                 
            continue                                                                                                        
        }                                                                                                                   
                                                                                                                            
        //3. model                                                                                                          
        if (crashPortray.model.isNotEmpty()                                                                                 
            && crashPortray.model.firstOrNull { Build.MODEL.equals(it, true) } == null                                      
        ) {                                                                                                                 
            continue                                                                                                        
        }                                                                                                                   
                                                                                                                            
        val throwableName = throwable.javaClass.simpleName                                                                  
        val message = throwable.message ?: ""                                                                               
        //4. class_name                                                                                                     
        if (crashPortray.className.isNotEmpty()                                                                             
            && crashPortray.className != throwableName                                                                      
        ) {                                                                                                                 
            continue                                                                                                        
        }                                                                                                                   
                                                                                                                            
        //5. message                                                                                                        
        if (crashPortray.message.isNotEmpty() && !message.contains(crashPortray.message)                                    
        ) {                                                                                                                 
            continue                                                                                                        
        }                                                                                                                   
                                                                                                                            
        //6. stack                                                                                                          
        if (crashPortray.stack.isNotEmpty()) {                                                                              
            var match = false                                                                                               
            throwable.stackTrace.forEach { element ->                                                                       
                val str = element.toString()                                                                                
                if (crashPortray.stack.find { str.contains(it) } != null) {                                                 
                    match = true                                                                                            
                    return@forEach                                                                                          
                }                                                                                                           
            }                                                                                                               
            if (!match) {                                                                                                   
                continue                                                                                                    
            }                                                                                                               
        }                                                                                                                   
                                                                                                                            
        //7. 相应操作                                                                                                           
        if (crashPortray.clearCache == 1) {                                                                                 
            actionImpl.cleanCache(application)                                                                              
        }                                                                                                                   
        if (crashPortray.finishPage == 1) {                                                                                 
            actionImpl.finishCurrentPage()                                                                                  
        }                                                                                                                   
        if (crashPortray.toast.isNotEmpty()) {                                                                              
            actionImpl.showToast(application, crashPortray.toast)                                                           
        }                                                                                                                   
        return true                                                                                                         
    }                                                                                                                       
    return false                                                                                                            
}                                                                                                                           
  1. 实现保护,looper 兜底:
kotlin 复制代码
override fun uncaughtException(t: Thread, e: Throwable) {       
    if (CrashPortrayHelper.needBandage(e)) {                    
        bandage()                                               
        return                                                  
    }                                                           
                                                                
    //崩吧                                                        
    oldHandler?.uncaughtException(t, e)                         
}                                                               
                                                                
/**                                                             
 * 让当前线程恢复运行                                                    
 */                                                             
private fun bandage() {                                         
    while (true) {                                              
        try {                                                   
            if (Looper.myLooper() == null) {                    
                Looper.prepare()                                
            }                                                   
            Looper.loop()                                       
        } catch (e: Exception) {                                
            uncaughtException(Thread.currentThread(), e)        
            break                                               
        }                                                       
    }                                                           
}                                                               

如何线上容灾

其实思路很简单,问几个问题,答完就知道了。

  1. 崩溃兜底机制可以保护 app,已知我们用配置文件来描述崩溃画像,那配置文件能否远程下发?

  2. 配置文件远程下发后,app 拉下来后能否立即生效?

  3. 假如线上出了个崩溃,崩溃本身涉及代码流程不重要,但是会让 app 直接挂掉,能否线上修改配置文件,将这个崩溃包括进去进行保护,后续在下版本修复之?

崩溃画像实例

css 复制代码
[  {    "class_name": "",    "message": "No space left on device",    "stack": [],
    "app_version": [],
    "clear_cache": 1,
    "finish_page": 0,
    "toast": "",
    "os_version": [],
    "model": []
  },
  {
    "class_name": "BadTokenException",
    "message": "",
    "stack": [],
    "app_version": [],
    "clear_cache": 0,
    "finish_page": 0,
    "toast": "",
    "os_version": [],
    "model": []
  },
  {
    "class_name": "IllegalStateException",
    "message": "not running",
    "stack": [
      "Daemons"
    ],
    "app_version": [],
    "clear_cache": 0,
    "finish_page": 0,
    "toast": "",
    "os_version": [],
    "model": []
  },
  {
    "class_name": "",
    "message": "Activity client record must not be null to execute",
    "stack": [],
    "app_version": [],
    "clear_cache": 0,
    "finish_page": 0,
    "toast": "",
    "os_version": [],
    "model": []
  },
  {
    "class_name": "",
    "message": "The previous transaction has not been applied or aborted",
    "stack": [],
    "app_version": [],
    "clear_cache": 0,
    "finish_page": 0,
    "toast": "",
    "os_version": [],
    "model": []
  }
]

好了,拜拜。

相关推荐
fatsheep洋2 分钟前
文件上传 --- uploadlabs靶场
android
雨白9 小时前
Android 自定义 View:从绘制基础到实战仪表盘与饼图
android
jiunian_cn9 小时前
【Linux】线程
android·linux·运维·c语言·c++·后端
Frank_HarmonyOS18 小时前
Android MVVM(Model-View-ViewModel)架构
android·架构
新子y1 天前
【操作记录】我的 MNN Android LLM 编译学习笔记记录(一)
android·学习·mnn
lincats1 天前
一步一步学习使用FireMonkey动画(1) 使用动画组件为窗体添加动态效果
android·ide·delphi·livebindings·delphi 12.3·firemonkey
想想吴1 天前
Android.bp 基础
android·安卓·android.bp
bianshaopeng1 天前
Android studio gradle 下载不下来
ide·android studio
写点啥呢1 天前
Android为ijkplayer设置音频发音类型usage
android·音视频·usage·mediaplayer·jikplayer
coder_pig1 天前
🤡 公司Android老项目升级踩坑小记
android·flutter·gradle