Android 面试题 异常捕获 四

🔥 为什么要捕获奔溃 🔥

因为在开发或者测试阶段不能做到100%的问题解决,因为 app 上线之后会有你想不到的各种各样的使用的场景,而发生问题时用户只能描述一下怎么怎么怎么就出现了问题。也许反馈到开发这边可以100%复现那就可以得到解决,但是也有可能在本地复现不了,只有在用户的手机上可以出现,这可能和用户使用的场景(温度太高导致CPU限速,温度太低等),手机的内存,CPU,老年机等等都有关系。

在 android 里面,奔溃可以分为二大类,一个为 java 奔溃,一个为 native 奔溃。对于这二种奔溃需要用不同的方式去捕获。

一个合格的异常捕获组件也要能达到以下目的:

A : 支持在crash时进行更多扩展操作, 例如 :

        1、打印logcat和应用日志

        2、上报crash次数

        3、对不同的crash做不同的恢复措施

B : 可以针对业务不断改进和适应

🔥 Java异常分类 🔥 

可查的异常(checked exceptions)

编译器要求必须处置的异常(使用 try…catch…finally 或者 throws )。在方法中要么用try-catch语句捕获它并处理,要么用 throws 子句声明抛出它,否则编译不会通过。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。

不可查的异常(unchecked exceptions)

包括运行时异常(RuntimeException与其子类)和错误(Error)。在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。

🔥 收集Java层Crash 🔥

在 Applicaiton中进行初始化崩溃收集器

 

public class MyApp extends Application {@Overridepublic void onCreate() {super.onCreate();//初始化崩溃收集器CollectCrashUtils.initColleteCrash();}
}

收集java层崩溃和native层崩溃 

public class CollectCrashUtils {public static void initColleteCrash() {//初始化Handler,收集java层崩溃MyJavaCrashHandler handler = new MyJavaCrashHandler();Thread.setDefaultUncaughtExceptionHandler(handler);//收集native层崩溃File file = new File("sdcard/Crashlog");if (!file.exists()) {file.mkdirs();}NativeBreakpad.init(file.getAbsolutePath());}
}

native层的崩溃收集可以使用编译好的breakpad.so 

java层崩溃实现Thread.UncaughtExceptionHandler接口进行收集

 

public class MyJavaCrashHandler implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {Log.e("程序出现异常了", "Thread = " + t.getName() + "\nThrowable = " + e.getMessage());String stackTraceInfo = getStackTraceInfo(e);Log.e("stackTraceInfo", stackTraceInfo);saveThrowableMessage(stackTraceInfo);}/*** 获取错误的信息** @param throwable* @return*/private String getStackTraceInfo(final Throwable throwable) {PrintWriter pw = null;Writer writer = new StringWriter();try {pw = new PrintWriter(writer);throwable.printStackTrace(pw);} catch (Exception e) {return "";} finally {if (pw != null) {pw.close();}}return writer.toString();}private String logFilePath = "sdcard/Crashlog";private void saveThrowableMessage(String errorMessage) {if (TextUtils.isEmpty(errorMessage)) {return;}File file = new File(logFilePath);if (!file.exists()) {boolean mkdirs = file.mkdirs();if (mkdirs) {writeStringToFile(errorMessage, file);}} else {writeStringToFile(errorMessage, file);}}private void writeStringToFile(final String errorMessage, final File file) {new Thread(new Runnable() {@Overridepublic void run() {FileOutputStream outputStream = null;try {ByteArrayInputStream inputStream = new ByteArrayInputStream(errorMessage.getBytes());outputStream = new FileOutputStream(new File(file, System.currentTimeMillis() + ".txt"));int len = 0;byte[] bytes = new byte[1024];while ((len = inputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, len);}outputStream.flush();Log.e("程序出异常了", "写入本地文件成功:" + file.getAbsolutePath());} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {if (outputStream != null) {try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}}}}).start();}}

 🔥 UncaughtExceptionHandler 异常🔥

Thread中存在两个UncaughtExceptionHandler:

