一、什么是Crash?
二、Android中有哪些类型Crash
通常情况下会有以下两种类型Crash:
三、Java层捕获Crash
通过UncaughtExceptionHandler来记录dump异常日志
package com.devilwwj.androidcrashdemo;
/**
* com.devilwwj.androidcrashdemo
* Created by devilwwj on 16/5/27.
*/
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static final boolean DEBUG = true;
private static final String PATH = Environment
.getExternalStorageDirectory() + "/CrashDemo/log/";
private static final String FILE_NAME = "crash";
private static final String FILE_NAME_SUFFIX = ".trace";
private static final String ABOLUTE_PATH = PATH + FILE_NAME + FILE_NAME_SUFFIX;
private String deviceToken;
private static CrashHandler sInstance = new CrashHandler();
private UncaughtExceptionHandler mDefaultCrashHandler;
private Context mContext;
private CrashHandler() {
}
public static CrashHandler getInstance() {
return sInstance;
}
public void init(Context context) {
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
mContext = context.getApplicationContext();
}
/**
* 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用#uncaughtException方法
* thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息。
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
try {
// 导出异常信息到SD卡中
dumpExceptionToSDCard(ex);
} catch (IOException e) {
e.printStackTrace();
}
ex.printStackTrace();
// 如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己
if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread, ex);
} else {
Process.killProcess(Process.myPid());
}
}
private File dumpExceptionToSDCard(Throwable ex) throws IOException {
// 如果SD卡不存在或无法使用,则无法把异常信息写入SD卡
if (!Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
if (DEBUG) {
Log.w(TAG, "sdcard unmounted,skip dump exception");
return null;
}
}
File dir = new File(PATH);
if (!dir.exists()) {
dir.mkdirs();
}
long current = System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date(current));
// File file = new File(PATH + FILE_NAME + time + "_"+ deviceToken +
// FILE_NAME_SUFFIX);
File file = new File(PATH + FILE_NAME + FILE_NAME_SUFFIX);
if (!file.exists()) {
file.createNewFile();
} else {
try {
// 追加内容
PrintWriter pw = new PrintWriter(new BufferedWriter(
new FileWriter(file, true)));
pw.println(time);
dumpPhoneInfo(pw);
pw.println();
ex.printStackTrace(pw);
pw.println("---------------------------------分割线----------------------------------");
pw.println();
pw.close();
} catch (Exception e) {
Log.e(TAG, "dump crash info failed");
}
}
return file;
}
private void dumpPhoneInfo(PrintWriter pw) throws NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),
PackageManager.GET_ACTIVITIES);
pw.print("App Version: ");
pw.print(pi.versionName);
pw.print('_');
pw.println(pi.versionCode);
// android版本号
pw.print("OS Version: ");
pw.print(Build.VERSION.RELEASE);
pw.print("_");
pw.println(Build.VERSION.SDK_INT);
// 手机制造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);
// 手机型号
pw.print("Model: ");
pw.println(Build.MODEL);
// cpu架构
pw.print("CPU ABI: ");
pw.println(Build.CPU_ABI);
}
/**
* 提供方法上传异常信息到服务器
* @param log
*/
private void uploadExceptionToServer(File log) {
// TODO Upload Exception Message To Your Web Server
}
}
四、Native层捕获Crash
1.Native Crash在Android上的特点
我们在实际Android开发的时候,可能会引入第三方的一些so库或者自己开发相应的so库供程序使用,然而so库一般是通过c或者c++开发的。Android开发者通过java层的JNI机制调用Native语言写的函数,然而Natice语言也可以调用java层的函数。 如果有同学不明白的话,建议先去了解下JNI的相应技术,总的来说通过JNI技术,就让我们让Java世界跟Native世界可以联系在一起,也因为这个特性,让Java具有跨平台的特性。
2.Native Crash是如何产生的?
Java层定义Native方法
JNI层实现Native方法
通过Java调用Native方法
3.Native Crash如何分析?
既然要分析就必须找到可以分析的东西,我们在分析Java层Crash的时候是通过logcat日志找到对应的出错代码,然而Native层Crash也是可以logcat日志来进行分析的。
这里我们截取上面制造的crash在logcat显示的日志:
这个是什么鬼,看不懂啊有木有。这个出错信息是我们调用native函数时打印出来的日志,只是简单的描述出错信号,出错地址还有进程号,看这个是完全摸不着调的。不过系统还是会提供相关有用的日志,我们在Android Studio查看logcat的时候需要做一下过滤。
在logcat添加完”DEBUG”的过滤项之后,我们就能得到以下log:
- 进程信息:pid表示进程号,tid表示线程号,name表示进程名
- 错误信号:signal 11表示信号的数字,SIGSEGV表示信号的名字,code 1(SEGV_MAPERR)表示出错代码,fault addr 00000000 表示出错的地址。
- 寄存器快照:进程收到错误信号时保存下来的寄存器快照,一共有15个寄存器。
- 堆栈信息:##00表示栈顶,##01调用#00,以此往下都是嵌套的调用关系,直至到栈顶。
我们在栈顶就已经看到我们出错的地方了:
#00 pc 00000730 /data/app-lib/com.devilwwj.jnidemo-1/libJNIDemo.so (Java_com_devilwwj_jnidemo_TestJNI_createANativeCrash)
1
从上面的分析我们可以看到,so库崩溃时会产生信号异常,如果我们能够捕获到信号异常,相当于我们也能够顾捕获到Android Native崩溃了。
1)addr2line
addr2line 是 用来获得指定动态链接库文件或者可执行文件中指定地址对应的源代码信息的工具.可以根据崩溃地址获得对应的函数。
4.常见的crash信号量
#define SIGHUP 1 // 终端连接结束时发出(不管正常或非正常)
#define SIGINT 2 // 程序终止(例如Ctrl-C)
#define SIGQUIT 3 // 程序退出(Ctrl-\)
#define SIGILL 4 // 执行了非法指令,或者试图执行数据段,堆栈溢出
#define SIGTRAP 5 // 断点时产生,由debugger使用
#define SIGABRT 6 // 调用abort函数生成的信号,表示程序异常
#define SIGIOT 6 // 同上,更全,IO异常也会发出
#define SIGBUS 7 // 非法地址,包括内存地址对齐出错,比如访问一个4字节的整数, 但其地址不是4的倍数
#define SIGFPE 8 // 计算错误,比如除0、溢出
#define SIGKILL 9 // 强制结束程序,具有最高优先级,本信号不能被阻塞、处理和忽略
#define SIGUSR1 10 // 未使用,保留
#define SIGSEGV 11 // 非法内存操作,与SIGBUS不同,他是对合法地址的非法访问,比如访问没有读权限的内存,向没有写权限的地址写数据
#define SIGUSR2 12 // 未使用,保留
#define SIGPIPE 13 // 管道破裂,通常在进程间通信产生
#define SIGALRM 14 // 定时信号,
#define SIGTERM 15 // 结束程序,类似温和的SIGKILL,可被阻塞和处理。通常程序如果终止不了,才会尝试SIGKILL
#define SIGSTKFLT 16 // 协处理器堆栈错误
#define SIGCHLD 17 // 子进程结束时, 父进程会收到这个信号。
#define SIGCONT 18 // 让一个停止的进程继续执行
#define SIGSTOP 19 // 停止进程,本信号不能被阻塞,处理或忽略
#define SIGTSTP 20 // 停止进程,但该信号可以被处理和忽略
#define SIGTTIN 21 // 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号
#define SIGTTOU 22 // 类似于SIGTTIN, 但在写终端时收到
#define SIGURG 23 // 有紧急数据或out-of-band数据到达socket时产生
#define SIGXCPU 24 // 超过CPU时间资源限制时发出
#define SIGXFSZ 25 // 当进程企图扩大文件以至于超过文件大小资源限制
#define SIGVTALRM 26 // 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
#define SIGPROF 27 // 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间
#define SIGWINCH 28 // 窗口大小改变时发出
#define SIGIO 29 // 文件描述符准备就绪, 可以开始进行输入/输出操作
#define SIGPOLL SIGIO // 同上,别称
#define SIGPWR 30 // 电源异常
#define SIGSYS 31 // 非法的系统调用