说说烦人的Crash

/ 1评 / 0

前言

在我们日常的开发中,Crash总是无法避免的,也许是由于糟糕的网络,也许是代码的逻辑bug等等,总之发生了Crash就要修复,可是当我们的Crash发生在客户的手上,那么Crash信息就不是那么好获取了,还有就是在开发过程中,昙花一现的bug,可是这时ADB把log吃了,那就悲催了,所以写了一个工具类,用来自动保存/上传Crash信息。

UncaughtExceptionHandler

在java中Thread有一个静态方法Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler)用来设置没有被程序处理的异常,一般情况下,默认的处理就是系统杀死程序,也就是Crash,我们所要做的就是在系统杀死我们程序之前保存异常堆栈信息到本地,然后下一次启动的时候进行上传处理。

read this fucking source code

原理已经说了,然后接下来看看实现。首先将CrashHandler实现UncaughtExceptionHandler接口,覆写其uncaughtException方法,在init()方法中将线程默认的异常处理设置为自己,这样就可以处理未捕获的异常了。然后在程序结束之前保存异常堆栈信息。调用checkCrash()方法会自动检查是否上一次关闭有Crash信息,如果有则自动调用uploadCrashInfo()方法上传,默认为空实现,你可以继承CrashHandler,并重写此方法即可。

/**
 * 将未处理的异常自动保存在/data/data/package_name/cache/crash 下面
 */
@SuppressLint("SimpleDateFormat")
public class CrashHandler implements UncaughtExceptionHandler {

    private static String TAG = "CrashHandler";

    private static CrashHandler mInstance = new CrashHandler();

    private Context mContext = null;

    // 格式化日期,用于错误日志保存名
    private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

    private UncaughtExceptionHandler mDefaultExceptionHandler = null;

    private CrashHandler() {

    }

    public static CrashHandler getInstance() {
        return mInstance;
    }

    public CrashHandler init(Context context) {
        mContext = context;
        mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        return this;
    }

    /**
     * 检查是否存在Crash日志,如果存在,则调用uploadCrashInfo上传日志信息
     */
    public void checkCrash() {
        try {
            String crashPath = getCrashPath(mContext);
            File crashFile = new File(crashPath);
            final File[] listFiles = crashFile.listFiles();
            if (listFiles != null && listFiles.length > 0) {
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                        for (File file : listFiles) {
                            // Crash目录下面只有Crash文件,如果有文件夹则不管
                            if (file.isFile()) {
                                boolean upload = uploadCrashInfo(file.getAbsolutePath());
                                if (upload) {
                                    file.delete();
                                }
                            }
                        }
                    }
                }).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 上传Crash信息,此函数在非UI线程中被调用,所以不用开启线程
     * 
     * @param crashPath Crash信息保存路径
     * @return true 表示处理了Crash信息,会自动清空Crash目录,false,表示没有处理,会继续保存
     */
    protected boolean uploadCrashInfo(String crashPath) {
        // 在这里进行上传操作,一个Crash文件就会调用一次
        return false;
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        handleException(ex);
        if (mDefaultExceptionHandler != null) {
            mDefaultExceptionHandler.uncaughtException(thread, ex);
        } else {
            // 退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }

    /**
     * 自定义错误处理,收集错误信息
     * 
     * @param ex
     */
    private void handleException(Throwable ex) {
        if (ex == null) {
            return;
        }
        String carshTrace = getStackTraceString(ex);
        saveCrashInfo2File(carshTrace);
    }

    /**
     * 获取异常的堆栈信息
     */
    private String getStackTraceString(Throwable tr) {
        if (tr == null) {
            return "";
        }
        // This is to reduce the amount of log spew that apps do in the non-error
        // condition of the network being unavailable.
        Throwable t = tr;
        while (t != null) {
            if (t instanceof UnknownHostException) {
                return "";
            }
            t = t.getCause();
        }

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        tr.printStackTrace(pw);
        pw.flush();
        return sw.toString();
    }

    /**
     * 保存错误信息到文件中
     */
    private void saveCrashInfo2File(String crashInfo) {
        if (null == crashInfo)
            return;
        try {
            long timestamp = System.currentTimeMillis();
            String time = mDateFormat.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".log";
            String path = getCrashPath(mContext);
            File crashFile = new File(path);
            if (!crashFile.exists()) {
                crashFile.mkdirs();
            }
            if (null != path) {
                FileOutputStream fos = new FileOutputStream(crashFile.getAbsolutePath() + File.separator + fileName);
                fos.write(crashInfo.getBytes("utf-8"));
                fos.close();
            }
        } catch (Exception e) {
            Log.e(TAG, "an error occured while writing file...");
        }
    }

    /**
     * 获取Crash信息的保存路径,可以覆写此函数进行自定义位置
     * 
     * @return default path /data/data/package_name/cache/crash/
     */
    protected String getCrashPath(Context context) {
        File cacheDir = context.getCacheDir();
        return cacheDir.getAbsolutePath() + File.separatorChar + "crash";
    }
}

然后调用这段代码也是so easy的,一般来说,这种初始化是放在Application中的,所以我们继承Application,然后在其onCreate()方法中调用下面一句代码即可。

// 拦截所有异常,checkCrash会检查上次是否有未处理Crash信息,如果有则调用上传
CrashHandler.getInstance().init(this).checkCrash();

最后需要在AndroidManifest.xml文件中配置application的 android:name属性为自定义的Application。这样当异常发生的时候,证据就被留下来了。

  1. Leridy_Lei说道:

    高产博主

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注