学习笔记之Android利用UncaughtExceptionHandler捕获全局异常

来源:互联网 发布:中国轻工业出版社 知乎 编辑:程序博客网 时间:2024/06/04 20:58

一、概述

现如今,Android手机越发的普及,手机的品牌与型号五花八门、Android版本的不同,在开发过程中运行良好的app到了发布上线后安装到某款手机上说不定就出现异常崩溃的现象,开发者不可能在所有的设备逐个调试,所以在apk发布上线后,如果出现了崩溃现象,应及时捕获该设备导致崩溃的日志信息,这对于版本维护bug修复帮助极大,那么如何在app出现crash的情况下捕获设备的参数和较详细的异常信息,并将其上传到服务器供开发人员分析与修复。

二、UncaughtExceptionHandler

通常会导致程序崩溃的异常,这些异常不能被捕获到,利用Thread.UncaughtExceptionHandler就可以捕获到这些异常。从名字就可以看出来UncaughtExceptionHandler是针对某个线程而言的,由于在Android编程中,大量使用线程,如果统一处理呢?因为主线程只有一个,我们可以在主线程作处理。

三、原理

既然知道利用UncaughtExceptionHandler类来实现捕获全局异常,那么在Android SDK中进入UncaughtExceptionHandler.class里我们只需要关注以下函数


很显然,在初始化CrashHandler时:

1.获取系统默认的UncaughtException处理器(getDefaultUncaughtExceptionHandler())

2.设置该CrashHandler为程序的默认处理器(setDefaultUncaughtExceptionHandler())

3.当发生Crash时会转入UncaughtException函数来处理相应的业务代码

四、实现过程

关键代码CrashHandler类

public class CrashHandler implements UncaughtExceptionHandler {    public static final String TAG = "CrashHandler";    //系统默认的UncaughtException处理类    private UncaughtExceptionHandler mDefaultHandler;    //CrashHandler实例    private static CrashHandler INSTANCE = new CrashHandler();    //程序的Context对象    private Context mContext;    //用于格式化日期,作为日志文件名的一部分    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");    /**     * 保证只有一个CrashHandler实例     */    private CrashHandler() {    }    /**     * 获取CrashHandler实例 ,单例模式     */    public static CrashHandler getInstance() {        return INSTANCE;    }    /**     * 初始化     *     * @param context     */    public void init(Context context) {        mContext = context;        //获取系统默认的UncaughtException处理器        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();        //设置该CrashHandler为程序的默认处理器        Thread.setDefaultUncaughtExceptionHandler(this);    }    /**     * 当UncaughtException发生时会转入该函数来处理     */    @Override    public void uncaughtException(Thread thread, Throwable ex) {        if (!handleException(ex) && mDefaultHandler != null) {            //如果用户没有处理则让系统默认的异常处理器来处理            mDefaultHandler.uncaughtException(thread, ex);        } else {            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                Log.e(TAG, "error : ", e);            }            //退出程序            android.os.Process.killProcess(android.os.Process.myPid());            System.exit(1);        }    }    /**     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.     *     * @param ex     * @return true:如果处理了该异常信息;否则返回false.     */    private boolean handleException(final Throwable ex) {        if (ex == null) {            return false;        }        final String strhh = saveCrashInfo2File(ex);        Log.e(TAG, strhh);        //使用Toast来显示异常信息        new Thread() {            @Override            public void run() {                Looper.prepare();                Toast.makeText(mContext, strhh, Toast.LENGTH_LONG).show();//                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();                Looper.loop();            }        }.start();        //收集设备参数信息,保存日志文件        writeFileSdcardFile(FileUtils.SDPATH, "Crash_" + System.currentTimeMillis() + ".txt",                saveCrashInfo2File(ex), ex.getMessage());        return true;    }    /**     * 收集设备信息与错误日志     *     * @param e     */    public String saveCrashInfo2File(Throwable e) {        StringBuilder sb = new StringBuilder();        sb.append("生产厂商:\n");        sb.append(Build.MANUFACTURER).append("\n\n");        sb.append("手机型号:\n");        sb.append(Build.MODEL).append("\n\n");        sb.append("系统版本:\n");        sb.append(Build.VERSION.RELEASE).append("\n\n");        sb.append("异常时间:\n");        sb.append(formatter.format(new Date())).append("\n\n");        sb.append("异常类型:\n");        sb.append(e.getClass().getName()).append("\n\n");        sb.append("异常信息:\n");        sb.append(e.getMessage()).append("\n\n");        sb.append("异常堆栈:\n");        Writer writer = new StringWriter();        PrintWriter printWriter = new PrintWriter(writer);        e.printStackTrace(printWriter);        Throwable cause = e.getCause();        while (cause != null) {            cause.printStackTrace(printWriter);            cause = cause.getCause();        }        printWriter.close();        String result = writer.toString();        sb.append(result);        return sb.toString();    }    /**     * 保存错误信息到文件中     *     * @param path     * @param fileName  文件名     * @param write_str 错误日志     * @param ex        错误信息     */    public void writeFileSdcardFile(String path, String fileName, String write_str, String ex) {        if (!FileUtils.file.exists()) {            FileUtils.CreateDir();        }        try {            FileOutputStream fout = new FileOutputStream(path + fileName);            byte[] bytes = write_str.getBytes();            fout.write(bytes);            fout.close();            Log.e(TAG, "保存成功" + path + fileName);            //此地做上传错误日志代码//            uploadLogFile(new File(path + fileName), ex);        } catch (Exception e) {            e.printStackTrace();            Log.e(TAG, "保存失败");        }    }}

根据需求非debug版本,在BaseApplication类中初始化CrashHandler

FileUtils.CreateDir();//创建错误日志文件夹        if (CrashConfig.HAVE_LOG) {            CrashHandler crashHandler = CrashHandler.getInstance();            crashHandler.init(this.getApplicationContext());        }    }
在某个Activity里人为制造crash来调试
boolean b = FileUtils.checkFilePathExists(FileUtils.SDPATH);        StringBuffer buffer = new StringBuffer();        buffer.append("是否会生成错误日志:"+(CrashConfig.HAVE_LOG+""))                .append("\n\n")                .append("当前编译模式:")                .append(BuildConfig.DEBUG ? "debug模式" : "release模式")                .append("\n\n")                .append("存放错误日志文件夹是否存在:" + b)                .append("\n\n")                .append("存放错误日志文件夹物理路径:")                .append("\n\n")                .append(FileUtils.file.getAbsolutePath());        tv.setText(buffer);        carsh.setText("点我崩溃");        carsh.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if(CrashConfig.HAVE_LOG){                    Log.e("mjb", 5 % 0 + "");//                    throw new RuntimeException("这里是异常");                }else{                    Toast.makeText(MainActivity.this,"无错误日志",Toast.LENGTH_SHORT).show();                }            }        });

五、小结

通过以上就可以捕获crash异常,这里的crash界面只是Toast,大家可根据需求优化发生crash时的友好界面以及将crash信息文件上传至后台服务器当中维护迭代等。

项目demo已经上传至GitHub 喜欢的star、fork。谢谢






1 0
原创粉丝点击