app保存崩溃日志

大体思路

“当一个线程突然终止,java虚拟机通过getUncaughtExceptionHandler可以得到这个线程的UncaughtExceptionHandler,并进入这个handler的uncaughtException方法。”所以我们
定义一个类实现当前线程的UncaughtExceptionHandler,并修改uncaughtException方法实现自己需要的功能。
定义一个CrashManager类实现Thread.UncaughtExceptionHandler,需要override uncaughtException:

public class CrashManager implements Thread.UncaughtExceptionHandler {

    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        CMLog.i_ui(CLASS_TAG, "uncaughtException called...");
    }
}

需要做这几步:

  1. 获取线程默认handler;
  2. 设置当前类为当前线程的异常处理handler;
  3. 修改uncaughtException,实现自己的异常处理,然后调用系统的默认处理方式。

获取系统异常处理handler,只需要下面一行代码:

mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();

设置当前实现类为线程handler:

Thread.setDefaultUncaughtExceptionHandler(this);

修改uncaughtException中:

@Override
    public void uncaughtException(Thread thread, Throwable ex) {
        CMLog.i_ui(CLASS_TAG, "uncaughtException called...");
        myHandleException();//添加自己的方法
        mDefaultHandler.uncaughtException(thread, ex);//之后继续调用系统的异常处理方法
    }

具体实现

实现当前线程的异常处理:

public class CrashManager implements Thread.UncaughtExceptionHandler {
    public final String CLASS_TAG = getClass().getSimpleName();
    // 程序的Context对象
    private Application mContext;
    // 系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;

    private CrashManager(Context context) {
        mContext = (Application) context.getApplicationContext();
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    public static void initManager(Context context){
        new CrashManager(context.getApplicationContext());
    }

    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        CMLog.i_ui(CLASS_TAG, "uncaughtException called...");
        if (!handleException(ex) && mDefaultHandler != null){
            mDefaultHandler.uncaughtException(thread, ex);
        }else {

        }
    }

    /**
     * 自定义错误处理,收集错误信息,发送错误报告等操作均在此完成
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则返回 false
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }

        // 使用 Toast 来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出。", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();

        CMLog.i_ui(CLASS_TAG, "save crash log...");
        String fileName = Utils.setLogFileName(mContext);//设置日志文件名
        String logStr = Utils.collectLogCatToString();//搜集logcat,保存为Striing
        int ret;
        ret = Utils.writeStringToAppStorage(mContext, logStr, fileName);//把String存入文件
        CMLog.i_ui(CLASS_TAG, "saveLogOnDevice ret:" + ret);
        return false;
    }
}

定义一个保存日志的开关在build.gradle中:

android {
    defaultConfig {
        buildConfigField "boolean", "HANDLE_UNCAUGHT_EXCEPTION", "true"
    }
}

在Application类中初始化:

    @Override
    public void onCreate() {
        //初始化 错误日志系统
        if (BuildConfig.HANDLE_UNCAUGHT_EXCEPTION){
            CrashManager.initManager(getApplicationContext());
        }

把工具类中三个方法代码也贴上来:

    /**
     * 设置文件名,返回imei_crashLog_时间.log
     */
    public static String setLogFileName(Context ctx){
        CMLog.i_ui(CLASS_TAG, "setLogFileName called...");
        String fileName = "";
        String imei = "";
        String time = "";

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
        time = sdf.format(new Date());

        try {
            imei = getImei(ctx);
        } catch (GetImeiException e) {
            e.printStackTrace();
        }
        fileName = imei +"_" + "crashLog_"+ time + ".log";
        return fileName;
    }

    public static String getImei(Context context) throws GetImeiException {
        if (context == null) {
            return null;
        }

        String imei;
        TelephonyManager mTelephonyMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        imei = mTelephonyMgr.getDeviceId();
        CMLog.i_ui(CLASS_TAG, "imei:" + imei);

        return imei;
    }

    /**
     * 收集logcat
     * @return
     */
    public static String collectLogCatToString() {

        ArrayList commandLine = new ArrayList();
        String[] defaultValues = new String[]{"logcat", "-d", "threadtime"};
        ArrayList logcatArgumentsList = new ArrayList(Arrays.asList(defaultValues));

        commandLine.addAll(logcatArgumentsList);
        CMLog.i_ui(CLASS_TAG, "commandLine: " + commandLine);

        String logcatBuf = "";

        try {
            final java.lang.Process e = Runtime.getRuntime().exec((String[])commandLine.toArray(new String[commandLine.size()]));
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(e.getInputStream()), 8192);

            while(true) {
                String line;
                line = bufferedReader.readLine();
                logcatBuf = logcatBuf + line + "\n";
                if(line == null) {
                    return logcatBuf;
                }
            }
        } catch (IOException var11) {
            return null;
        }
    }
    /**
     * 保存文件到app文件夹
     */
    public static int writeStringToAppStorage(Context context, String message, String fileName) {
        CMLog.i_ui(CLASS_TAG,"writeStringToInnerSDCardFile called...");

        try {
            //以Mate9为例,得到Android/data/package/files
            File file = new File(context.getExternalFilesDir(null),
                    fileName);
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(message.getBytes());
            fos.close();
            CMLog.i_ui(CLASS_TAG,"save logcat success");
            CMLog.i_ui(CLASS_TAG, "logcat is:" + file.getAbsolutePath());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return CREAT_LOGFILE_SUCC;
    }

获取imei需要申明权限:

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

由于日志路径是在应用本身包名下,不需要读写权限。

最初以为需要重新开一个进程来保存崩溃日志,后来发现没必要,exception和crash是两回事,当前进程进入uncaughtException,系统默认处理方式就是终止进程,在这之前进程是一直存在的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值