红橙Darren视频笔记 自己捕获异常并保存到本地

应用场景,我们经常遇到这样的情况,有些bug是偶现的,实际遇到的时候可能来不及获取log,那么我们可以自己写个异常捕获的类,将捕获的异常输出到文件,当然最好能上传到服务器,因为我们并不一定能拿到用户的机器。
这次我们先简单处理,将文件保存在本地。

大致思路

1.构建自己的异常捕获类
2.获取崩溃信息 手机版本号 apk版本信息 利用StringWriter StringBuilder等辅助类进行拼装
3.在内部存储创建文件 存储上面的那些信息(为避免应用产生的文件吃内存,在创建之前会判断当前是否存在crash文件夹,如果存在 删除并重新创建,这样始终只会有一个crash文件)
4.在Application初始化异常捕获类

实现过程

1.构建自己的异常捕获类

public class ExceptionCrashHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "ExceptionCrashHandler";
    private static ExceptionCrashHandler mInstance;

    // 留下原来异常处理器的引用,便于开发的时候调试
    private Thread.UncaughtExceptionHandler mDefaultHandler;

    // 上下文  用于获取版本信息和手机信息
    private WeakReference<Context>  mContextRef;

    public static ExceptionCrashHandler getInstance() {
        if (mInstance == null) {
            synchronized (ExceptionCrashHandler.class) {
                if (mInstance == null) {
                    mInstance = new ExceptionCrashHandler();
                }
            }
        }
        return mInstance;
    }

    public void init(Context context) {
        Log.e(TAG, " ExceptionCrashHandler init ");
        /*
         * 官方解释
         * Set the handler invoked when this thread abruptly terminates
         * due to an uncaught exception.
         *
         * 当前线程由于未捕获异常而终止的时候 调用当前的handler处理异常
         **/
        Thread.currentThread().setUncaughtExceptionHandler(this);
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        this.mContextRef = new WeakReference<>(context);
    }

    private ExceptionCrashHandler() {

    }

    @Override
    public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
        Log.e(TAG, "拦截到闪退信息" + e.getMessage());
        // 1. 获取信息
        // 1.1 崩溃信息
        // 1.2 手机信息
        // 1.3 版本信息
        // 2.写入文件
        String crashFileName = saveInfoToCache(e);

        Log.e(TAG, "fileName --> " + crashFileName);
        // 让系统默认处理 否则应用发生crash后没有任何反应
        mDefaultHandler.uncaughtException(t, e);
    }

    /**
     * 得到获取的 软件信息,设备信息和出错信息
     * 保存在机身内存
     */
    private String saveInfoToCache(Throwable ex) {
        String fileName = null;
        StringBuilder stringBuffer = new StringBuilder();

        //遍历手机信息 格式化后输出
        for (Map.Entry<String, String> entry : obtainSimpleInfo(mContextRef.get())
                .entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            stringBuffer.append(key).append(" = ").append(value).append("\n");
        }
        stringBuffer.append(obtainExceptionInfo(ex));

        //这里原课程有问题 为什么获取外部存储的可用状态 最后存储在内部存储 因此我注释掉了最外层的判断
        //if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) {
        // 获取crash文件夹
        File dir = new File(mContextRef.get().getFilesDir() + File.separator + "crash"
                + File.separator);

        // 先删除之前的异常信息
        if (dir.exists()) {
            deleteDir(dir);
        }

        // 再重新创建文件夹
        if (!dir.exists()) {
            boolean res = dir.mkdir();
            if (!res) {
                Log.e(TAG, "saveInfoToSD: create dir failed ");
                return "";
            }
        }
        // 创建文件
        try {
            fileName = dir.toString()
                    + File.separator
                    + getAssignTime() + ".txt";
            FileOutputStream fos = new FileOutputStream(fileName);
            fos.write(stringBuffer.toString().getBytes());
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //}
        return fileName;
    }

    /**
     * 返回当前日期根据格式
     **/
    private String getAssignTime() {
        DateFormat dataFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.US);
        long currentTime = System.currentTimeMillis();
        return dataFormat.format(currentTime);
    }

    public static void deleteDir(File file) {
        boolean res;
        if (file.isFile()) {
            res = file.delete();
            if (!res){
                Log.e(TAG, "deleteDir: failed when delete "+ file);
            }
            return;
        }
        if (file.isDirectory()) {
            File[] childFile = file.listFiles();
            if (childFile == null || childFile.length == 0) {
                res = file.delete();
                if (!res){
                    Log.e(TAG, "deleteDir: failed when delete "+ file);
                }
                return;
            }
            for (File f : childFile) {
                deleteDir(f);
            }
            res = file.delete();
            if (!res){
                Log.e(TAG, "deleteDir: failed when delete "+ file);
            }
        }
    }


    /**
     * 获取系统未捕捉的错误信息 将其转化为字符串
     */
    private String obtainExceptionInfo(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        throwable.printStackTrace(printWriter);
        printWriter.close();
        return stringWriter.toString();
    }

    /**
     * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中
     *
     */
    private HashMap<String, String> obtainSimpleInfo(Context context) {
        HashMap<String, String> map = new HashMap<>();
        PackageManager mPackageManager = context.getPackageManager();
        PackageInfo mPackageInfo = null;
        try {
            mPackageInfo = mPackageManager.getPackageInfo(
                    context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        map.put("versionName", mPackageInfo.versionName);
        map.put("versionCode", "" + mPackageInfo.versionCode);
        map.put("MODEL", "" + Build.MODEL);
        map.put("SDK_INT", "" + Build.VERSION.SDK_INT);
        map.put("PRODUCT", "" + Build.PRODUCT);
        map.put("MOBLE_INFO", getMobileInfo());
        return map;
    }

    /**
     * 获取机器信息
     */
    public static String getMobileInfo() {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            Field[] fields = Build.class.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String name = field.getName();
                String value = field.get(null).toString();
                stringBuilder.append(name).append("=").append(value).append("\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }
}

2.BaseApplication相关处理

public class BaseApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化异常处理类
        ExceptionCrashHandler.getInstance().init(this);
    }
}

在清单文件配置BaseApplication

android:name=".BaseApplication"

3.在activity中模拟异常

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv:
                int x = 2 / 0;
                Toast.makeText(this, "text view clicked" + x, Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn:
                Toast.makeText(this, "button clicked", Toast.LENGTH_SHORT).show();
                break;
        }
    }

