android开发技巧-记录未捕获的crash异常日志

提出问题

开发过程中,在自测和交付测试的过程中会遇到发生crash但是无法捕获异常的情况。比如:一个人在厕所无聊瞎点,突然就crash了。你不可能带着线、电脑和手机在厕所瞎点的吧,所以你看不到电脑上crash的日志,毕竟是瞎点,那么crash的步骤你并不清楚。如果crash日志没有,那么就尴尬了。就像明明有个老王,但你却没有看清他的脸,是不是好气?

解决方案

既然有了老王,就需要在家里安装个摄像头,再来的时候,一定能记录他的外貌。而UncaughtExceptionHandler就是能够帮你记录老王面貌的工具。大多数人都把他封装为CrashHandler。打包发布的apk当然也可以将捕获的UncaughtExceptionHandler上传到公司的服务器分析。一般都选择了一种友盟分析的sdk集成来做,人家好的东西不花钱,拿来就用。

明确需求

需求很简单,就是告诉我问题出在哪里,即:where,当然能带上设备信息,即:what,和发生的时间 when会更好,因为你搞不清到底有几个老王来了几次?

方案执行

  • 编写相关异常捕获类CrashHandler

    通过实现UncaughtExceptionHandler,先通过Thread的getDefaultUncaughtExceptionHandler静态方法将原来系统提供的UncaughtExceptionHandler保存为mDefaultHandler,然后通过Thread静态方法
    setDefaultUncaughtExceptionHandler设置当前Tread的处理Handler为crashHandler,当Thread有异常时回调crashHandler uncaughtException方法,先将错误日志记录在存储上,然后再通过原来系统提供的mDefaultHandler抛出异常。

public class CrashHandler implements UncaughtExceptionHandler {

    /** 获取CrashHandler实例 ,单例模式 */
    public static CrashHandler getInstance() {

        if (crashHandler == null) {
            synchronized (CrashHandler.class) {
                crashHandler = new CrashHandler();
            }
        }
        return crashHandler;

    }
    // 系统默认的UncaughtException处理类实例
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler静态实例
    private static CrashHandler crashHandler;
    // 程序的Context对象
    private Context mContext;
    //时间输出格式化
    private SimpleDateFormat simpleDateFormat=new SimpleDateFormat("_yyyy_MM_dd_HH_mm_ss");

    /** 保证只有一个CrashHandler实例 */
    private CrashHandler() {

    }

    /**
     * 初始化
     * 
     * @param context
     */
    public void init(Context context) {
        mContext = context;
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        // 设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 调用Thread.setDefaultUncaughtExceptionHandler(this);后,this实现了  
     * UncaughtExceptionHandler,所以crash会进入到这里的回调。
     * 当UncaughtException发生时会转入该重写的方法来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (thread == null || ex == null || mDefaultHandler == null) {
            System.exit(0);
            return;
        }

        File savePathFile = getLogFilePath(mContext);

        if (savePathFile == null) {
            //抛出异常
            mDefaultHandler.uncaughtException(thread, ex);
            return;
        }

        String logMessage = String
                .format("CustomUncaughtExceptionHandler.uncaughtException: Thread %d Message %s",
                        thread.getId(), ex.getMessage());
        PrintWriter printWriter = null;

        try {
            printWriter = new PrintWriter(new FileWriter(savePathFile, true));
            logMessage = String.format("%s\r\n\r\n%s\r\n\r\nThread: 
                            %d\r\n\r\nMessage:\r\n\r\n%s\r\n\r\nStack Trace:\r\n\r\n%s",
                            geDeviceInfo(mContext), new Date(),
                            thread.getId(), ex.getMessage(),
                            Log.getStackTraceString(ex));
            printWriter.print(logMessage);
            printWriter.print("\n\n-------------------------------------------------\n\n");
        } catch (Throwable tr2) {
        //最后回抛出异常,所以这里就可以不用处理了。
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
        }
        mDefaultHandler.uncaughtException(thread, ex);
    }

    /**
     * 
     * 获取设备信息
     * 
     * @param context
     * @return String  设备信息
     */
    public String geDeviceInfo(Context ctx)
    {
        StringBuffer deviceInfo = new StringBuffer();

        deviceInfo.append(android.os.Build.MODEL).append("/");

        deviceInfo.append(android.os.Build.VERSION.SDK).append("/");

        deviceInfo.append(getVersionName(ctx));

        return deviceInfo.toString();
    }

    /**
     * 
     * 获取应用版本号
     * 
     * @param context
     * @return String  应用版本号
     */
    public String getVersionName(Context context) {
        String version = "";
        if(context == null) {
            return version;
        }
        try {
            // 获取packagemanager的实例
            PackageManager packageManager = context.getPackageManager();
            // getPackageName()是你当前类的包名,0代表是获取版本信息
            if(packageManager != null) {
                PackageInfo packInfo;
                packInfo = packageManager.getPackageInfo(context.getPackageName(),
                        0);
                if(packInfo != null) {
                    version = packInfo.versionName;
                }
            }
        } catch (Exception e) {
        }
        return version;
    }

    /**
     * 
     * 获取日志保存路径
     * 
     * @param context
     * @return String  日志保存路径
     */
    public File getLogFilePath(Context ctx) {
            String sdStatus = Environment.getExternalStorageState();
            if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) {
                return null;
            }

            String pathName = Environment.getExternalStorageDirectory().getPath()
                    + "/Android/data/" + ctx.getPackageName()
                    + "/files/error/"
                    + android.os.Build.MODEL + "_"
                    + getVersionName(ctx) + simpleDateFormat.format(new Date()) +".log";

            File path = new File(pathName);
            if (!path.getParentFile().exists()) {
                path.getParentFile().mkdirs();
            }

            return path;
        }

}
  • 初始化CrashHandler

public class MyApplication extends Application{

    @Override
    public void onCreate() {

        //如果是调试的apk,就初始化
        if(BuildConfig.DEBUG){
         CrashHandler.getInstance().init(this);
        }

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值