一篇文章搞定《Android异常处理》

news2024/10/7 11:28:04

------《Android异常处理》

  • 异常种类(简述)
    • 编译时异常
    • 运行时异常
  • 运行时的异常和崩溃
    • 受检时的异常
      • 第一种做法:
      • 第二种做法:
    • 不受检时的异常(崩溃Crash)
      • 异常的传播
      • 崩溃的兜底
      • Looper 循环问题
      • 主流程抛出异常问题
  • 安全气囊的实现
    • 方案设计
    • 代码实现
  • 线上崩溃检测Bugly
    • 是什么
    • 注册
    • 使用步骤
    • 真实Bugly例子
  • 总结

异常种类(简述)

编译时异常

  1. 语法错误(Syntax Error):如少了分号、缺少括号、拼写错误等,编译器无法理解代码语法而发生错误。
  2. 类型不匹配错误(Type Mismatch Error):如把一个字符串变量赋值给整型变量、或是在使用函数时传入错误类型的参数等,导致编译器无法将代码转换成二进制文件。
  3. 未声明的变量或函数(Error of undeclared variable or function):如果使用未声明的变量或函数,编译器无法理解代码含义而抛出异常。

运行时异常

  1. 空指针异常(NullPointerException):当程序试图访问空对象时,会触发空指针异常。
  2. 数组越界异常(ArrayIndexOutOfBoundsException):当程序试图访问不存在的数组元素时,会触发数组越界异常。
  3. 类型转换异常(ClassCastException):当程序试图将一个对象强制转换为另一种不兼容类型时,会触发类型转换异常。
  4. 算术异常(ArithmeticException):当程序试图执行除法操作时除数为0时,会触发算术异常。

运行时的异常和崩溃

其实我们经常关注和处理的就是我们的运行时的异常,因为它可能会导致应用程序崩溃或者运行不正常
我们常见的运行时异常可以分为:

受检时的异常

顾名思义就是可以检测到的异常,在程序中会对我们进行提示。
在这里插入图片描述

第一种做法:

那么这时候我们大多数的做法是用try-catch语句块来捕获异常。如果try语句块中的代码发生异常,那么会立即跳转到catch语句块,并执行其中的代码。catch语句块中可以包含多个catch子句,每个子句用于捕获不同类型的异常。

具体的语法如下:
try{
   //可能发生错误的程式码
}catch(具体错误 e){
   //具体错误有就写,没有就不写,有多个,就写多个catch
   e.printStackTrace(); //在命令行打印错误信息
}catch(Exception e){
   log(e.toString());
}finally{
   //无论是否捕捉到错误,一定会执行的代码
}

注意:这里纠正一下finally
问题:finally一定会执行吗?
答:肯定不是的
问题:那什么情况下不会执行
在try代码或者catch代码块中加入System.exit(0);来杀死App进程那么就不会执行了

第二种做法:

使用throw抛出一个异常,并获取这个异常的引用,这个异常会被抛到外部的环境,由外部环境进行处理。但是你还是要去外部环境进行异常处理,比如try-catch。否则最终传递给系统捕获处理,那么就会引发崩溃。

不受检时的异常(崩溃Crash)

当然上面的第二种做法传递给系统引发的崩溃,也可以通过下面的处理进行来全局的捕获
除了throw到系统引发的崩溃,上面列举的运行时的空指针,数组越界,类型转换等等异常都会引发崩溃。我们也叫它Crash。
我们都知道,当 Andoird 程序发生未捕获的异常的时候,程序会直接 Crash 退出
而所谓安全气囊,是指在 Crash 发生时,可以捕获异常,触发兜底逻辑,在程序退出前做最后的抢救
接下来我们来看一下怎么实现一个安全气囊,以在 Crash 发生时做最后的抢救

异常的传播

