Android Crash日志手机原理与实践

         Android 应用在开发和测试过程中,如果出现Crash,我们一般可以通过查看Logcat日志信息来获取崩溃的原因,如果是必现的Crash,可以保留手机的现场信息,很方便的进行调试和分析,但是当应用发布到应用市场后,在用户的手机上出现Crash,我们显然不可能直接去找用户要日志信息以及手机来进行定位,那么这时我们如何才能得到Crash相关的信息呢,这就要通过Crash 监控SDK来实现。

        Android 应用的Crash日志收集与上报是每个APP都必须具备的基本能力,Crash对应用的用户留存路,口碑等有着极大的影响,试想如果你使用的APP经常性的发生崩溃,你还会继续使用它吗,特别是现在市场上同类产品竞争如此激烈的情况下,一个App Crash率的高低是衡量程序员能力的一个指标,很多的公司都是将它作为程序员的KPI指标之一。
       大多数的开发者其实都不会面临需要自己研发Crash SDK的任务,原因很简单,市场上已经有很多第三方的Crash SDK可供选择,例如腾讯的Bugly,百度的MTJ,友盟SDK等,如果没有特殊需求,我们主要目标还是应该聚焦在App业务功能的实现与优化上面。

       实现一个Crash SDK包括以下三个方面:

1 Crash 的捕获 

2 Crash 堆栈信息的获取

3 Crash日志的上报

Android 底层是基于linux 操作系统构建的,上层是基于java语言实现的,上层与底层的通信基于JNI,因此在Android上面做开发,不可避免的需要跟java和c/c++打交道,相应的,Crash可能发生在这两层,Java 是基于虚拟机运行的,而C/C++更贴近操作系统,这两层的Crash机制差别比较大。

java层Crash 捕获机制

基本原理:

Android 应用程序都是基于java语言开发的,异常处理也是沿用java语言的机制,在java中异常分为两类:Checked Execption 和UnCheckedException。CheckedException又称为编译时异常,它是在编译阶段必须处理的,否则编译失败,一般使用try...crash 捕获并进行处理即可。UnCheckedException又称为运行时异常,这种类型的异常时在编译阶段检测不出来的,只有在程序运行时满足某些条件才会触发,当然也可以使用try..crash来捕获这类异常。

好在java API提供了一个全局异常捕获处理去,Android 应用在java层捕获Crash依赖的就是ThreadUncaughtExceptionHandler处理器接口,通常情况下,我们只需要实现这个接口,并重写其中的uncaughtException方法,在该方法中可以读取Crash的堆栈信息

Android Crash日志手机原理与实践_第1张图片

为了使用自定义的UncaughtExceptionHandler,我们还需要对它进行注册,以替换应用默认的异常处理器,一般都是在Application类的onCreate方法中进行注册

Android Crash日志手机原理与实践_第2张图片

通常情况下,收集发生Crash的堆栈信息就已经足够我们分析并定位出崩溃的原因,从而修复这个Crash ,但复杂一点的Crash ,可能仅仅有堆栈信息是不够的,我们还需要其它一些信息来辅助问题的定位和解决

线程信息

线程的基本信息包括ID,名字,优先级和所在的线程组,可以根据实际情况收集某些线程的信息,但通常收集发生Crash的线程信息即可,通常的线程信息收集代码如下:

Android Crash日志手机原理与实践_第3张图片

SharedPreference 信息

某些类型的Crash依赖于应用的SharedPreference中的某些信息项,例如某个开关,当打开时,会导致APP运行发生Crash,关闭时不存在问题,这时为了准确复现这个Crash,如果有收集SharedPreference中的信息,将会极大的加速问题的定位,通过的收集代码如下

Android Crash日志手机原理与实践_第4张图片
Android Crash日志手机原理与实践_第5张图片

系统设置

 在Android中,许多的系统属性都是在系统设置中进行设置的,比如蓝牙,WIFI的状态,当前的首选语言,屏幕亮度,这些信息存放在数据库中,对应的URL为content://settings/system,content://systems/secure,对这些数据库的读写操作对应着Android SDK中的settings类,我们对系统设置的读写本质上就说对这些数据库表的操作。

