Android app Java层异常捕获方案

news2024/11/21 0:25:22

背景: 在Android app运行中,有时一些无关紧要的异常出现时希望App 不崩溃,能继续让用户操作,可以有效提升用户体验和增加业务价值。

在这里插入图片描述

在这里插入图片描述
新流程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
哪些场景需要Catch

这里是引用

Crash Config配置信息:
在这里插入图片描述

支持从网络上获取Crash配置表,动态防护,避免crash。

使用: 在Application onCreate中调用:

CrashPortrayHelper.INSTANCE.init(this);

实现原理—源代码:

CrashPortray.kt

package com.mcd.library.crashProtect

import com.google.gson.annotations.SerializedName
import java.io.Serializable

data class CrashPortray(
    @SerializedName("class_name")
    val className: String = "",
    val message: String = "",
    val stack: List<String> = emptyList(),
    @SerializedName("app_version")
    val appVersion: List<String> = emptyList(),
    @SerializedName("os_version")
    val osVersion: List<Int> = emptyList(),
    val model: List<String> = emptyList(),
    val type: String = "all",
    @SerializedName("clear_cache")
    val clearCache: Int = 0,
    @SerializedName("finish_page")
    val finishPage: Int = 0,
    val toast: String = ""
) : Serializable {

    fun valid(): Boolean {
        return className.isNotEmpty() || message.isNotEmpty() || stack.isNotEmpty()
    }
}

CrashPortrayHelper.kt

package com.mcd.library.crashProtect

import android.app.Application
import android.content.Context
import android.os.Build
import com.mcd.library.AppConfigLib
import com.mcd.library.common.McdLifecycleCallback
import com.mcd.library.utils.CacheUtil
import com.mcd.library.utils.DialogUtil
import java.io.File
import java.lang.reflect.InvocationTargetException

object CrashPortrayHelper {
    private var crashPortrayConfig: List<CrashPortray>? = null
    private lateinit var application: Application
    private lateinit var actionImpl: IApp
    private const val crashProtectClosed: Boolean = false // 是否关闭该功能

    fun init(application: Application) {
        if (AppConfigLib.isDebugMode() || crashProtectClosed) { // debug模式下不进行初始化
            return
        }
        CrashPortrayHelper.application = application
        crashPortrayConfig = getCrashConfig()
        actionImpl = getAppImpl()
        CrashUncaughtExceptionHandler.init()
    }

    private fun getCrashConfig(): List<CrashPortray> {
        // 从网络获取crash配置
        val crashList = AppConfigLib.getCrashPortrays() ?: ArrayList()
        //添加本地默认配置
        crashList.apply {
            this.addAll(getSystemException())
            this.addAll(getRNException())
            this.addAll(getSDKException())
        }
        return crashList
    }

    // 三方sdk异常
    private fun getSDKException(): List<CrashPortray> {
        val crashList = mutableListOf<CrashPortray>()
        crashList.add(
            CrashPortray(
                className = "IllegalArgumentException",
                message = "[PaymentActivity] not attached to window manager"
            )
        ) // 支付页
        crashList.add(CrashPortray(className = "EOFException")) //lottie
        crashList.add(CrashPortray(className = "JsonEncodingException")) //lottie
        return crashList
    }

    // 系统异常
    private fun getSystemException(): List<CrashPortray> {
        val crashList = mutableListOf<CrashPortray>()
        crashList.add(CrashPortray(className = "BadTokenException"))
        crashList.add(CrashPortray(className = "AssertionError"))
        crashList.add(CrashPortray(className = "NoSuchMethodError"))
        crashList.add(CrashPortray(className = "NoClassDefFoundError"))
        crashList.add(CrashPortray(className = "CannotDeliverBroadcastException"))
        crashList.add(CrashPortray(className = "OutOfMemoryError"))
        crashList.add(CrashPortray(className = "DeadSystemRuntimeException"))
        crashList.add(CrashPortray(className = "DeadSystemException"))
        crashList.add(CrashPortray(className = "NullPointerException"))
        crashList.add(CrashPortray(className = "TimeoutException"))
        crashList.add(CrashPortray(className = "RemoteException"))
        crashList.add(CrashPortray(className = "SecurityException"))
        crashList.add(CrashPortray(className = "TransactionTooLargeException"))
        crashList.add(CrashPortray(className = "SQLiteFullException"))
        crashList.add(CrashPortray(className = "ConcurrentModificationException"))
        crashList.add(CrashPortray(className = "InvocationTargetException"))
        return crashList
    }