我们首先要了解一下当异常发生时是怎么传播的
在这里插入图片描述
其实也很简单,主要分为以下几步

  1. 当抛出异常时,通过Thread.dispatchUncaughtException进行分发
  2. 依次由Thread,ThreadGroup,Thread.getDefaultUncaughtExceptionHandler处理
  3. 在默认情况下,KillApplicationHandler会被设置defaultUncaughtExceptionHandler
  4. KillApplicationHandler中会调用Process.killProcess退出应用
    这就是异常发生时的传播路径,可以看出,如果我们通过Thread.setDefaultUncaughtExceptionHandler设置自定义处理器,就可以捕获异常做一些兜底操作了,其实 bugly 这些库也是这么做的

崩溃的兜底

如果我们设置了自定义处理器,在里面只做一些打印日志的操作,而不是退出应用,是不是就可以让 app 永不崩溃了呢?
答案当然是否定的,主要有以下两个问题

Looper 循环问题

在这里插入图片描述
我们知道,App 的运行在很大程度上依赖于 Handler 消息机制,Handler 不断的往 MessageQueue 中发送 Message,而Looper则死循环的不断从MessageQueue中取出Message并消费,整个 app 才能运行起来
而当异常发生时,Looper.loop 循环被退出了,事件也就不会被消费了,因此虽然 app 不会直接退出,但也会因为无响应发生 ANR
因此,当崩溃发生在主线程时,我们需要恢复一下Looper.loop

主流程抛出异常问题

当我们在主淤积抛出异常时,比如在onCreate方法中,虽然我们捕获住了异常,但程序的执行也被中断了,界面的绘制可能无法完成,点击事件的设置也没有生效
这就导致了 app 虽然没有退出,但用户却无法操作的问题,这种情况似乎还不如直接 Crash 了呢
因此我们的安全气囊应该支持配置,只处理那些非主流程的操作,比如点击按钮触发的崩溃,或者一些打点等对用户无感知操作造成的崩溃

安全气囊的实现

方案设计

为了解决上面提到的两个问题,我们的方案如下
在这里插入图片描述
主要分为以下几步:

  1. 注册自定义DefaultUncaughtExceptionHandler
  2. 当异常发生时捕获异常
  3. 匹配异常堆栈是否符合配置,如果符合则捕获,否则交给默认处理器处理
  4. 判断异常发生时是否是主线程,如果是则重启Looper

代码实现

package com.example.meng.utils

import android.os.Looper
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.Thread.UncaughtExceptionHandler

/**
 * Author: mql
 * Date: 2023/5/12
 * Describe : 全局的崩溃处理工具类
 */

class CrashHandler : UncaughtExceptionHandler{
    private var mDefaultCrashHandler: UncaughtExceptionHandler

    init {
        Thread.setDefaultUncaughtExceptionHandler(this)
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler() as UncaughtExceptionHandler
    }

    /**
     * 双重校验锁
     */
    companion object {
        @Volatile
        private var instance: CrashHandler? = null
        fun getInstance() =
            instance ?: synchronized(this) {
                instance ?: CrashHandler().also { instance = it }
            }
    }

    /**
     * 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用#uncaughtException方法
     * thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息
     */
    override fun uncaughtException(thread: Thread, throwable: Throwable) {
        //打印我们的异常信息
        val stackTrace = StringWriter()
        throwable.printStackTrace(PrintWriter(stackTrace))

        if (isMainThread()) {
            //1、你可以选择重启应用
            //重启方法很多自己实现吧

            //2、重启主线程Looper将崩溃跳过继续运行App
            while (true) {
                try {
                    Looper.loop()
                } catch (e: Throwable) {
                    //处理异常的信息
                    handleException(thread, throwable)
                }
            }

            //3、结束App进程
            //关闭所有栈中的Activity:removeAllActivities()
            //Process.killProcess(Process.myPid())
            //System.exit(0)

        } else {
            //子线程的崩溃而已,直接给系统处理推出子线程
            mDefaultCrashHandler.uncaughtException(thread, throwable)
        }
    }

