欢迎光临
专注android技术,聚焦行业精粹,我们一直在努力

Android崩溃日志收集:Java、Lua和C#脚本、native

1、前言

崩溃日志栈收集是 Android 应用基础的功能,我们最常收集的崩溃信息通常来自于 java 或者 native。而游戏应用还会
包含一些脚本的崩溃,常见的有c#脚本和Lua脚本。
本文会围绕这4种崩溃类型,学习如何收集,以及这些平台崩溃处理的一些知识

2、Android Java 崩溃

2.1、java 里面线程崩溃了的整体流程

  1. jvm 将线程崩溃派发给对应线程的dispatchUncaughtException方法
  2. 判断线程是否有自己的uncaughtExceptionHandler,有则交给它处理。如果无则交给该线程所属的ThreadGroup处理(ThreadGroup继承了UncaughtExceptionHandler)
  3. ThreadGroup会继续将该异常抛给它的父ThreadGroup(如果有),如果没有父ThreadGroup,则派发给Thread.getDefaultUncaughtExceptionHandler去处理
  4. 上一步中Thread的defaultUncaughtExceptionHandler会在进程创建时初始化的时候(RuntimeInit#commonInit)设置一个默认的handler(KillApplicationHandler)。应用程序随时可以通过Thread.setDefaultUncaughtExceptionHandler改变这个默认的handler

2.2、java 崩溃日志抓取流程

通过上面我们知道了java崩溃的派发流程,我们可以设置自己的handler来处理jvm派发的崩溃信息(处理操作包括:崩溃栈信息收集、上传、打印等)。同时为了保证程序能正常的崩溃,我们在前面覆盖uncaughtExceptionHandler的时候先缓存系统默认的handler,在我们处理完崩溃信息后,将崩溃继续抛给系统的handler。这样就完成了java 崩溃日志抓取的的逻辑。

2.3、抓取示例

设置我们自己的handler

我们就可以在mMyExcetionHandler里面收集异常信息了,注意:收集完异常信息,建议该异常抛给系统处理,因为此时应用已经无法正常运行或运行行为不正确。做法如下:

2.3.1、捕获异常后想弹窗提示

也许你想在捕获到异常后弹窗提示给用户,你需要注意uncaughtException方法里面是不能做UI操作的,你可以通过intent方式启动另一个activity来实现:

 

3、Native Crash 崩溃

3.1、Native Crash 怎么产生

与 Java 平台不同,C/C++ 没有一个通用的异常处理接口,在 C 层,CPU 通过异常中断的方式,触发异常处理流程。不同的处理器,有不同的异常中断类型和中断处理方式,linux 把这些中断处理,统一为信号量,每一种异常都有一个对应的信号,可以注册回调函数进行处理需要关注的信号量。
所有的信号量都定义在<signal.h>文件中,这里我将几乎全部的信号量以及所代表的含义都标注出来了:

3.2、Native 崩溃信息抓取

通常我们在做 crash 收集的时候,主要关注这几个信号量:

接收Native Crash崩溃信息:

可以用sigaction方法来接收native crash 崩溃信息回调

函数说明:sigaction()会依参数signum 指定的信号编号来设置该信号的处理函数. 参数signum 可以指定SIGKILL 和SIGSTOP 以外的所有信号。如参数结构sigaction 定义如下:

第一个参数 int 类型,表示需要关注的信号量
第二个参数 sigaction 结构体指针,用于声明当某个特定信号发生的时候,应该如何处理。
第三个参数也是 sigaction 结构体指针,他表示的是默认处理方式,当我们自定义了信号量处理的时候,用他存储之前默认的处理方式。

所以,要订阅异常发生的信号,最简单的做法就是直接用一个循环遍历所有要订阅的信号,对每个信号调用sigaction()

 

一个详细的Native 崩溃栈信息如下:

 

详细参考这篇文章:《Android Native Crash 收集

 

4、C# 脚本崩溃收集

C#脚本未捕获的异常,与Android和Native未捕获异常很大的区别是,未捕获异常不会照成引用的闪退。所以,C#脚本的异常危害相对较小,但是同样更加容易存在在游戏中。而闪退问题能够及时发现并进行修复。C#脚本异常,抛出的时机不同,危害性也有所不同; 在Start、Awake等函数抛出的异常,会造成Update、OnGUI无法正常运行,游戏可能表现为无响应、图片确实等。Update、OnGUI的异常也一定会引起游戏逻辑及画面上的一些异常。

从测试角度,C#脚本未捕获的异常时一定需要报告给开发者的。

C#里面提供了两个方法可以接收处理脚本异常:

  • AppDomain.CurrentDomain.UnhandledException
  • Application.RegisterLogCallback或者Application.logMessageReceived

UnhandledException

如果是在默认域中注册,任何线程中抛出的未捕获异常均会触发这个未处理异常函数。

收到崩溃并提取崩溃信息的代码示例如下:

 

Application.RegisterLogCallback

在开发的过程中,为了防止游戏运行产生异常信息,可以借助unity为我们提供的log回调方法,判断运行过程中是否产生意外状况,比如游戏运行过程中产生一些异常错误,我们可以通过该方法,对异常进行处理。例如根据日志的TAG都会包含Unity,可以大致判断出这条日志是UnityEngine自身的接口打印的,在catch异常之后也是会调用Debug.LogError来输出日志的。所以,我们可以通过注册RegisterLogCallBack来获取到系统的调用。

另外C#还提供了另一个类似方法:RegisterLogCallbackThreaded,它们都是在一个日志信息上注册一个委托来被调用,这个函数和RegisterLogCallback唯一不同的是,这个函数将从不同的线程被调用,注意:你只有你知道你在做什么时才能使用这个函数,否则使用Application.RegisterLogCallback

在使用RegisterLogCallback方法时还需要特别注意:检查是否有其他存在注册Application.RegisterLogCallback(LogCallback)的逻辑,由于系统默认的LogCallback是单播实现,所以只能维持一个回调实例

4.1、代码示例

监听崩溃异常信息回调:

注意:在unity5.0版本以上RegisterLogCallback的单播实现变为了可以注册多个回调。

我们可以在Unity的MonoBehaviour子类的Start()方法里面初始化上面的内容。

 

5、Lua 脚本崩溃

lua 里面获取脚本执行过程中的异常信息是通过pcall的执行结果来实现的。我们来看下lua的pcall方法介绍:

[-(nargs + 1), +(nresults|1), ]

Calls a function in protected mode.

Both nargs and nresults have the same meaning as in lua_call. If there are no errors during the call,lua_pcall behaves exactly like lua_call. However, if there is any error, lua_pcall catches it, pushes a single value on the stack (the error message), and returns an error code. Like lua_calllua_pcall always removes the function and its arguments from the stack.

If errfunc is 0, then the error message returned on the stack is exactly the original error message. Otherwise, errfunc is the stack index of an error handler function. (In the current implementation, this index cannot be a pseudo-index.) In case of runtime errors, this function will be called with the error message and its return value will be the message returned on the stack by lua_pcall.

Typically, the error handler function is used to add more debug information to the error message, such as a stack traceback. Such information cannot be gathered after the return of lua_pcall, since by then the stack has unwound.

The lua_pcall function returns 0 in case of success or one of the following error codes (defined in lua.h):

  • LUA_ERRRUN: a runtime error.
  • LUA_ERRMEM: memory allocation error. For such errors, Lua does not call the error handler function.
  • LUA_ERRERR: error while running the error handler function.

lua对pcall封装了lua和c两种语言的实现,我们分别对这两种接口介绍如何抓取lua脚本崩溃日志信息:

5.1、Lua实现

我们用一个例子介绍在lua里面抓取执行异常,在java里面有try..catch可以抓住某段代码执行的异常信息,但lua原生并没有提供try-catch的语法来捕获异常处理,但是提供了pcall/xpcall等接口,可在保护模式下执行lua函数。

因此,可以通过封装这两个接口,来实现try-catch块的捕获机制。

我们可以先来看下,封装后的try-catch使用方式:

上面的代码中,在try块内部认为引发了一个异常,并且抛出错误消息,在catch中进行了捕获,并且将错误消息进行输出显示。

这里除了对pcall/xpcall进行了封装,用来捕获异常信息,还利用了lua的函数调用语法特性,在只有一个参数传递的情况下,lua可以直接传递一个table类型,并且省略()

其实try后面的整个{...} 都是一个table而已,作为参数传递给了try函数,其具体实现如下:

可以看到这里用了pcall来实际调用try块里面的函数,这样就算函数内部出现异常,也不会中断程序,pcall会返回false表示运行失败。同时我们也可以使用 debug.traceback方法获得当前调用栈的栈回溯信息。

traceback的函数介绍如下:

traceback ([thread,] [message [, level]]):如果 message 有,且不是字符串或 nil, 函数不做任何处理直接返回 message。 否则,它返回调用栈的栈回溯信息。 字符串可选项 message 被添加在栈回溯信息的开头。 数字可选项 level 指明从栈的哪一层开始回溯 (默认为 1 ,即调用 traceback 的那里)。

5.2、C 实现

lua 提供了 C的接口让它接入android,unity,ios等其它平台成为可能,例如:android可以通过native方法调到lua的c的接口,再通过lua的c库的pcall接口来执行lua脚本。

lua的C库中执行lua脚本并设置异常处理函数的代码示例如下:

 

6、崩溃日志分析工具

上面介绍了这么多语言的崩溃日志抓取的方法,介绍一个崩溃日志分析工具——LogAnalyzer

 

赞(1) 打赏
未经允许不得转载:花花鞋 » Android崩溃日志收集:Java、Lua和C#脚本、native
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

国内精品Android技术社区

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