    // RN异常
    private fun getRNException(): List<CrashPortray> {
        val crashList = mutableListOf<CrashPortray>()
        crashList.add(CrashPortray(className = "TooManyRequestsException"))
        crashList.add(
            CrashPortray(
                className = "RuntimeException",
                message = "Attempting to call JS function on a bad application bundle"
            )
        )
        crashList.add(
            CrashPortray(
                className = "RuntimeException",
                message = "Illegal callback invocation from native module"
            )
        )
        crashList.add(
            CrashPortray(
                className = "CppException",
                message = "facebook::react::Recoverable"
            )
        )
        crashList.add(CrashPortray(className = "JavascriptException"))
        crashList.add(
            CrashPortray(
                className = "UnsupportedOperationException",
                message = "Tried to obtain display from a Context not associated with one"
            )
        )
        crashList.add(CrashPortray(className = "MissingWebViewPackageException"))
        return crashList
    }

    private fun getAppImpl(): IApp {
        return object : IApp {
            override fun showToast(context: Context, msg: String) {
                DialogUtil.showShortPromptToast(context, msg)
            }

            override fun cleanCache(context: Context) {
                CacheUtil.trimCache(context.applicationContext)
            }

            override fun finishCurrentPage() {
                McdLifecycleCallback.getInstance().finishActivityWithNumber(1)
            }

            override fun getVersionName(context: Context): String =
                AppConfigLib.getCurrentVersionName()

            override fun downloadFile(url: String): File? {
                return null
            }

            override fun readStringFromCache(key: String): String {
                return ""
            }

            override fun writeStringToCache(file: File, content: String) {
            }
        }
    }


    fun needProtect(throwable: Throwable): Boolean {
        val config: List<CrashPortray>? = crashPortrayConfig
        if (config.isNullOrEmpty()) {
            return false
        }
        kotlin.runCatching {
            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
                }
                var throwableName = throwable.javaClass.simpleName
                val message = throwable.message ?: ""
                if (throwable.cause is InvocationTargetException) { // 处理原始异常(华为等机型)
                    throwableName = (throwable.cause as InvocationTargetException).targetException.javaClass.simpleName ?: ""
                }
                //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
    }
}

CrashUncaughtExceptionHandler.kt

package com.mcd.library.crashProtect

import android.os.Looper
import com.mcd.appcatch.AppInfoOperateProvider
import com.mcd.appcatch.appEvent.AppEventName
import com.mcd.library.utils.JsonUtil

object CrashUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
    private var oldHandler: Thread.UncaughtExceptionHandler? = null

    fun init() {
        oldHandler = Thread.getDefaultUncaughtExceptionHandler()
        oldHandler?.let {
            Thread.setDefaultUncaughtExceptionHandler(this)
        }
    }

    override fun uncaughtException(t: Thread, e: Throwable) {
        if (CrashPortrayHelper.needProtect(e)) {
            report(e)
            bandage()
            return
        }
        //旧的处理方式
        oldHandler?.uncaughtException(t, e)
    }

    // crash 信息上报
    private fun report(e: Throwable) {
        kotlin.runCatching {
            AppInfoOperateProvider.getInstance().saveEventInfo(
                AppEventName.Crash.crash_protect_report,
                System.currentTimeMillis(), e.message + JsonUtil.encode(e.stackTrace.take(5))) // 取message+异常堆栈前5条
        }
    }

    /**
     * 让主线程恢复运行
     */
    private fun bandage() {
        try {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                return
            }
            Looper.loop()
        } catch (e: Exception) {
            uncaughtException(Thread.currentThread(), e)
        }
    }
}

IApp.kt

package com.mcd.library.crashProtect

import android.content.Context
import java.io.File

