转载请注明出处:http://blog.csdn.net/fishle123/article/details/50823358
我们的应用不可避免的会发生crash,如果是在调试阶段,我们可以使用Logcat查看异常信息。但是如果应用发布之后呢?如果在用户那边crash了,如果我们可以捕获这些crash信息,那么对我们定位crash原因并修复问题是很有帮助的。应用crash即可能是Java层的异常导致的,也可能是native层导致,下面分别来看一下该如何处理。
1 Java层的未捕获异常处理
先来看一下Java层的crash信息收集吧。要想捕获Java层的crash信息并不难,Android已经提供了接口来帮助我们监控系统的未捕获的异常:使用Thread.setDefaultUncaughtExceptionHandler就可以让我们轻松的监控应用的任何意外crash。
首先来看一下Thread.setDefaultUncaughtExceptionHandler这个方法:
/**
* Sets the default uncaught exception handler. This handler is invoked in
* case any Thread dies due to an unhandled exception.
*
* @param handler
* The handler to set or null.
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
Thread.defaultUncaughtHandler = handler;
}
从Thread.setDefaultUncaughtExceptionHandler这个方法的注释就可以看到:当进程内(因为Thread.defaultUncaughtHandler 是一个静态变量,因此对整个进程内的所有线程有效)的任何线程发生未捕获异常时,会调用这里设置的handler。那我们看一下UncaughtExceptionHandler 这个类吧:
/**
* Implemented by objects that want to handle cases where a thread is being
* terminated by an uncaught exception. Upon such termination, the handler
* is notified of the terminating thread and causal exception. If there is
* no explicit handler set then the thread's group is the default handler.
*/
public static interface UncaughtExceptionHandler {
/**
* The thread is being terminated by an uncaught exception. Further
* exceptions thrown in this method are prevent the remainder of the
* method from executing, but are otherwise ignored.
*
* @param thread the thread that has an uncaught exception
* @param ex the exception that was thrown
*/
void uncaughtException(Thread thread, Throwable ex);
}
从源码可以看出,UncaughtExceptionHandler 其实是一个接口,它只定义了一个方法uncaughtException(Thread thread, Throwable ex),当线程因为遇到未捕获异常而终止的时候就会调用这个方法。
如果我们想要捕获应用的crash信息,那么定义一个自己的UncaughtExceptionHandler 就可以,当然我们需要在自己的UncaughtExceptionHandler 里面把crash信息保存起来,必要的时候还可以上传到我们的服务器,这样就可以很方便的收集用户的crash信息。
2 native层的异常处理
如果我们的应用使用到c/c++,那么也需要收集native层的异常处理。大家都知道,Android的底层是基于Linux的,那么native层的未捕获异常就可以通过捕获信号来处理了。Native层如果异常终止会发出SIGKILL信号,我们可以使用sigaaction来注册一个信号处理函数来处理SIGKILL信号,这样就可以收集到native层的未捕获异常了。这里给出一个大概的代码框架:
void sigkill_handler(int signo){
//打印堆栈,并写入到文件中
}
void install(){
struct sigaction act, oldact;
act.sa_handler = sigkill_handler;
sigaddset(&act.sa_mask, SIGKILL);
sigaction(SIGKILL, &act, &oldact);//注册信号处理函数
......
}
3 实现
结合上面的介绍,下面就来定义一个自己的UncaughtExceptionHandler ,这个例子只处理了Java层的crash收集,并把收集到的crash信息保存到sd卡上。这里给我们自定义的crash处理器起了一个名字叫做AppCR(Application Crash Response)。
首先定义ErrorReporter ,它实现了UncaughtExceptionHandler :
public class ErrorReporter implements UncaughtExceptionHandler {
private final Application mContext;
private final ReporterExecutor mReporterExecutor;
ErrorReporter(Application context, boolean enabled) {
mContext = context;
final Thread.UncaughtExceptionHandler defaultExceptionHandler = Thread
.getDefaultUncaughtExceptionHandler();
mReporterExecutor = new ReporterExecutor(context, defaultExceptionHandler);
mReporterExecutor.setEnabled(enabled);
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(final Thread thread,final Throwable ex) {
// TODO Auto-generated method stub
LogUtil.i(AppCR.LOG_TAG,"catch uncaughtException");
mReporterExecutor.execute(thread, ex);
}
public void setEnabled(boolean enabled) {
LogUtil.i(AppCR.LOG_TAG, "AppCR is" + (enabled ? "enabled" : "disabled") + " for "
+ mContext.getPackageName());
}
}
ReporterExecutor会调用Thread.setDefaultUncaughtExceptionHandler(this);来修改默认的UncaughtExceptionHandler。当发生未捕获的异常时,调用mReporterExecutor.execute(thread, ex);来处理异常。
ReporterExecutor 中把异常信息以及操作系统的相关信息保存到文件中。
public class ReporterExecutor {
public static final String TAG = ReporterExecutor.class.getSimpleName();
private Context mContext;
private boolean mEnabled = false;
private final Thread.UncaughtExceptionHandler mDefaultExceptionHandler;
private File mCrashInfoFile;
public ReporterExecutor(Context context,
Thread.UncaughtExceptionHandler defaultedExceptionHandler) {
mContext = context;
mDefaultExceptionHandler = defaultedExceptionHandler;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
File path = Environment.getExternalStorageDirectory();
File dir = new File(path, "BleFairy");
if (!dir.exists()) {
dir.mkdirs();
}
mCrashInfoFile = new File(dir, getCrashFileName());
if (!mCrashInfoFile.exists()) {
try {
mCrashInfoFile.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public boolean isEnabled() {
return mEnabled;
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
public void execute(Thread thread, Throwable ex) {
if (!mEnabled) {
endApplication(thread, ex);
return;
}
// log crash info to file
Log.w(AppCR.LOG_TAG, "getSysInfo.");
CrashReportData data = CrashReportData.produce(thread, ex, mContext);
data.writeToFile(mCrashInfoFile);
endApplication(thread, ex);
}
private void endApplication(Thread thread, Throwable ex) {
if (mDefaultExceptionHandler != null) {
Log.w(AppCR.LOG_TAG, "execute default uncaughtException handler.");
mDefaultExceptionHandler.uncaughtException(thread, ex);
} else {
Log.w(AppCR.LOG_TAG, "kill process and exit.");
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
private String getCrashFileName() {
StringBuilder ret = new StringBuilder();
Calendar calendar = Calendar.getInstance();
ret.append("crash_");
ret.append(calendar.get(Calendar.YEAR));
int month = calendar.get(Calendar.MONTH)+1;
int date = calendar.get(Calendar.DATE);
if(month < 10 ){
ret.append("0");
}
ret.append(month);
if(date<10){
ret.append("0");
}
ret.append(date);
ret.append(".txt");
return ret.toString();
}
}
CrashReportData 类用于保存异常信息:
public class CrashReportData {
private final String info;
private CrashReportData(String crashInfo) {
this.info = crashInfo;
}
public static CrashReportData produce(Thread thread, Throwable ex, Context context) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream print = new PrintStream(out);
out.toString();
print.append("crahtime:" + TimeUtil.getCurTimeString()).append("\n");
print.append(SysInfo.getSysInfo(context)).append("\n");
print.append(thread.getName()).append("(threadID=" + thread.getId() + ")").append("\n");
print.append(ex.getMessage()).append("\n");
ex.printStackTrace(print);
return new CrashReportData(out.toString());
}
public void writeToFile(File file) {
PrintWriter printer = null;
try {
// append to the end of crash file
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file, true));
printer = new PrintWriter(out);
printer.println(info);
printer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (printer != null) {
printer.close();
}
LogUtil.w(AppCR.LOG_TAG, "write exception info to file over.");
}
}
@Override
public String toString() {
// TODO Auto-generated method stub
return info;
// return super.toString();
}
}
SysIno类:
public class SysInfo {
public static String getSysInfo(Context context) {
StringBuilder info = new StringBuilder();
info.append("osVersion=Android ").append(Build.VERSION.RELEASE).append("\n");
info.append("model=").append(Build.MODEL).append("\n");
info.append("brand=").append(Build.BRAND).append("\n");
LogUtil.i(AppCR.LOG_TAG, "sys info collect over.");
return info.toString();
}
}
使用AppCR来安装我们的crash处理器:
public class AppCR {
public static final String LOG_TAG=AppCR.class.getSimpleName();
private static ErrorReporter mErrorReporter;
public static void init(Application application){
init(application,true);
}
public static void init(Application application,boolean enabled){
mErrorReporter = new ErrorReporter(application, enabled);
}
}
Application中安装上面自定义的AppCR就可以了:
public class BeaconApplication extends Application {
private final String TAG = "BeaconFairy.BeaconApplication";
@Override
public void onCreate() {
super.onCreate();
AppCR.init(this,true);
}
}
需要注意的是:我们需要定义自己的Application,然后修改manifest就可以啦,还要记得加上写SD卡的权限:
<application
android:name=".BeaconApplication"
android:allowBackup="true"
android:allowTaskReparenting="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
........
</application>
申请写SD卡的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
到此为止,我们自定义的crash信息收集程序AppCR就完成了。