CrashHandler

崩溃日志显示Toast并输出至SD Card - 适配Android10

1、工具类

异常捕获Handler

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private Context mContext;

    // 用来存储应用信息和设备信息
    private Map<String, String> mInfo = new LinkedHashMap<>();

    // 默认的未捕获异常处理器
    private Thread.UncaughtExceptionHandler mDefaultHandler;

    /**
     * instance       实例
     * INSTANCE_LOCK  互斥锁
     */
    private static CrashHandler instance = null;
    private static final Object INSTANCE_LOCK = new Object();

    /**
     * 获取实例
     *
     * @return
     */
    public static CrashHandler getInstance() {
        if (instance == null) {
            synchronized (INSTANCE_LOCK) {
                if (instance == null) {
                    instance = new CrashHandler();
                }
            }
        }
        return instance;
    }

    /**
     * 初始化
     *
     * @param context
     */
    public void init(Context context) {
        mContext = context.getApplicationContext();
        // 获取默认的未捕获异常处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        // 设置CrashHandler为默认的未捕获异常处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }


    /**
     * 捕获异常
     *
     * @param t
     * @param ex
     */
    @Override
    public void uncaughtException(@NonNull Thread t, @NonNull Throwable ex) {
        // 处理异常
        handleException(ex);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 结束应用
        if (mDefaultHandler != null) {
            mDefaultHandler.uncaughtException(t, ex);
        } else {
            System.exit(1);
        }
    }

    /**
     * 处理异常 - 输出 Toast 及 输出到 sd card
     *
     * @param ex
     */
    private void handleException(Throwable ex) {
        final Throwable e = ex;
        // Toast提示出现异常
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        // 收集应用信息和设备信息
        collectInfo();
        // 保存崩溃信息到SD卡
        AndroidUtils.saveErrInfoToGalleryByScopedStorage(mContext, outputErrorLogInfo(ex));
    }


    /**
     * 错误日志
     *
     * @param ex
     * @return
     */
    private String outputErrorLogInfo(Throwable ex) {
        String deviceInfo = "";
        for (String key : mInfo.keySet()) {
            deviceInfo += key + "  : " + mInfo.get(key) + "\n";
        }
        return "Error Info : \n" + ex.getLocalizedMessage() + "\n\n" + "Device Info : \n" + deviceInfo;
    }


    /**
     * 收集信息
     */
    private void collectInfo() {
        // 收集BuildConfig类信息
//        collectClassInfo(BuildConfig.class);
        // 收集Build.VERSION类信息
        collectClassInfo(Build.VERSION.class);
        // 收集Build类信息
        collectClassInfo(Build.class);
    }

    /**
     * 收集信息
     *
     * @param cls
     */
    private void collectClassInfo(Class cls) {
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                mInfo.put(field.getName(), field.get(null).toString());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

崩溃日志输出至sd card

public class AndroidUtils {

    public static final String TAG = "AndroidUtils";


    /**
     * ERROR_LOG_FILE_PATH_1 --- Android 10 及以上错误日志存储路径
     * ERROR_LOG_FILE_PATH_2 --- Android 10 以下错误日志存储路径
     * ERROR_LOG_FILE_NAME   --- 错误日志文件名
     */
    public static final String ERROR_LOG_FILE_PATH_1 = "Download/UAT Error Logs";
    public static final String ERROR_LOG_FILE_PATH_2 = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "UAT Error Logs";
    public static final String ERROR_LOG_FILE_NAME = "errLog";

    /**
     * 错误日志输出到 sdk card
     * <p>
     * Android 10 及以上日志路径 --- /storage/sdcard/Download/UAT Error Logs
     * Android 10 以下日志路径   --- /storage/emulated/0/UAT Error Logs
     *
     * @param context
     * @param logContent
     * @return
     */
    public static boolean saveErrInfoToGalleryByScopedStorage(Context context, String logContent) {

        long timestamp = System.currentTimeMillis();
        String time = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(timestamp));
        String fileName = ERROR_LOG_FILE_NAME + "_" + time + ".txt";

        FileOutputStream fos = null;
        OutputStream os = null;
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                //Android 10 及以上
                ContentValues values = new ContentValues();
                values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
                //设置文件类型
                values.put(MediaStore.Downloads.MIME_TYPE, "text/plain");
                //这个方法只可在Android10的手机上执行,设置路径
                values.put(MediaStore.Downloads.RELATIVE_PATH, ERROR_LOG_FILE_PATH_1);
                Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
                // 写入文件
                Uri uri = context.getContentResolver().insert(external, values);
                if (uri != null) {
                    os = context.getContentResolver().openOutputStream(uri);
                    os.write(logContent.getBytes());
                    os.flush();
                    return true;
                }
            } else {
                //Android 10 以下
                File dir = new File(ERROR_LOG_FILE_PATH_2);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                File file = new File(dir, fileName);
                if (!file.exists()) {
                    file.createNewFile();
                }
                fos = new FileOutputStream(file);
                fos.write(logContent.getBytes());
                fos.flush();
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, Log.getStackTraceString(e));
        } finally {
            closeIO(fos);
            closeIO(os);
        }
        return false;
    }

    /**
     * 关闭流,普通不严重的可采用此函数关闭
     */
    public static void closeIO(Closeable... closeables) {
        if (null == closeables || closeables.length <= 0) {
            return;
        }
        for (Closeable cb : closeables) {
            try {
                if (null == cb) {
                    continue;
                }
                cb.close();
            } catch (Exception e) {
                Log.e(TAG, Log.getStackTraceString(e));
            }
        }
    }
}

2、使用

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //崩溃日志
        CrashHandler.getInstance().init(this);
    }
}

程序崩溃闪退之后,在文件管理器中搜索UAT Error Logs文件夹
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
android.os.strictmode.DiskReadViolation at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1596) at libcore.io.BlockGuardOs.open(BlockGuardOs.java:249) at libcore.io.ForwardingOs.open(ForwardingOs.java:166) at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7844) at libcore.io.MemoryMappedFile.mmapRO(MemoryMappedFile.java:54) at libcore.timezone.ZoneInfoDb.loadData(ZoneInfoDb.java:185) at libcore.timezone.ZoneInfoDb.loadTzDataWithFallback(ZoneInfoDb.java:119) at libcore.timezone.ZoneInfoDb.<clinit>(ZoneInfoDb.java:46) at java.util.TimeZone.getTimeZone(TimeZone.java:579) at java.util.TimeZone.getDefaultRef(TimeZone.java:724) at java.util.TimeZone.getDefault(TimeZone.java:700) at java.text.SimpleDateFormat.initializeCalendar(SimpleDateFormat.java:743) at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:687) at java.text.SimpleDateFormat.<init>(SimpleDateFormat.java:666) at com.hryt.remoter.uimce.app.CrashHandler.<init>(CrashHandler.java:53) at com.hryt.remoter.uimce.app.CrashHandler.<clinit>(CrashHandler.java:49) at com.hryt.remoter.uimce.app.CrashHandler.getInstance(CrashHandler.java:65) at com.hryt.remoter.uimce.app.RemoterApplication.onCreate(RemoterApplication.java:32) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1192) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7013) at android.app.ActivityThread.access$1300(ActivityThread.java:238) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1921) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7958) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
06-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KillerNoBlood

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值