    private fun isMainThread(): Boolean {
        return Thread.currentThread() === Looper.getMainLooper().thread
    }

    private fun handleException(thread: Thread, throwable: Throwable) {
        //可以导出异常信息到SD卡中
        //dumpExceptionToSDCard(ex)

        //也可以将异常上传到服务器上
        //uploadExceptionToServer()
    }

}

线上崩溃检测Bugly

开发者手册

是什么

Bugly简单来说就是一个第三方统计平台,可以捕捉异常,运营统计和应用升级等功能。

注册

注册平台信息后创建自己产品
在这里插入图片描述
创建完得到APPID等一系列值
在这里插入图片描述

使用步骤

我们这里用最简单的,自动集成,在Module的build.gradle文件中添加依赖和属性配置

//bugly
implementation 'com.tencent.bugly:crashreport:latest.release' 
//其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9

implementation 'com.tencent.bugly:nativecrashreport:latest.release' 
//其中latest.release指代最新Bugly NDK版本号,也可以指定明确的版本号,例如3.0

自动集成时会自动包含Bugly SO库,需要在Module的build.gradle文件中使用NDK的“abiFilter”配置,设置支持的SO库架构。

android {
    defaultConfig {
        ndk {
            // 设置支持的SO库架构
            abiFilters 'armeabi' //, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }
    }
}

如果在添加“abiFilter”之后Android Studio出现以下提示:

NDK integration is deprecated in the current plugin. Consider trying
the new experimental plugin。

则在项目根目录的gradle.properties文件中添加:

android.useDeprecatedNdk=true

在AndroidManifest.xml中添加权限

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />

注意:如果您的App需要上传到google play store,您需要将READ_PHONE_STATE权限屏蔽掉或者移除,否则可能会被下架。
避免混淆Bugly,在Proguard混淆文件中增加以下配置

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}

MultiDex注意事项
如果使用了MultiDex,建议通过Gradle的“multiDexKeepFile”配置等方式把Bugly的类放到主Dex,另外建议在Application类的"attachBaseContext"方法中主动加载非主dex:

public class MyApplication extends SomeOtherApplication {
  @Override
  protected void attachBaseContext(Context base) {
     super.attachBaseContext(context);
     Multidex.install(this);
  }
}

初始化
获取APP ID并将以下代码复制到项目Application类onCreate()中,Bugly会为自动检测环境并完成配置:

CrashReport.initCrashReport(getApplicationContext(), "注册时申请的APPID", false); 

为了保证运营数据的准确性,建议不要在异步线程初始化Bugly。
第三个参数为SDK调试模式开关,调试模式的行为特性如下:
输出详细的Bugly SDK的Log;
每一条Crash都会被立即上报;
自定义日志将会在Logcat中输出。
建议在测试阶段建议设置成true,发布时设置为false。

真实Bugly例子

打开我们的异常上报,点击我们的崩溃分析。就可以看到我们相关的崩溃日志了。划线了是因为我解决了改变了他的状态。
在这里插入图片描述
点进去我们可以详细的去分析这个问题。并记录问题的状态。
在这里插入图片描述
在这里插入图片描述
可以看到还是很详细的。自己去看看摸索摸索吧。

总结

自己动手吧

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

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

相关文章

计算机组成原理---第六章总线系统 习题详解版

&#xff08;一&#xff09;课内习题 &#xff08;二&#xff09;课后习题 1.比较单总线、多总线结构的性能特点。 答&#xff1a; &#xff08;1&#xff09; 单总线结构:它是用单一的系统总线连接整个计算 机系统的各大功能部件,各大部件之间的所有的信息传送都通过这组总线…

【企业信息化】第6集 免费开源ERP: Odoo 16 MRP + 维护+ PLM +质量全面生产制造管理

文章目录 一、MRP 物料需求计划1.一款软件&#xff0c;满足您的所有需要2.工作中心控制面板3.优化您的库存等级4.条形码&#xff0c;即开即用5.出色报告关键绩效指标6.与其他Odoo应用程序完全集成 二、PLM 产品生命周期管理1.管理工程变更2.集成文件管理3.智能版本管理4.与其他…

