Android怎样捕获应用的crash信息
转载请注明出处: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信息保存起来,必要的时候还能够上传到我们的server,这样就能够非常方便的收集用户的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就完毕了。
Android怎样捕获应用的crash信息的更多相关文章
- android 之 Crash信息的持久化处理
		
需求: 持久化运行时异常的信息 1.CrashHandler.java import android.content.Context; import android.content.pm.Packag ...
 - 保留全部Android crash信息
		
保留全部Android crash信息 framework/base/core/java/com/android/internal/os/RuntimeInit.java 又一次以下这个函数,增加自己 ...
 - Java & Android未捕获异常处理机制
		
一.背景 无论是Java还是Android项目,往往都会用到多线程.不管是主线程还是子线程,在运行过程中,都有可能出现未捕获异常.未捕获异常中含有详细的异常信息堆栈,可以很方便的去帮助我们排查问题. ...
 - 使用CrashHandler获取应用crash信息
		
Android应用不可避免会发生crash,也称之为崩溃.发生原因可能是由于Android系统底层的bug,也可能是由于不充分的机型适配或者是糟糕的网络情况.当crash发生时,系统会kill掉正 ...
 - Xamarin.Android 使用百度地图获取定位信息
		
最近做一个项目,web端使用百度地图,PDA使用手持机自带的GPS定位系统获取经纬度,然后再百度地图上显示该经纬度会有一定距离的差异,这里就像可乐的瓶子拧上雪碧的盖子,能拧的上却不美观.所以为了数据的 ...
 - 【转】android 安卓APP获取手机设备信息和手机号码的代码示例
		
http://blog.csdn.net/changemyself/article/details/7421476 下面我从安卓开发的角度,简单写一下如何获取手机设备信息和手机号码 准备条件:一部安卓 ...
 - Android 向系统添加一个联系人信息contact
		
private void writeContacts() { Uri rawContacts = Uri.parse("content://com.android.contacts/raw_ ...
 - Android的5样的调试信息
		
Android的5样的调试信息 华清2014-10-23 北京海淀区 张俊浩 verbose:只是滤全部的信息. 啰嗦的意思. debug:debug调试的意思. info:一般提示的信息inf ...
 - Android Text Color设置不当造成信息不显示
		
Android Text Color设置不当造成信息不显示 Android 的TextView 可以设置颜色,默认我们可以设置成 #000000 ,但某一次设置成了#00000000 ,就是多了两个0 ...
 
随机推荐
- 洛谷 P1506 拯救oibh总部
			
P1506 拯救oibh总部 题目背景 oibh总部突然被水淹没了!现在需要你的救援…… 题目描述 oibh被突来的洪水淹没了>.<还好oibh总部有在某些重要的地方起一些围墙,用*号表示 ...
 - 32款iOS开发插件和工具介绍[效率]
			
插件和工具介绍内容均收集于网络,太多了就不一一注明了,在此谢过! 1.Charles 为了调试与server端的网络通讯协议.经常须要截取网络封包来分析. Charles通过将自己设置成系统的网络 ...
 - 50行python代码实现个代理server(你懂的)
			
之前遇到一个场景是这种: 我在自己的电脑上须要用mongodb图形client,可是mongodb的server地址没有对外网开放,仅仅能通过先登录主机A,然后再从A连接mongodbserverB. ...
 - worktools-源码下拉问题
			
今天下拉源码的时候,出现了一个问题,就是当地的内容跟仓库的内容冲突,导致merge冲突.这时候CC指令不能用.然后希望通过checkout到其他分支,然后cc掉的.结果没办法切换到其他分支,一直停留在 ...
 - actionBar-shareIcon 分享按钮的修改
			
今天为了修改图库的分享按钮,进行了很多的尝试 1.寻找到了xml文件,如下 <?xml version="1.0" encoding="utf-8"?&g ...
 - 分组的listview——ExpandableListView
			
开发使用到的数据统计时可以用分组的ExpandablelistView 效果:
 - thinkphp动态注册路由
			
thinkphp动态注册路由 一.总结 1.thinkphp使用路由步骤:a.config配置文件中开启路由 b.Route类的rule方法创建路由(在Routephp中)Route::rule(' ...
 - Solr 读数据流程
			
Solr 读数据流程: 1.用户提供搜索关键词,也就是搜索语句,需要经过分词器处理以及语言处理. 2.对处理之后的关键词,搜索索引找出对应Document 即记录. 3.用户根据需要从找到的Docum ...
 - tcl   -mode
			
-exact 严格匹配(如string equal) -glob 通配符式匹配(string match) -regexp 正则表达式匹配(regexp) array get和array ...
 - 【hihocoder 1562】⼩Hi的钟表
			
[链接]点击打开链接 [题意] 在这里写题意 [题解] 时针每过1分钟转0.5°. (360/(12*60)) 分钟每过1分钟转6° (360/60); 根据这个就能算出时针和分针的角度之差了. [错 ...