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": []
  }
]

好了,拜拜。

相关推荐
Cao_Shixin攻城狮2 小时前
Flutter运行Android项目时显示java版本不兼容(Unsupported class file major version 65)的处理
android·java·flutter
呼啦啦呼啦啦啦啦啦啦5 小时前
利用pdfjs实现的pdf预览简单demo(包含翻页功能)
android·javascript·pdf
idjl7 小时前
Mysql测试题
android·adb
游戏开发爱好者89 小时前
iOS App 电池消耗管理与优化 提升用户体验的完整指南
android·ios·小程序·https·uni-app·iphone·webview
人生游戏牛马NPC1号10 小时前
学习 Flutter (四):玩安卓项目实战 - 中
android·学习·flutter
星辰也为你祝福h11 小时前
Android原生Dialog
android
梁同学与Android12 小时前
Android ---【CPU优化】需要优化的原因及优化的地方
android
Misha韩13 小时前
React Native 基础tabBar和自定义tabBar - bottom-tabs
android·react native
iHero13 小时前
【Nextcloud】在 Ubuntu 22.04.3 LTS 上的 Nextcloud Hub 10 (31.0.2) 后台任务cron 的优化
android·linux·ubuntu·nextcloud
imknown14 小时前
将一个 现有 iOS Xcode 项目, 快速改造为 可以用 Android Studio 运行和调试 的项目
android studio