还在为项目初始化、依赖管理问题困扰?Dubbo Initializer 来了!

作者&#xff1a;Dubbo 社区 通过这篇文章&#xff0c;你将学习如何在 1 分钟内用 Dubbo Initializer 模板快速创建 Dubbo Spring Boot 项目&#xff0c;帮你解决项目初始化问题。 什么是 Dubbo Initializer&#xff1f; Dubbo Initializer 是一款帮助开发者快速生成 Dubbo …

【0基础也能学会】JMeter:如何开始简单的WEB压力测试?

背景 最近工作上被安排针对Web网站进行性能压测&#xff0c;以评估特定的硬件配置下Web网站可支持的并发用户数。考虑到JMeter是流行的Web性能压测工具&#xff0c;因此趁着这次机会上网查阅了很多关于JMeter的资料&#xff0c;也自己动手进行软件的配置和调测&#xff0c;从最…

前瞻洞察|借助机器学习,揪出利用DNS隐蔽隧道作恶黑手

黑客会利用DNS协议进行违法犯罪活动&#xff0c;那DNS协议到底是什么&#xff1f;它有何作用&#xff1f;为什么会被选作进行作恶的手段&#xff1f;会造成什么危害&#xff1f;怎么检测及研究现状如何&#xff1f;一连串疑问接踵而至。本篇文章中&#xff0c;我们会为大家一一…

【Java多线程编程】Thread类

Thread类是什么&#xff1f; Thread 类是 Java 提供的一个标准库&#xff0c;我们可以通过 Thread 类进行多线程编程。因此&#xff0c;今天我给大家讲解的是如何使用 Thread 类进行线程编程。 详细讲解 Thread 类中的&#xff1a;lambda 表达式、start 方法&#xff08;启动线…

WiFi(Wireless Fidelity)基础(七)

目录 一、基本介绍&#xff08;Introduction&#xff09; 二、进化发展&#xff08;Evolution&#xff09; 三、PHY帧&#xff08;&#xff08;PHY Frame &#xff09; 四、MAC帧&#xff08;MAC Frame &#xff09; 五、协议&#xff08;Protocol&#xff09; 六、安全&#x…

Cloud Kernel SIG月度动态:发布ANCK 5.10、4.19新版本,ABS新增仓库构建功能

Cloud Kernel SIG&#xff08;Special Interest Group&#xff09;&#xff1a;支撑龙蜥内核版本的研发、发布和服务&#xff0c;提供生产可用的高性价比内核产品。 01 SIG 整体进展 发布 ANCK 5.10-014 版本。 发布 ANCK 4.19-027.2 版本。 ABS 平台新增 OOT 仓库临时构建功…

如何远程控制电脑?3个方法轻松搞定!

案例&#xff1a;如何远程控制电脑&#xff1f; 【我不想时时刻刻都带着自己的电脑&#xff0c;听朋友说可以远程电脑。有没有大神分享一下具体的操作方法&#xff1f;感谢&#xff01;】 随着科技的不断进步&#xff0c;远程控制电脑已经不再是一件难以实现的事情。如今&…

09.python可视化-Seanorn绘制类别关系图boxplot() boxenplot() violinplot()

分类散点图 分类分布图 1). 箱线图 : boxplot() 2).增强箱图boxenplot() 3).小提琴图 :violinplot() 分类统计图 2. 分类分布图 1). 箱线图 应用场景&#xff1a;主要用来显示与类别相关的数据分布。 seaborn.boxplot(xNone, yNone, hueNone, dataNone, orderNone, hue_orde…

GoView 是一个Vue3搭建的低代码数据可视化开发平台