system:以键值对的形式存放系统中的各个类型的常规偏好设置,它是可读写的,获取这种类型设置的代码如下,使用反射的方式是为了兼任不同的API Level

Android Crash日志手机原理与实践_第6张图片

Secure:以键值对的形式存放系统的安全设置,这个是只读的,获取这种类型设置的代码如下:

Android Crash日志手机原理与实践_第7张图片

Global:以键值对的形式存放系统中对所有用户公用的偏好设置,它是只读的,获取这种类型设置的代码如下:

Android Crash日志手机原理与实践_第8张图片

Logcat中的日志记录

捕获Logcat日志的好处是可以清楚的知道Crash发生前后的上下文,对于准确定位Crash来说提供了更完备的信息

自定义Log文件中的内容

Android Crash日志手机原理与实践_第9张图片
Android Crash日志手机原理与实践_第10张图片

Meminfo信息

Crash发生时的内存使用情况对某些类型的Crash定位也是有很大帮助的,通过执行dumpsys meminfo 命令可以获取到当前进程的内存使用信息

Android Crash日志手机原理与实践_第11张图片

Native层Crash捕获机制

Native层代码的错误可以分为两种:
1 C++异常:在Native层,如果使用C++语言进行开发,而且使用了C++异常机制,那么函数执行可以抛出std:exception类型的异常,如果使用C/C++语言开发,使用的是错误码机制,那么对于一些导致系统不可用的错误码,我们也可以进行捕获上报,C++异常通常是可以捕获的,一般不会引起App Crash,如果处理不当,会引起逻辑错误

2 Fatal Signal异常:在Native层,由于C/C++野指针或者内存读取越界等原因,导致App整个Crash的错误,这种Crash一般会在Logcat中打印出包含Fatal signal字样的日志,对于这种Crash ,前面介绍的java异常捕获类Thread.UncaughtExceptionHandler是检测不到的。

这种Crash 是基于Linux的信号处理机制,信号本质是一种软件层面的中断机制,用来通知进程发生了异步时间,进程之间可以互相通过系统调用kill来发生软中断信号,Linux内核也可以因为内部时间而给进程发送信号,通知进程某个时间的发生,需要注意的是,信号并不携带任何数据,它只是用来通知某个进程发生了什么事件,接受到信号后,通常有三种处理方式:

1 自定义处理信号:进程为需要处理的信号提供信号处理函数

2忽略信号:进程忽略不感兴趣的信号

3 使用系统的默认处理:使用内核的默认信号处理函数,默认情况如下,系统对大部分信号的缺省操作是终止进程

由于Native层Crash大部分都是signal软中断类型错误,因此只要捕获signal并进行处理,得到中断的具体信息就可以很好帮助定位了,这一步可以通过调用sigaction注册信号处理函数来完成。

Android Crash日志手机原理与实践_第12张图片
Android Crash日志手机原理与实践_第13张图片

上面代码种的my_handler回调函数就是用来处理信号的,在这个函数中,我们设法获取Native Crash的相关堆栈信息,然后上报给服务器,但是Native层并没有提供像Java层那样的Throwable.printStackTrace函数来获取堆栈信息,目前来说有两种思路。

1 抓取Logcat日志:Naive层发生fatal signal导致App崩溃,也会在Logcat中打印出相关的堆栈信息,因此,当Native层检测到fatal signal ,利用我们的信号处理函数my_handler可以向java层发送消息,通知它去抓取Logcat的日志

2 Google Breadpad:这是一个跨平台的崩溃转储和分析工具,支持Windows,Linux,Android等,提供集成它提供的函数库,在应用发生崩溃时会将相关堆栈信息写入一个minidump格式文件中。

Crash的上报


Crash的上报基本工作就是和服务端定好通信协议的格式,通常情况下,Crash上报的信息除了包含上面说到的堆栈信息之外,还需要包含以下的信息

应用的版本号

系统类型及版本号

手机设备型号

手机唯一的设备ID

渠道号

Crash发生的时间

应用的包名

你可能感兴趣的:(Android Crash日志手机原理与实践)