Android全局异常捕获(支持保存到sdcard、上传服务器)

转载自: https://blog.csdn.net/xuanzhiqiang01/article/details/79362947

Thread.UncaughtExceptionHandler接口

        当程序发生异常终止时,如果我们没有对其进行异常捕获处理,系统会调用该线程组的默认异常捕获处理器。我们想要对应用全局的异常进行捕获,实现思路就是自己写一个捕获器去替换系统默认的,然后对设备信息、版本信息、异常信息进行收集并上传到服务器进行分析。

Android 提供了这样一个接口Thread.UncaughtExceptionHandler

    /**
     * Implemented by objects that want to handle cases where a thread is being
     * terminated by an uncaught exception. Upon such termination, the handler
     * is notified of the terminating thread and causal exception. If there is
     * no explicit handler set then the thread's group is the default handler.
     */
    public static interface UncaughtExceptionHandler {
        /**
         * The thread is being terminated by an uncaught exception. Further
         * exceptions thrown in this method are prevent the remainder of the
         * method from executing, but are otherwise ignored.
         *
         * @param thread the thread that has an uncaught exception
         * @param ex the exception that was thrown
         */
        void uncaughtException(Thread thread, Throwable ex);
    }

该接口就是系统异常终止时,用来处理异常的约束。

系统默认异常处理器:

Thread.getDefaultUncaughtExceptionHandler();

实现思路

  1. 自定义CrashHandler实现接口Thread.UncaughtExceptionHandler
  2. 在应用Application初始化时替换系统默认的异常处理器

自定义CrashHandler需要做的工作:

  1. 收集设备信息、版本信息、异常信息
  2. 写入本地记录
  3. 上传到服务器

实现示例

public class CrashHandler implements UncaughtExceptionHandler {


    private static CrashHandler mInstance;
    private Context mContext;
    private UncaughtExceptionHandler mDefautHandler;
    private static final String TAG = "CrashHandlerTag";

    private CrashHandler() {

    }

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


    public void init(Context context) {
        mContext = context;
        mDefautHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }


    @Override
    public void uncaughtException(Thread thread, Throwable ex) {


        if (ex != null) {

            // 收集设备信息、版本信息、异常信息
            String info = collectDeviceInfo(mContext, ex);
            // 本地存储
            saveInfo(info);
            Log.d(TAG, "已捕获到异常 ");

            //不进行延时处理的话kill不能执行完成
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    // 异常已经处理,结束进程
                    Process.killProcess(Process.myPid());
                    System.exit(1);

                }
            }, 100);

        } else {
            Log.d(TAG, "没有捕获异常 ");
            //没有处理还交给系统默认的处理器
            if (mDefautHandler != null) {
                mDefautHandler.uncaughtException(thread, ex);
            }
        }


    }

    /**
     * 收集设备信息
     *
     * @param c
     * @param ex
     */
    private String collectDeviceInfo(Context c, Throwable ex) {
        Map<String, String> infos = new HashMap<>();
        //收集版本信息
        try {
            PackageManager pm = c.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(c.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionCode = pi.versionCode + "";
                String versionName = TextUtils.isEmpty(pi.versionName) ? "没有版本名称" : pi.versionName;
                infos.put("versionCode", versionCode);
                infos.put("versionName", versionName);
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        //收集设备信息
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
            } catch (Exception e) {
            }
        }

        // 收集异常信息
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();

        // 转化为字符串
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }
        sb.append(result);

        return sb.toString();
    }

    /**
     * 保存异常信息到本地
     * @param infos
     */
    private void saveInfo(String infos) {
        Log.d(TAG, "输出log日志: " + infos);
        // 把采集到的信息写入到本地文件
//        outToSdcard(infos);

    }

    /**
     * 保存异常信息到sdcard中
     *
     * @param errorlog
     */
    public void outToSdcard(String errorlog) {


        String sdPath = Environment.getExternalStorageDirectory().getPath() + "/error_log";
        //新建文件
        File sdCardDir = new File(sdPath);

        if (!sdCardDir.exists()) {

            if (!sdCardDir.mkdirs()) {

                try {
                    sdCardDir.createNewFile();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        try {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                    .format(new Date());
            String fileName = time + ".txt";
            //新建文件
            File saveFile = new File(sdCardDir, fileName);

            if (!saveFile.exists()) {
                saveFile.createNewFile();
            }

            final FileOutputStream outStream = new FileOutputStream(saveFile);

            try {
                if (errorlog != null) {
                    outStream.write(errorlog.getBytes());
                    outStream.close();
                }

            } catch (IOException e) {
                e.printStackTrace();
                Log.d(TAG, "流写入错误:" + e.toString());
            }

            Log.d(TAG, "日志输出成功! ");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

注意: outToSdcard保存到sdcard时,需要添加并打开写入权限。

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

使用示例

public class MyApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        //全局异常捕获
        CrashHandler.getInstance().init(getApplicationContext());
    }

}

造错实例

    //动态授权
    private void permission() {

        if (Build.VERSION.SDK_INT >= 23) {
            String[] mPermissionList = new String[]{
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE,
            };
            ActivityCompat.requestPermissions(UserActivity.this, mPermissionList, 123);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);

        permission();


        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(UserActivity.this, "子线程toast,会报线程异常",                 Toast.LENGTH_SHORT).show();
            }
        });

        thread.start();

    }

 原始代码,50行报错:

 日志输出:

2021-09-09 11:32:02.045 17993-18012/com.example.sqlitemanager D/CrashHandlerTag: 输出报错日志: MEIZU_FINGERPRINT=7.1.2-1576008878_stable
    SUPPORTED_64_BIT_ABIS=[Ljava.lang.String;@dc55e30
    versionCode=1
    BOARD=M6Note
    BOOTLOADER=unknown
    TYPE=user
    ID=N2G47H
    TIME=1576011277000
    BRAND=Meizu
    TAG=Build
    HARDWARE=qcom
    SERIAL=721QACSM222VX
    SUPPORTED_ABIS=[Ljava.lang.String;@8dbeaa9
    CPU_ABI=arm64-v8a
    IS_DEBUGGABLE=false
    RADIO=unknown
    MANUFACTURER=Meizu
    IS_EMULATOR=false
    SUPPORTED_32_BIT_ABIS=[Ljava.lang.String;@97bdf73
    TAGS=release-keys
    CPU_ABI2=
    UNKNOWN=unknown
    PERMISSIONS_REVIEW_REQUIRED=false
    USER=flyme
    FINGERPRINT=Meizu/meizu_M6Note_CN/M6Note:7.1.2/N2G47H/m1721.Flyme_8.0.1568701887:user/release-keys
    HOST=Mz-Builder-l7
    versionName=1.0
    PRODUCT=meizu_M6Note_CN
    DISPLAY=Flyme 8.0.0.0A
    MODEL=M6 Note
    DEVICE=M6Note
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)
        at android.widget.Toast$TN$2.<init>(Toast.java:368)
        at android.widget.Toast$TN.<init>(Toast.java:368)
        at android.widget.Toast.<init>(Toast.java:112)
        at android.widget.Toast.makeText(Toast.java:276)
        at com.example.sqlitemanager.UserActivity$1.run(UserActivity.java:50)
        at java.lang.Thread.run(Thread.java:761)
2021-09-09 11:32:02.045 17993-18012/com.example.sqlitemanager D/CrashHandlerTag: 已捕获异常 
2021-09-09 11:32:02.045 17993-18012/com.example.sqlitemanager D/CrashHandlerTag: 进程杀死前

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值