一、总览 开源、精美、便捷的「数据可视化」低代码开发平台 二、整体介绍 框架&#xff1a;基于 Vue3 框架编写&#xff0c;使用 hooks 写法抽离部分逻辑&#xff0c;使代码结构更加清晰&#xff1b; 类型&#xff1a;使用 TypeScript 进行类型约束&#xff0c;减少未知错误…

WiFi(Wireless Fidelity)基础(九)

目录 一、基本介绍&#xff08;Introduction&#xff09; 二、进化发展&#xff08;Evolution&#xff09; 三、PHY帧&#xff08;&#xff08;PHY Frame &#xff09; 四、MAC帧&#xff08;MAC Frame &#xff09; 五、协议&#xff08;Protocol&#xff09; 六、安全&#x…

MySql -- 事务

目录 1.概念 2.事务的运用场景 3.事务的四大特点 4.执行事务带来的问题 4.1 脏读 4.2 不可重复度 4.3 幻读 5. MySQL中事务的隔离级别 1.概念 事务就是把若干个独立操作打包成一个整体而诞生的一种功能. 2.事务的运用场景 比如&#xff1a;A——>B 转账500 A的余额-500…

【Qt编程之Widgets模块】-007:QStandardPaths类使用方法

1 头文件&#xff1a; #include <QStandardPaths>2 详细说明 QStandardPaths类提供用于访问标准路径的方法&#xff0c;该类包含用于查询本地文件系统上的标准位置的函数&#xff0c;用于常见任务&#xff0c;如特定于用户的目录或系统范围的配置目录。 所谓系统标准路…

[pgrx开发postgresql数据库扩展]6.返回序列的函数编写(1)单值序列

上篇文章是中规中矩的标准计算函数&#xff0c;就算不用pgrx&#xff0c;也是可以正常理解的&#xff0c;所以基本上没有什么对于pgrx框架有关系的东西&#xff08;唯一有关系的东西&#xff0c;应该就是Rust的时间类型与pgrx的时间类型的计算了&#xff09;。 这篇文章会讲一…

Java面试(1)Java概述

文章目录 Java 概述1.什么是Java2. JDK1.5 之后的三大版本3. Jdk和Jre和JVM的区别4. 什么是跨平台性&#xff1f;原理是什么5. Java 语言有哪些特点?6. 什么是字节码&#xff1f;采用字节码的最大好处是什么7. 为什么不全部使用 AOT&#xff08;since JDK9&#xff09; 呢&…

马赛克处理

去取马赛克的网址&#xff1a; Redact • Photo - Free And Private Image Redaction In The Browser https://redact.photo/ REDACT.PHOTO &#xff08;照片马赛克处理在线工具&#xff09;简介 REDACT.PHOTO是一个照片马赛克处理在线工具&#xff0c;能够帮助我们非常方便…

2023自动化测试的10个最佳实践(建议收藏)

虽然大家都知道坚果是非常健康和有营养的&#xff0c;但是&#xff0c;当你尝试吃它的时候&#xff0c;我猜测过程都不会很顺利。现实就是那么相似&#xff0c;我们都知道测试自动化对软件开发有好处&#xff08;就像坚果对我们的身体一样&#xff01;&#xff09;&#xff0c;…

arcgis插件-带属性TXT转SHP数据(支持独立图层、追加到图层)

20230512记录更新 arcgis插件-带属性TXT转SHP数据&#xff08;支持独立图层、追加到图层&#xff09; 这个版本省略掉新建面图层&#xff0c;再在界面进行选择图层的操作。 界面简化到只需要一步操作&#xff0c;选择&#xff08;或者复制&#xff09;TXT文件所在路径&#x…

机器学习(二)决策树原理剖析及python实现

本篇介绍第二个机器学习算法&#xff1a;决策树算法&#xff0c;我们经常使用决策树处理分类问题&#xff0c;近来的调查表明决策树也是最经常使用的数据挖掘算法。 图1所示的流程图就是一个决策树&#xff0c;长方形代表判断模块&#xff08;decision block&#xff09;&…