  1. 一个是静态的defaultUncaughtExceptionHandler:来自所有线程中的Exception在抛出并且未捕获的情况下,都会从此路过。进程fork的时候设置的就是这个静态的defaultUncaughtExceptionHandler,管辖范围为整个进程。
  2. 另一个是非静态uncaughtExceptionHandler:为单个线程设置一个属于线程自己的uncaughtExceptionHandler,辖范围比较小。

Thread类的异常处理变量声明 :

//成员变量,线程独有的
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
//静态变量,用于所有线程
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

当一个线程由于未捕获异常即将终止时,Java虚拟机将使用Thread的getuncaughtexceptionhandler()方法查询线程的uncaughtException处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。

一个线程如果没有设置uncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获异常。线程组ThreadGroup实现了UncaughtExceptionHandler,所以可以用来处理未捕获异常。

ThreadGroup实现的uncaughtException如下

public void uncaughtException(Thread t, Throwable e) {if (parent != null) {parent.uncaughtException(t, e);} else {Thread.UncaughtExceptionHandler ueh =Thread.getDefaultUncaughtExceptionHandler();if (ueh != null) {ueh.uncaughtException(t, e);} else if (!(e instanceof ThreadDeath)) {System.err.print("Exception in thread ""+ t.getName() + "" ");e.printStackTrace(System.err);}}}

线程组处理未捕获异常的逻辑是:

  1. 首先将异常消息通知给父线程组处理。
  2. 否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常。
  3. 如果没有默认的异常处理器则将错误信息输出到System.err。

🔥 异常(Crash)处理和捕获 🔥

 在Android中,运行时异常(属于不可查的异常),如果没有在try-catch语句捕获并处理,就会产生Crash,导致程序崩溃。

Crash是App稳定性的一个重要指标,大量的Crash是非常差的用户体验,会导致用户的流失。Crash发生之后,我们应该及时处理并解决,然后发版对其进行修复。

那么,问题来了,我们想要解决Crash,但是Crash发生在用户手机上,我们如何能拿到我们想要的错误信息呢?

Android中添加全局的Crash监控实战

在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash的发生。

1、创建一个自定义UncaughtExceptionHandler类

public class CrashHandler implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread thread, Throwable ex) {//回调函数,处理异常//在这里将崩溃日志读取出来,然后保存到SD卡,或者直接上传到日志服务器//如果我们也想继续调用系统的默认处理,可以先把系统UncaughtExceptionHandler存下来,然后在这里调用。}
}

2. 设置全局监控 

CrashHandler crashHandler = new CrashHandler();
Thread.setDefaultUncaughtExceptionHandler(crashHandler);

完成以上2个步骤,我们就可以实现全局的Crash监控了。这里所说的全局,是指针对整个进程生效。

🔥 Android系统中Crash的处理、分发逻辑 🔥

上面我们了解了怎么在Android中捕获Crash,实现Crash的监控、上报。那么,在Android中,系统是如何处理、分发Crash的呢?

异常处理的注册

App启动时,会通过zygote进程fork一个进程,然后创建VM虚拟机,然后会调用到zygoteInit进行初始化工作。

zygoteInit方法

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java的zygoteInit方法:

public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,ClassLoader classLoader) {……RuntimeInit.commonInit();……}

zygoteInit调用了RuntimeInit.commonInit()方法。

 RuntimeInit.commonInit()方法

