Android平台的碎片化与Android崩溃SDK日志捕获(Checked、unChecked异常)
2016-01-14 09:21
573 查看
》Android源码中有Google做的原生Launcher。在2.1及之前的版本中,使用com.android.launcher;2.2至4.3版本使用的是com.android.launcher2;4.4版本开始则使用com.android.launcher3。
应用要创建和删除自己的快捷方式图标只需发送如下两个Intent即可:
同时需要在AndroidManifest.xml中添加如下两个权限:
》常见的Android崩溃有两类,一类是Java Exception异常,一类是Native Signal异常。对于很多基于Unity、Cocos平台的游戏,还会有C#、JavaScript、Lua等的异常。
Java的异常可以分为两类:Checked Exception和UnChecked Exception。所有RuntimeException类及其子类的实例被称为Runt ime异常,即UnChecked Exception,不是RuntimeException类及其子类的异常实例则被称为Checked Exception。
Checked异常又称为编译时异常,即在编译阶段被处理的异常。编译器会强制程序处理所有的Checked异常,也就是用try…catch显式的捕获并处理,因为Java认为这类异常都是可以被处理(修复)的。在Java API文档中,方法说明时,都会添加是否throw某个exception,这个exception就是Checked异常。如果没有try…catch这个异常,则编译出错,错误提示类似于“Unhandled
该类异常捕获的流程是:
执行try块中的代码出现异常,系统会自动生成一个异常对象,并将该异常对象提交给Java运行环境,这个就是异常抛出(throw)阶段;
当Java运行环境收到异常对象时,会寻找最近的能够处理该异常对象的catch块,找到之后把该异常对象交给catch块处理,这个就是异常捕获(catch)阶段。
Checked异常一般是不引起Android App Crash的,注意是“一般”,这里之所以介绍,有两个原因:
形成系统的了解,更好地对比理解UnCheckedException;
对于一些Checked Exception,虽然我们在程序里面已经捕获并处理了,但是如果能同时将该异常收集并发送到后台,将有助于提升App的健壮性。比如修改代码逻辑回避该异常,或者捕获后采用更好的方法去处理该异常。至于应该收集哪些Checked Exception,则取决于App的业务逻辑和开发者的经验了。
UnChecked异常又称为运行时异常,即Runtime-Exception,最常见的莫过于NullPointerException。UnChecked异常发生时,由于没有相应的try…catch处理该异常对象,所以Java运行环境将会终止,程序将退出,也就是我们所说的Crash。当然,你可能会说,那我们把这些异常也try…catch住不就行了。理论上确实是可以的,但有两点会导致这种方案不可行:
无法将所有的代码都加上try…catch,这样对代码的效率和可读性将是毁灭性的;
UnChecked异常通常都是较为严重的异常,或者说已经破坏了运行环境的。比如内存地址,即使我们try…catch住了,也不能明确知道如何处理该异常,才能保证程序接下来的运行是正确的。
android奔溃日志收集SDK。
》Exception的分类及捕获:
static
class
MyCrashHandler
implements
UncaughtExceptionHandler {
private
UncaughtExceptionHandler originalHandler;
private
MyCrashHandler(Context context) {
originalHandler
= Thread.getDefaultUncaughtExceptionHandler(); }
@Override
public
void
uncaughtException(Thread thread, final
Throwable throwable) {
//
Deal this exception
if
(originalHandler != null)
{
originalHandler.uncaughtException(thread,
throwable);
}
}}
》获取Exception崩溃堆栈:
捕获Exception之后,我们还需要知道崩溃堆栈的信息,这样有助于我们分析崩溃的原因,查找代码的Bug。异常对象的printStackTrace方法用于打印异常的堆栈信息,根据printStackTrace方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。
》Native崩溃分析与捕获
Linux进程对信号异常的响应可以归结为以下几类:
忽略信号:对信号不做任何处理,除了SIGKILL及SIGSTOP以外(超级用户杀掉进程时产生),其他都可以忽略;
捕获信号:注册信号处理函数,当信号发生时,执行相应的处理函数;
默认处理:执行内核分配的默认信号处理函数,大多数我们遇到的信号异常,默认处理是终止程序并生成core文件。
对Native代码的崩溃,可以通过调用sigaction()注册信号处理函数来完成。
signum:代表信号编码,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号,如果为这两个信号定义自己的处理函数,将导致信号安装错误。
act:指向结构体sigaction的一个实例的指针,该实例指定了对特定信号的处理,如果设置为空,进程会执行默认处理。
oldact:和参数act类似,只不过保存的是原来对相应信号的处理,也可设置为NULL。
Native代码崩溃(即信号异常)捕获的代码片段如下:
const
int
handledSignals[] = { SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS };
const
int
handledSignalsNum = sizeof(handledSignals)
/ sizeof(handledSignals[0]);
struct
sigaction old_handlers[handledSignalsNum];
int
nativeCrashHandler_onLoad(JNIEnv *env) {
struct
sigaction handler;
memset(&handler,
0,
sizeof(sigaction));
handler.sa_sigaction
= my_sigaction;
handler.sa_flags
= SA_RESETHAND;
for
(int
i = 0;
i < handledSignalsNum; ++i) { sigaction(handledSignals[i], &handler, &old_handlers[i]); }
return
1;
}
当Android应用程序加载so库的时候,调用nativeCrashHandler_onLoad会为SIGSEGV、SIGABRT、SIGFPE、SIGILL、SIGBUS通过sigaction注册信号处理函数my_sigaction。当发生Native崩溃并且发生前面几个信号异常时,就会调用my_sigaction完成信号处理。
Android没有提供像throwable.printStackTrace一样的接口去获取Native崩溃后堆栈信息,所以我们需要自己想办法实现。这里有两种思路可以考虑。
利用LogCat日志
在本地调试代码时,我们经常通过查看LogCat日志来分析解决问题。对于发布的应用,在代码中执行命令“logcat -d -v threadtime”也能达到同样的效果,只不过是获取到了用户手机的logcat。当Native崩溃时,Android系统同样会输出崩溃堆栈到LogCat,那么拿到了LogCat信息也就拿到了Native的崩溃堆栈。
在my_sigaction捕获到异常信号后,通知Java层代码,在Java层启动新的进程,并在新的进程中完成上面的操作。这里注意一定要在新的进程中完成,因为原有的进程马上就会结束。
网络上有一些对应这种思路的代码,但是在很多手机上都无法获得Native的崩溃堆栈。原因是对崩溃堆栈产生了破坏,使得相关信息并没有输出到logcat中。研究一下Android backtrace的底层实现以及Google Breakpad的源码,会帮助你解决这个问题。
Google Breakpad
Linux提供了Core Dump机制,即操作系统会把程序崩溃时的内存内容dump出来,写入一个叫做core的文件里面。Google Breakpad作为跨平台的崩溃转储和分析模块(支持Windows、OS X、Linux、iOS和Android等),便是通过类似的MiniDump机制来获取崩溃堆栈的。
通过Google Breakpad捕获信号异常,并将堆栈信息写入你指定的本地MiniDump文件中。下次启动应用程序的时候,便可以读取该MiniDump文件进行相应的操作,比如上传到后台服务器。当然,也可以修改Google Breakpad的源码,不写MiniDump文件,而是通过dumpCallback直接获得堆栈信息,并将相关信息通知到Java层代码,做相应的处理。
Google Breakpad是权威的捕获Native崩溃的方法,相关的使用方法可以查看官网文档。由于它跨平台,代码体量较大,所以建议大家裁剪源码,只保留Android相关的功能,保持自己APK的小巧。
应用要创建和删除自己的快捷方式图标只需发送如下两个Intent即可:
com.android.launcher.action.INSTALL_SHORTCUT com.android.launcher.action.UNINSTALL_SHORTCUT
同时需要在AndroidManifest.xml中添加如下两个权限:
android:name="com.android.launcher.permission.INSTALL_SHORTCUT android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT
》常见的Android崩溃有两类,一类是Java Exception异常,一类是Native Signal异常。对于很多基于Unity、Cocos平台的游戏,还会有C#、JavaScript、Lua等的异常。
Java的异常可以分为两类:Checked Exception和UnChecked Exception。所有RuntimeException类及其子类的实例被称为Runt ime异常,即UnChecked Exception,不是RuntimeException类及其子类的异常实例则被称为Checked Exception。
Checked异常又称为编译时异常,即在编译阶段被处理的异常。编译器会强制程序处理所有的Checked异常,也就是用try…catch显式的捕获并处理,因为Java认为这类异常都是可以被处理(修复)的。在Java API文档中,方法说明时,都会添加是否throw某个exception,这个exception就是Checked异常。如果没有try…catch这个异常,则编译出错,错误提示类似于“Unhandled
该类异常捕获的流程是:
执行try块中的代码出现异常,系统会自动生成一个异常对象,并将该异常对象提交给Java运行环境,这个就是异常抛出(throw)阶段;
当Java运行环境收到异常对象时,会寻找最近的能够处理该异常对象的catch块,找到之后把该异常对象交给catch块处理,这个就是异常捕获(catch)阶段。
Checked异常一般是不引起Android App Crash的,注意是“一般”,这里之所以介绍,有两个原因:
形成系统的了解,更好地对比理解UnCheckedException;
对于一些Checked Exception,虽然我们在程序里面已经捕获并处理了,但是如果能同时将该异常收集并发送到后台,将有助于提升App的健壮性。比如修改代码逻辑回避该异常,或者捕获后采用更好的方法去处理该异常。至于应该收集哪些Checked Exception,则取决于App的业务逻辑和开发者的经验了。
UnChecked异常又称为运行时异常,即Runtime-Exception,最常见的莫过于NullPointerException。UnChecked异常发生时,由于没有相应的try…catch处理该异常对象,所以Java运行环境将会终止,程序将退出,也就是我们所说的Crash。当然,你可能会说,那我们把这些异常也try…catch住不就行了。理论上确实是可以的,但有两点会导致这种方案不可行:
无法将所有的代码都加上try…catch,这样对代码的效率和可读性将是毁灭性的;
UnChecked异常通常都是较为严重的异常,或者说已经破坏了运行环境的。比如内存地址,即使我们try…catch住了,也不能明确知道如何处理该异常,才能保证程序接下来的运行是正确的。
android奔溃日志收集SDK。
》Exception的分类及捕获:
static
class
MyCrashHandler
implements
UncaughtExceptionHandler {
private
UncaughtExceptionHandler originalHandler;
private
MyCrashHandler(Context context) {
originalHandler
= Thread.getDefaultUncaughtExceptionHandler(); }
@Override
public
void
uncaughtException(Thread thread, final
Throwable throwable) {
//
Deal this exception
if
(originalHandler != null)
{
originalHandler.uncaughtException(thread,
throwable);
}
}}
》获取Exception崩溃堆栈:
捕获Exception之后,我们还需要知道崩溃堆栈的信息,这样有助于我们分析崩溃的原因,查找代码的Bug。异常对象的printStackTrace方法用于打印异常的堆栈信息,根据printStackTrace方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。
public static String getStackTraceInfo(final Throwable throwable) { String trace = ""; try { Writer writer = new StringWriter(); PrintWriter pw = new PrintWriter(writer); throwable.printStackTrace(pw); trace = writer.toString(); pw.close(); } catch (Exception e) { return ""; } return trace; }
》Native崩溃分析与捕获
Linux进程对信号异常的响应可以归结为以下几类:
忽略信号:对信号不做任何处理,除了SIGKILL及SIGSTOP以外(超级用户杀掉进程时产生),其他都可以忽略;
捕获信号:注册信号处理函数,当信号发生时,执行相应的处理函数;
默认处理:执行内核分配的默认信号处理函数,大多数我们遇到的信号异常,默认处理是终止程序并生成core文件。
对Native代码的崩溃,可以通过调用sigaction()注册信号处理函数来完成。
#include <signal.h> int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
signum:代表信号编码,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号,如果为这两个信号定义自己的处理函数,将导致信号安装错误。
act:指向结构体sigaction的一个实例的指针,该实例指定了对特定信号的处理,如果设置为空,进程会执行默认处理。
oldact:和参数act类似,只不过保存的是原来对相应信号的处理,也可设置为NULL。
Native代码崩溃(即信号异常)捕获的代码片段如下:
const
int
handledSignals[] = { SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS };
const
int
handledSignalsNum = sizeof(handledSignals)
/ sizeof(handledSignals[0]);
struct
sigaction old_handlers[handledSignalsNum];
int
nativeCrashHandler_onLoad(JNIEnv *env) {
struct
sigaction handler;
memset(&handler,
0,
sizeof(sigaction));
handler.sa_sigaction
= my_sigaction;
handler.sa_flags
= SA_RESETHAND;
for
(int
i = 0;
i < handledSignalsNum; ++i) { sigaction(handledSignals[i], &handler, &old_handlers[i]); }
return
1;
}
当Android应用程序加载so库的时候,调用nativeCrashHandler_onLoad会为SIGSEGV、SIGABRT、SIGFPE、SIGILL、SIGBUS通过sigaction注册信号处理函数my_sigaction。当发生Native崩溃并且发生前面几个信号异常时,就会调用my_sigaction完成信号处理。
notifyNativeCrash = (*env)->GetMethodID(env, cls, "notifyNativeCrash", "()V"); void my_sigaction(int signal, siginfo_t *info, void *reserved) { // Here catch the native crash }
Android没有提供像throwable.printStackTrace一样的接口去获取Native崩溃后堆栈信息,所以我们需要自己想办法实现。这里有两种思路可以考虑。
利用LogCat日志
在本地调试代码时,我们经常通过查看LogCat日志来分析解决问题。对于发布的应用,在代码中执行命令“logcat -d -v threadtime”也能达到同样的效果,只不过是获取到了用户手机的logcat。当Native崩溃时,Android系统同样会输出崩溃堆栈到LogCat,那么拿到了LogCat信息也就拿到了Native的崩溃堆栈。
Process process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-v","threadtime"}); String logTxt = getSysLogInfo(process.getInputStream());
在my_sigaction捕获到异常信号后,通知Java层代码,在Java层启动新的进程,并在新的进程中完成上面的操作。这里注意一定要在新的进程中完成,因为原有的进程马上就会结束。
网络上有一些对应这种思路的代码,但是在很多手机上都无法获得Native的崩溃堆栈。原因是对崩溃堆栈产生了破坏,使得相关信息并没有输出到logcat中。研究一下Android backtrace的底层实现以及Google Breakpad的源码,会帮助你解决这个问题。
Google Breakpad
Linux提供了Core Dump机制,即操作系统会把程序崩溃时的内存内容dump出来,写入一个叫做core的文件里面。Google Breakpad作为跨平台的崩溃转储和分析模块(支持Windows、OS X、Linux、iOS和Android等),便是通过类似的MiniDump机制来获取崩溃堆栈的。
通过Google Breakpad捕获信号异常,并将堆栈信息写入你指定的本地MiniDump文件中。下次启动应用程序的时候,便可以读取该MiniDump文件进行相应的操作,比如上传到后台服务器。当然,也可以修改Google Breakpad的源码,不写MiniDump文件,而是通过dumpCallback直接获得堆栈信息,并将相关信息通知到Java层代码,做相应的处理。
Google Breakpad是权威的捕获Native崩溃的方法,相关的使用方法可以查看官网文档。由于它跨平台,代码体量较大,所以建议大家裁剪源码,只保留Android相关的功能,保持自己APK的小巧。
相关文章推荐
- Android弹性侧拉抽屉效果
- 《Android源码设计模式解析与实战》读书笔记(二十)
- Android学习之Fragment
- Android Studio 1.4.x导入ffmpeg进行JNI编程
- [原创]android resource&nbsp…
- [原创]android studio占用很多内存
- @+id/android:list"和"@android:id/list"的写法
- android的handler图片循环播放
- Android中的PopupWindow详解
- Android Lolipop 屏蔽隐式Intent检查引发的错误
- Android日期选择器用法
- Android 内存抖动 性能分析 <10>
- SurfaceView 基础用法
- android138 360 小火箭
- Android Studio项目目录结构介绍
- 从TextView源码解析:“android开发中,文字的最难适配”
- Android学习第一章第一节android学习路线图
- android sdk是个啥
- 我用的Android Studio插件
- ant android 打包签名和渠道