在开发中异常是难以避免的,开发过程中我们可以通过Logcat查看异常信息快速定位异常的原因进行处理,但是当app给测试或者上线后遇到一些难以重现的bug时,我们就很难找出问题所在了,所以需要捕获未捕获的异常。java中给我们提供了UncaughtExceptionHandler这个接口,实现这个接口就可以捕获未捕获的异常信息了。

捕获处理未捕获的异常

public class MyUncaughtException implements Thread.UncaughtExceptionHandler {

    @SuppressLint("StaticFieldLeak")
    private static MyUncaughtException instance;

    private Context mContext;

    /**
     * 获取单例
     *
     * @return 捕获异常类
     */
    public static MyUncaughtException getInstance() {
        if (instance == null) {
            synchronized (MyUncaughtException.class) {
                if (instance == null) {
                    instance = new MyUncaughtException();
                }
            }
        }
        return instance;
    }

    /**
     * 初始化
     *
     * @param context 上下文对象
     */
    public void init(Context context) {
        this.mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 捕获的异常
     *
     * @param thread    线程
     * @param throwable 异常
     */
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        try {
            StringWriter sw = new StringWriter();
            PrintWriter err = new PrintWriter(sw);

            Field[] fields = Build.class.getFields();
            for (Field f : fields) {
                sw.write(f.getName() + ":" + f.get(null) + "\n");// 静态属性
            }

            throwable.printStackTrace(err);
            String errorLog = sw.toString();

            @SuppressLint("SimpleDateFormat")
            SimpleDateFormat pathFormat = new SimpleDateFormat("yyyy-MM-dd");
            String datePath = pathFormat.format(new Date());

            //保存到本地
            String filePath = mContext.getExternalCacheDir() + "/Error/" + datePath + "/";
            String fileName = "log.txt";
            writeTxtToFile(errorLog, filePath, fileName);
            sw.close();
            err.close();  

 			e.printStackTrace();
          
        } catch (IllegalAccessException | IOException e) {
            e.printStackTrace();
        }

    }

    // 将字符串写入到文本文件中
    private void writeTxtToFile(String strcontent, String filePath, String fileName) {
        makeFilePath(filePath, fileName);
        String strFilePath = filePath + fileName;
        // 每次写入时,都换行写
        String strContent = strcontent + "\r\n";
        try {
            File file = new File(strFilePath);
            if (!file.exists()) {
                file.getParentFile().mkdirs();
                file.createNewFile();
            }
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            raf.seek(file.length());
            raf.write(strContent.getBytes());
            raf.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 生成文件
    private void makeFilePath(String filePath, String fileName) {
        File file = null;
        makeRootDirectory(filePath);
        try {
            file = new File(filePath + fileName);
            if (!file.exists()) {
                file.createNewFile();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 生成文件夹
    private static void makeRootDirectory(String filePath) {
        File file = null;
        try {
            file = new File(filePath);
            if (!file.exists()) {
                file.mkdirs();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


}

使用

放到自定义的Application中初始化即可。

MyUncaughtException.getInstance().init(getApplicationContext());

虽然这样可以捕获到未捕获的异常,当异常在子线程产生时被我们捕获处理,但是当异常产生在主线程时我们发现app此时就会被crash或者导致ANR,那么怎么才能使我们的应用程序不会崩溃呢,我在简书上看到一个crash防护的思路

 new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
               //主线程异常拦截
                while (true) {
                    try {
                        Looper.loop();
                    } catch (Throwable e) {
                        //主线程的异常会从这里抛出         
                    }
                }
            }
        });    
});

这段代码的原理作者也做出了详细的解释,我就不画蛇添足了,但是如果应用程序ANP黑屏的时候会导致app无反应,还是需要将app杀死,具体请看原文。