当crash发生时会有log和文件产生:

2021-02-19 10:28:51.544 13270-13270/com.example.learneassyjoke E/ExceptionCrashHandler:  ExceptionCrashHandler init 
2021-02-19 10:28:53.324 13270-13270/com.example.learneassyjoke E/ExceptionCrashHandler: 拦截到闪退信息divide by zero
2021-02-19 10:28:53.344 13270-13270/com.example.learneassyjoke E/ExceptionCrashHandler: fileName --> /data/user/0/com.example.learneassyjoke/files/crash/2021_02_19_10_28.txt
generic_x86:/data/user/0/com.example.learneassyjoke/files # ls -l
total 4
drwx------ 2 u0_a81 u0_a81 4096 2021-02-19 10:28 crash
generic_x86:/data/user/0/com.example.learneassyjoke/files # cd crash/
generic_x86:/data/user/0/com.example.learneassyjoke/files/crash # ls
2021_02_19_10_28.txt
generic_x86:/data/user/0/com.example.learneassyjoke/files/crash # cat 2021_02_19_10_28.txt
SDK_INT = 27
MOBLE_INFO = BOARD=unknown
BOOTLOADER=unknown
BRAND=google
CPU_ABI=x86
CPU_ABI2=
DEVICE=generic_x86
DISPLAY=sdk_gphone_x86-userdebug 8.1.0 OSM1.180201.037 6739391 dev-keys
FINGERPRINT=google/sdk_gphone_x86/generic_x86:8.1.0/OSM1.180201.037/6739391:userdebug/dev-keys
HARDWARE=ranchu
HOST=abfarm-01134
ID=OSM1.180201.037
IS_CONTAINER=false
IS_DEBUGGABLE=true
IS_EMULATOR=true
IS_ENG=false
IS_TREBLE_ENABLED=true
IS_USER=false
IS_USERDEBUG=true
MANUFACTURER=Google
MODEL=Android SDK built for x86
PERMISSIONS_REVIEW_REQUIRED=false
PRODUCT=sdk_gphone_x86
RADIO=unknown
SERIAL=EMULATOR30X3X5X0
SUPPORTED_32_BIT_ABIS=[Ljava.lang.String;@f54b11
SUPPORTED_64_BIT_ABIS=[Ljava.lang.String;@5765476
SUPPORTED_ABIS=[Ljava.lang.String;@a621a77
TAG=Build
TAGS=dev-keys
TIME=1596676149000
TYPE=userdebug
UNKNOWN=unknown
USER=android-build

PRODUCT = sdk_gphone_x86
MODEL = Android SDK built for x86
versionName = 1.0
versionCode = 1
java.lang.ArithmeticException: divide by zero
        at com.example.learneassyjoke.MainActivity.onClick(MainActivity.java:68)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

这样我们可以获取异常发生的时间,异常发生的原因,异常发生的机器类型,apk版本信息。这样我们分析crash问题就方便多了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值