如何全面Catch你的Crash

字数 610阅读 146

针对于Android开发的小伙伴来说,对于Android Monitor的logcat再熟悉不过了,在这里我们可以查看到项目中的一些log信息,检测开发中出现的一些crash问题。不过由于种种原因,有的时候Android Monitor会闪屏或者crash信息丢失,比较不可控,不过倘若能将crash文件都存到手机上的我们自己创建的文件中岂不是更方便省事呢。

那么我们一起来研究如何创建一个CrashHander来解决

  • 1、第一步
    由于安全起见,我们需要catch所有类型的问题,那么也就需要实现Thread.UncaughtExceptionHandler接口,首先初始化 UncaughtExceptionHandler, 并设置该 CrashHander为程序的默认处理器
  public void init(Context context) {
        mContext = context;
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        // 设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
  • 2、 第二步
    作为程序员来说,我们在工作学习中的惯性思维是首先catch掉异常,所以接下来,我们重写uncaughtException方法来处理当UncaughtException发生时的异常情况(这里的mDefaultHandler是初始化时的Thread.UncaughtExceptionHandler)。
@Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            // 如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(2500);
            } catch (InterruptedException e) {
                Logger.getLogger("error : " + e);
            }
            // 退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }
  • 3、第三步
    之所以方便就是因为我们可以自定义写入crash的模式、crash的处理方式以及如何收集crash信息,发送crash报告(返回是否处理了该异常信息)。
private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        ex.printStackTrace();
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                 //反馈给用户发生crash了
                Toast.makeText(mContext, R.string.text_error_crash, Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        //配置信息,如果在debug模式下
        if (Config.DEBUG) {
            // 收集设备参数信息
            collectDeviceInfo(mContext);
            // 保存日志文件
            saveCrashInfo2File(ex);
        }
        //线上的crash的话我们可以借助友盟,将crash上报友盟
        PrintsUMengUtil.reportError(mContext, ex);
        return true;
    }
  • 4、第四步
    收集设备参数信息,这里包括收集versionName和versionCode到file里
    public void collectDeviceInfo(Context ctx) {
        try {
            //这里的PackageManager,PackageInfo是Android API 24中提供的
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null" : pi.versionName;
                String versionCode = pi.versionCode + "";
                infos.put("versionName", versionName);
                infos.put("versionCode", versionCode);
            }
        } catch (PackageManager.NameNotFoundException e) {
            Logger.getLogger(TAG, "an error occured when collect package info");
        }
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
            } catch (Exception e) {
                Logger.getLogger(TAG, "an error occured when collect package info");
            }
        }
    }
  • 5、第五步
    保存错误信息到文件中,这里返回的是文件名称,便于将文件传送到服务器上。
private String saveCrashInfo2File(Throwable ex) {

        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key).append("=").append(value).append("\n");
        }

        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();
        sb.append(result);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatter.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".log";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = "/sdcard/crash_news/";
                File dir = new File(path);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(path + fileName);

                sb.append("\r\n报错日期:");
                sb.append(new Date(System.currentTimeMillis()).toLocaleString()).append("\r\n");
                printStackTrace(sb, ex);

                fos.write(sb.toString().getBytes());
                fos.close();
            }
            return fileName;
        } catch (Exception e) {
            Logger.getLogger(TAG, "an error occured while writing file...");
        }
        return null;
    }
    public void printStackTrace(StringBuffer sb, Throwable ex) {
        if (sb == null) sb = new StringBuffer();
        StackTraceElement[] trace = ex.getStackTrace();
        synchronized (sb) {
            sb.append(ex.toString() + "\r\n");
            for (int i = 0; i < trace.length; i++) {
                sb.append(" at " + trace[i] + "\r\n");
            }
            Throwable ourCause = ex.getCause();
            if (ourCause != null)
                printStackTraceAsCause(sb, ourCause, trace);
        }
    }
   private void printStackTraceAsCause(StringBuffer sb, Throwable ex, StackTraceElement[]
            causedTrace) {
        // assert Thread.holdsLock(s);
        // Compute number of frames in common between this and caused
        if (sb == null) sb = new StringBuffer();
        StackTraceElement[] trace = ex.getStackTrace();
        int m = trace.length - 1, n = causedTrace.length - 1;
        while (m >= 0 && n >= 0 && trace[m].equals(causedTrace[n])) {
            m--;
            n--;
        }
        int framesInCommon = trace.length - 1 - m;
        sb.append("Caused by: ");
        sb.append(ex + "\r\n");
        for (int i = 0; i <= m; i++) {
            sb.append(" at " + trace[i] + "\r\n");
        }
        if (framesInCommon != 0) {
            sb.append(" ..." + framesInCommon + " more \r\n");
        }

        // Recurse if we have a cause
        Throwable ourCause = ex.getCause();
        if (ourCause != null)
            printStackTraceAsCause(sb, ourCause, trace);
    }
  • 6、第6步
    网络请求数据时对onResponse函数中的,对于异常code的存储。
   public void saveLogInfo2File(String s) {

        if (TextUtils.isEmpty(s)) {
            return;
        }

        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }
        sb.append(s);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatter.format(new Date());
            String fileName = "log-" + time + "-" + timestamp + ".log";
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String path = "/sdcard/crash_news/";
                File dir = new File(path);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                FileOutputStream fos = new FileOutputStream(path + fileName);

                sb.append("\r\nLog日期:");
                sb.append(new Date(System.currentTimeMillis()).toLocaleString() + "\r\n");

                fos.write(sb.toString().getBytes());
                fos.close();
            }
        } catch (Exception e) {
            Logger.getLogger(TAG, "an error occured while writing file..." + e);
        }
    }
  • 7、第七步
    对Crash进行初始化和调用。此处是在BaseApplication中通过initCrashHandler方法进行初始化。
 private void initCrashHandler() {
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(getApplicationContext());
    }
  • 8、第八步
    在手机上下载并安装ES文件浏览器,由于这里自定义的crash文件的存储路径为/sdcard/crash_news/,文件名定义为crash_news了,当然了,这些都可以自行定义。


    crash_news.png

    打开crash文件就能看见捕获的crash文件,非常方便,like this:


    crash.jpg

    虽然我们比较不愿意看到crash ,但是面对crash我们还是不能放过任何一个,这样做如此一来便可以更加高效便捷的处理问题。

推荐阅读更多精彩内容