interface IApp {
    fun showToast(context: Context, msg: String)
    fun cleanCache(context: Context)
    fun finishCurrentPage()
    fun getVersionName(context: Context): String
    fun downloadFile(url: String): File?
    fun readStringFromCache(key : String): String
    fun writeStringToCache(file: File, content: String)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1863161.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL 5.7.42 主从复制环境搭建

MySQL 5.7.42 主从复制环境搭建 下载MySQL二进制包操作系统环境配置安装过程搭建从库 本次安装环境&#xff1a; OS版本&#xff1a;Red Hat Enterprise Linux Server release 6.8 (Santiago) MySQL版本&#xff1a;5.7.42 架构&#xff1a;同一台机器&#xff0c;多实例安装搭…

国标GB28181视频汇聚平台EasyCVR安防监控系统常见播放问题分析及解决方法

国标GB28181安防综合管理系统EasyCVR视频汇聚平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。平台支持多协议接入&#xff0c;包括&#xff1a;国标GB/T 28181协议、GA/T 1400协议、RTMP、RTSP/Onvif协议、海康Ehome、海康SDK、大华SDK、华为SDK、宇视…

【工具测评】ONLYOFFICE8.1版本桌面编辑器测评:好用!

随着远程工作的普及和数字化办公的发展&#xff0c;越来越多的人开始寻找功能强大、易于使用的办公软件。在这个背景下&#xff0c;ONLYOFFICE 8.1应运而生&#xff0c;成为许多用户的新选择。ONLYOFFICE 8.1是一款办公套件软件&#xff0c;提供文档处理、电子表格和幻灯片制作…

【node】深入探讨 class URL

【node】深入探讨 class URL &#x1f4cc; 浅说 fileURLToPath() 在vite.config.ts中有这么一段代码&#xff1a; import { fileURLToPath, URL } from node:url import { defineConfig } from vite export default defineConfig({resolve: {alias: {: fileURLToPath(new U…

python学习笔记四

1.自己平方本身 x2 x**4#xx**4 print(x) 2.把一个多位数拆分成单个数&#xff0c;方法一通过字符串下标获取对应元素&#xff0c;并对获取的元素使用eval函数把左右引号去掉&#xff0c;是字符串变为整型&#xff1b;方法二&#xff0c;通过对数进行取余和整除得到各个位的数 …

RK3568平台开发系列讲解(I2C篇)利用逻辑分析仪进行I2C总线的全面分析

🚀返回专栏总目录 文章目录 1. 基础协议1.1. 协议简介1.2. 物理信号1.3. 总线连接沉淀、分享、成长,让自己和他人都能有所收获!😄 1. 基础协议 1.1. 协议简介 IIC-BUS(Inter-IntegratedCircuit Bus)最早是由PHilip半导体(现在被NXP收购)于1982年开发。 主要是用来方…

安卓应用开发学习:获取导航卫星信息

一、引言 我昨天写了《安卓应用开发学习&#xff1a;获取经纬度及地理位置描述信息》日志&#xff0c;今天再接再厉&#xff0c;记录一下跟着《Android App 开发进阶与项目实战》一书&#xff0c;实现获取导航卫星信息&#xff0c;并在手机上显示的功能的情况。先上实现后的在…

go语言day2 配置

使用cmd 中的 go install &#xff1b; go build 命令出现 go cannot find main module 错误怎么解决&#xff1f; go学习-问题记录(开发环境)go: cannot find main module&#xff1b; see ‘go help modules‘_go: no flags specified (see go help mod edit)-CSDN博客 在本…

FPGA学习笔记(6)——硬件调试与网表添加探针

对信号进行分析&#xff0c;除了使用内置的ILA IP核&#xff0c;还可以在网表中添加探针。 本节采用之前配置的LED灯闪烁代码&#xff0c;对原始工程进行修改。 如果是新建工程&#xff0c;需要现将代码进行综合Synthesis&#xff0c;然后再进行接下来的操作。 1、点击Open S…

链表数组遍历输出的辨析(二者都含指针的情况下)----PTA期末复习题

输入输出三位学生的学号和信息 一开始我认为是指针&#xff0c;直接背了指针输出的方式&#xff1b;p;p!NULL;pp->next 这个是错误的 下面这个输出是正确的方式 分析怎么区分这两个 举个例子来 数组遍历&#xff1a; 链表遍历&#xff1a; 输出的结果&#xff1a; 如果将…

浏览器扩展V3开发系列之 chrome.cookies 的用法和案例

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 chrome.cookies API能够让我们在扩展程序中去操作浏览器的cookies。 在使用 chrome.cookies 要先声明…

【Redis】Zset有序集合常用命令以及使用场景

Redis 的有序集合&#xff08;Sorted Set&#xff0c;简称 Zset&#xff09;是一个非常强大的数据结构&#xff0c;它结合了集合&#xff08;Set&#xff09;的唯一性和列表&#xff08;List&#xff09;的有序性。每个元素都关联一个评分&#xff08;score&#xff09;&#x…

减少液氮罐内液氮损耗的方法

监测与管理液氮容器的密封性能 液氮容器的密封性能直接影响液氮的损耗情况。一个常见的损耗源是容器本身的密封不良或老化导致的泄漏。为了有效减少液氮损耗&#xff0c;首先应当定期检查液氮容器的密封性能。这可以通过简单的方法如肉眼检查外观&#xff0c;或者更精确的方法…

SEO与AI的结合:如何用ChatGPT生成符合搜索引擎优化的内容

在当今数字时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;已成为每个网站和内容创作者都必须掌握的一项技能。SEO的主要目标是通过优化内容&#xff0c;使其在搜索引擎结果页面&#xff08;SERP&#xff09;中排名更高&#xff0c;从而吸引更多的流量。然而&#xf…

嵌入式学习——硬件(ARM体系架构)——day51

1. S3C2440基础知识——一条指令四个字节 1.1 定义 S3C2440 是三星&#xff08;Samsung&#xff09;公司设计的一款基于 ARM920T 核心的微处理器&#xff0c;广泛应用于嵌入式系统中&#xff0c;属于三星的 S3C24xx 系列。 1.2 处理器核心 ARM920T&#xff1a;基于 ARM v5T …

[C#][opencvsharp]C#使用opencvsharp进行年龄和性别预测支持视频图片检测

使用 OpenCVSharp 来调用 age_net.caffemodel 和 gender_net.caffemodel 来进行性别和年龄预测涉及几个步骤。以下是一个简化的流程和示例文案&#xff1a; 1. 准备工作 确保你已经安装了 OpenCVSharp 和相关的依赖项。确保你有 age_net.prototxt、age_net.caffemodel、gende…

市面上很轻的 100kW 负载组

FX100S-C 负载组 EAK的 FX100S-C 负载组在轻质外壳中以 415Vac 50Hz 提供 100kW 的连续负载。数字仪表允许您测量功率、电压、电流和频率&#xff0c;同时还允许您在进行测试时记录数据。 EAK是市场上最轻的 100kW 负载组之一&#xff0c;它将使您能够访问其他负载组无法到达…

离线部署OpenIM

目录 1.提取相关安装包和镜像 2.安装docker和docker-compose 3.依次导入镜像 4.解压安装包 5.执行安装命令 6.PC Web 验证 7.开放端口 7.1IM 端口 7.2Chat 端口 7.3 PC Web 及管理后台前端资源端口 “如果您在解决类似问题时也遇到了困难&#xff0c;希望我的经验分享…

服务运营 | MS文章精选:线上点单,当真免排队?餐饮零售与医疗场景中的全渠道运营

编者按&#xff1a; 小A走进了一家奶茶店&#xff0c;准备向店员点单&#xff0c;但却在屏幕上看到还有98杯奶茶待制作&#xff08;因为线上订单突然暴增&#xff09;。因此&#xff0c;小A不满地嘟囔着离开了奶茶店。这个例子展示了线上渠道可能会对线下渠道造成一些负面影响…

并发编程-04synchronized原理

并发编程-04synchronized原理 一 synchronized基础 1.1 并发安全问题 在学习synchronized原理之前&#xff0c;我们先要了解synchronized是干嘛用的&#xff0c;什么场景下需要使用它&#xff0c;以及它的使用方式有哪些&#xff1f;接下来我们去根据一个业务场景去了解下sy…