protected static final void commonInit() {if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");/** set handlers; these apply to all threads in the VM. Apps can replace* the default handler, but not the pre handler.*/LoggingHandler loggingHandler = new LoggingHandler();RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));}

 逻辑解析:

  1. LoggingHandler用于处理打印日志,我们不做详细分析,感兴趣的可以自己看下。
  2. Thread.setDefaultUncaughtExceptionHandler用于注册系统默认异常处理的逻辑。

这里的RuntimeHooks.setUncaughtExceptionPreHandler方法,其实是调用了Thread的setUncaughtExceptionPreHandler方法。

Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用。

我们先来看异常的分发逻辑,后面再分析KillApplicationHandler中对异常的处理逻辑。

异常的分发

Thread的dispatchUncaughtException负责处理异常的分发逻辑。

Thread的dispatchUncaughtException

public final void dispatchUncaughtException(Throwable e) {// BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.Thread.UncaughtExceptionHandler initialUeh =Thread.getUncaughtExceptionPreHandler();if (initialUeh != null) {try {initialUeh.uncaughtException(this, e);} catch (RuntimeException | Error ignored) {// Throwables thrown by the initial handler are ignored}}// END Android-added: uncaughtExceptionPreHandler for use by platform.getUncaughtExceptionHandler().uncaughtException(this, e);}

这里首先处理setUncaughtExceptionPreHandler注册的异常处理方法,然后在调用通过setDefaultUncaughtExceptionHandler或setUncaughtExceptionHandler方法注册的异常处理方法。

Thread的getUncaughtExceptionHandler()方法

public UncaughtExceptionHandler getUncaughtExceptionHandler() {return uncaughtExceptionHandler != null ?uncaughtExceptionHandler : group;}
逻辑解析:
  1. 当uncaughtExceptionHandler不为空时,返回uncaughtExceptionHandler。uncaughtExceptionHandler是通过setUncaughtExceptionHandler方法注册异常处理Handler。
  2. 否则,返回group。
  3. 这里的group,其实就是当前线程所在的线程组。并且线程组ThreadGroup同样实现了UncaughtExceptionHandler接口。

ThreadGroup的uncaughtException

public void uncaughtException(Thread t, Throwable e) {if (parent != null) {parent.uncaughtException(t, e);} else {Thread.UncaughtExceptionHandler ueh =Thread.getDefaultUncaughtExceptionHandler();if (ueh != null) {ueh.uncaughtException(t, e);} else if (!(e instanceof ThreadDeath)) {System.err.print("Exception in thread ""+ t.getName() + "" ");e.printStackTrace(System.err);}}}
逻辑解析:

线程组处理未捕获异常的逻辑是:

  1. 首先将异常消息通知给父线程组处理。
  2. 否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常。
  3. 如果没有默认的异常处理器则将错误信息输出到System.err。

🔥 异常的处理 🔥 

系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,我们来看代码

KillApplicationHandler类的uncaughtException方法

        public void uncaughtException(Thread t, Throwable e) {try {//确保LoggingHandler的执行ensureLogging(t, e);// Don't re-enter -- avoid infinite loops if crash-reporting crashes.if (mCrashing) return;mCrashing = true;// Try to end profiling. If a profiler is running at this point, and we kill the// process (below), the in-memory buffer will be lost. So try to stop, which will// flush the buffer. (This makes method trace profiling useful to debug crashes.)if (ActivityThread.currentActivityThread() != null) {ActivityThread.currentActivityThread().stopProfiling();}//调用AMS,展示弹出等逻辑// Bring up crash dialog, wait for it to be dismissedActivityManager.getService().handleApplicationCrash(mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));} catch (Throwable t2) {if (t2 instanceof DeadObjectException) {// System process is dead; ignore} else {try {Clog_e(TAG, "Error reporting crash", t2);} catch (Throwable t3) {// Even Clog_e() fails!  Oh well.}}} finally {//杀死进程和退出虚拟机// Try everything to make sure this process goes away.Process.killProcess(Process.myPid());System.exit(10);}}
逻辑解析:
  1. 调用ensureLogging(t, e)确保LoggingHandler的执行(有去重逻辑,不用担心重复执行)。
  2. 然后调用了ActivityManager.getService().handleApplicationCrash方法来进行处理。
  3. 最后调用Process.killProcess(Process.myPid())来杀死进程,并且退出VM。

 AMS的handleApplicationCrash方法

我们继续来看ActivityManagerService的handleApplicationCrash方法:
位置:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

  public void handleApplicationCrash(IBinder app,ApplicationErrorReport.ParcelableCrashInfo crashInfo) {ProcessRecord r = findAppProcess(app, "Crash");final String processName = app == null ? "system_server": (r == null ? "unknown" : r.processName);handleApplicationCrashInner("crash", r, processName, crashInfo);}

这里通过Application的binder,取得进程的ProcessRecord对象,然后调用handleApplicationCrashInner方法。

AMS的handleApplicationCrashInner方法 

 final AppErrors mAppErrors;void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,ApplicationErrorReport.CrashInfo crashInfo) {/*处理一些错误日志相关的逻辑*///调用mAppErrors.crashApplication(r, crashInfo);}

这里处理一些错误日志相关的逻辑,然后调用AppErrors的crashApplication方法。

 AppErrors的crashApplication方法

/frameworks/base/services/core/java/com/android/server/am/AppErrors.java

 void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {final int callingPid = Binder.getCallingPid();final int callingUid = Binder.getCallingUid();final long origId = Binder.clearCallingIdentity();try {crashApplicationInner(r, crashInfo, callingPid, callingUid);} finally {Binder.restoreCallingIdentity(origId);}}

crashApplication调用crashApplicationInner方法,处理系统的crash弹框等逻辑 .

🔥 Crash优化建议 🔥 

Crash是App性能的一个非常重要的指标,我们要尽可能的减少Crash,增加App的稳定性,以下是几点实践经验:

  1. 要有可靠的Crash日志收集方式:可以自己实现,也可以集成第三方SDK来采集分析。
  2. 当一个Crash发生了,我们不但需要针性的解决这一个Crash,而且要考虑这一类Crash怎么去解决和预防,只有这样才能使得该类Crash真正的解决,而不是反复出现。
  3. 不能随意的使用try-catch,这样只会隐蔽真正的问题,要从根本上了解Crash的原因,根据原因去解决。
  4. 增加代码检测,预防常规可检测的代码问题的产生,预防胜于治理。

🔥  总结归纳 🔥 

  1. Java异常可分为:可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
  2. Java中,可以通过设置Thread类的uncaughtExceptionHandler属性或静态属性defaultUncaughtExceptionHandler来设置不可查异常的回调处理。
  3. 在Android中,运行时异常(属于不可查的异常),如果没有在try-catch语句捕获并处理,就会产生Crash,导致程序崩溃。
  4. 在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash的发生。
  5. 通过Thread的静态方法setDefaultUncaughtExceptionHandler方法,可以注册全局的默认Crash监控。通过Thread的setUncaughtExceptionHandler方法来注册某个线程的异常监控。
  6. setDefaultUncaughtExceptionHandler方法和setUncaughtExceptionHandler方法有注册顺序的问题,多次注册后,只有最后一次生效。
  7. Android系统中,默认的Crash处理Handler,是在进程创建时,通过RuntimeInit.commonInit()方法进行注册的。
  8. Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用。
  9. Thread的dispatchUncaughtException负责处理异常的分发逻辑。
  10. Android中,异常分发顺序为:
    • 首先处理setUncaughtExceptionPreHandler注册的异常处理方法;
    • 然后处理线程私有的(uncaughtExceptionHandler)Handler异常处理方法;
    • 如果私有Handler不存在,则处理ThreadGroup的Handler异常处理方法;
    • ThreadGroup中,优先调用父线程组的处理逻辑,否则,调用通过setUncaughtExceptionHandler方法注册异常处理Handler。
  11. 系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,系统默认Crash弹框等逻辑是通过AMS的handleApplicationCrash方法执行的。
  12. Crash是App性能的一个非常重要的指标,我们要尽可能的减少Crash,增加App的稳定性。

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

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

相关文章

AI生成式视频技术来临:Runway Gen-2文本生成视频

Runway Gen-2的官方网站提供了一种文本生成视频的工具。以下是对该工具的介绍: 文本生成视频:Runway Gen-2是一个创新的在线工具,可以将文本转化为视频。用户只需输入文本描述或句子,Runway Gen-2就能自动生成相应的视频内容。这…

机器学习-New Optimization

机器学习(New Optimization) 前言: 学习资料 videopptblog 下面的PPT里面有一些符号错误,但是我还是按照PPT的内容编写公式,自己直到符号表示什么含义就好了 Notation 符号解释 θ t \theta_t θt​第 t 步时,模型的参数 Δ L …

数据结构---并查集

目录标题 为什么会有并查集并查集的原理模拟实现并查集准备工作构造函数FindRootUnionSetCount 并查集实战题目一:省份数量题目解析题目二:等式方程的可满足性题目解析 为什么会有并查集 这里可以使用生活中的一个例子来带着大家理解并查集,…

深入理解 SQL:从基本查询到高级聚合

目录 背景理论知识示例1211. 查询结果的质量和占比(Round group by)1204. 最后一个能进入巴士的人 (Having limit order by)1193. 每月交易 I(if group by)1179. 重新格式化部门表1174. 即时食物配送 II&am…

JVM总结笔记

JVM JVM是什么?JVM 的主要组成部分JVM工作流程JVM内存模型直接内存与堆内存的区别:堆栈的区别Java会存在内存泄漏吗?简述Java垃圾回收机制垃圾收集算法轻GC(Minor GC)和重GC(Full GC)新生代gc流程JVM优化与JVM调优 JVM是什么? JVM是Java Virtual Mach…

Vue3 让localstorage变响应式

Hook使用方式: import {useLocalStore} from "../js/hooks"const aauseLocalStore("aa",1) 需求一: 通过window.localStorage.setItem可以更改本地存储是,还可以更新aa的值 window.localStorage.setItem("aa&quo…

Redis面试题

1、什么是 Redis?简述它的优缺点? Redis 的全称是:Remote Dictionary.Server,本质上是一个 Key-Value 类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作…

Linux操作系统2-软件的安装

软件安装方式 二进制发布包安装 软件已针对具体平台编译打包,只需要解压、修改配置rpm安装 安装按照redhat的包管理规范进行打包,使用rpm命令进行安装,不能自行解决库依赖问题yum安装 一种在线软件安装方式,本质上还是rpm安装&am…

【LeetCode每日一题】——766.托普利茨矩阵

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【题目进阶】八【解题思路】九【时间频度】十【代码实现】十一【提交结果】 一【题目类别】 矩阵 二【题目难度】 简单 三【题目编号】 766.托普利茨矩阵 四【题目描述…

Spring SSM整合

Spring SpringMvc Mybatis 整合 一. 配置类 1.1、 Spring配置类 Configuration ComponentScan({"com.itheima.service"}) PropertySource("classpath:jdbc.properties") Import({JdbcConfig.class, MybatisConfig.class}) EnableTransactionManagem…

使用Roles模块搭建LNMP架构

使用Roles模块搭建LNMP架构 1.Ansible-playbook中部署Nginx角色2.Ansible-playbook中部署PHP角色3.Ansible-playbook中部署MySQL角色4.启动安装分布式LNMP 1.Ansible-playbook中部署Nginx角色 创建nginx角色所需要的工作目录; mkdir -p /etc/ansible/playbook/rol…

【Python】jupyter Linux服务器使用

文章目录 环境使用访问 环境 pip install jupyter 使用 在你想访问的目录下执行: jupyter notebook --ip0.0.0.0jupyter 给出提示: [I 2023-07-28 14:32:43.589 ServerApp] Package notebook took 0.0000s to import [I 2023-07-28 14:32:43.597 Ser…

react中的高阶组件理解与使用

一、什么是高阶组件? 其实就是一个函数,参数是一个组件,经过这个函数的处理返回一个功能增加的组件。 二、代码中如何使用 1,高级组件headerHoc 2,在普通组件header中引入高阶组件并导出高阶组件,参数是普…

BUUCTF题目Crypto部分wp(持续更新)

Url编码 题目密文是%66%6c%61%67%7b%61%6e%64%20%31%3d%31%7d,根据题目名字使用python的urllib模块解码即可。flag{and 11} from urllib.parse import quote, unquotec r%66%6c%61%67%7b%61%6e%64%20%31%3d%31%7d m unquote(c, encodingutf-8) print(m)c2 quot…

Leetcode | DP | 338. 198. 139.

338. Counting Bits 重点在于这张图。 从i1开始,dp的array如果i是2的1次方之前的数,是1 dp[i - 2 ^ 0]; 如果i是2的2次方之前的数,是1 dp[i - 2 ^ 1]; 如果i是2的3次方之前的数,是1 dp[i - 2 ^ 2]; 198. House Robber 如果…

zookeeper学习(三)基础数据结构

数据模型 在 zookeeper 中,可以说 zookeeper 中的所有存储的数据是由 znode 组成的,节点也称为 znode,并以 key/value 形式存储数据。 整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 / 开头。 进入 zookeeper 安装的 …

如何查看 Chrome 网站有没有前端 JavaScript 报错?

您可以按照以下步骤在Chrome中查看网站是否存在前端JavaScript报错: 步骤1:打开Chrome浏览器并访问网站 首先,打开Chrome浏览器并访问您想要检查JavaScript报错的网站。 步骤2:打开开发者工具 在Chrome浏览器中,按…

【机器学习】Gradient Descent for Logistic Regression

Gradient Descent for Logistic Regression 1. 数据集(多变量)2. 逻辑梯度下降3. 梯度下降的实现及代码描述3.1 计算梯度3.2 梯度下降 4. 数据集(单变量)附录 导入所需的库 import copy, math import numpy as np %matplotlib wi…

OpenFeign 个性化_注解配置_日志_请求拦截_请求透传_FastJson解析

相关组件概念 Ribbon: Ribbon 是 Netflix开源的基于 HTTP 和 TCP 等协议负载均衡组件;Ribbon 可以用来做客户端负载均衡,调用注册中心的服务; Feign: Feign 是 Spring Cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客…

CompletableFuture 详解

目录 简单介绍 常见操作 创建 CompletableFuture new 关键字 静态工厂方法 处理异步结算的结果 简单介绍 CompletableFuture 同时实现了 Future 和 CompletionStage 接口。 public class CompletableFuture<T> implements Future<T>, CompletionStage<T…