欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Thread.UncaughtExceptionHandler获取应用的crash信息

程序员文章站 2022-04-09 10:30:14
...

在日常开发中,避免不了程序出现crash,即“崩溃”,但是如果这些crash不及时的解决,很大程度的会影响用户的体验,好比,你用的一个软件,天天卡死,你觉得会有多少人用?那么我们该如何解决这些crash的信息呢?

这里开始说到我要开始的话题,其实在Android系统中,我们可以通过Thread.UncaughtExceptionHandler来监视应用的crash信息,给程序设置一个Thread.UncaughtExceptionHandler,这样当程序crash的时候就会调用Thread.UncaughtExceptionHandler中的uncaughtException(Thread t, Throwable ex)这个方法,我们从方法中可以看出当前crash的线程和throwable的相关异常信息。有了crash的异常信息,我们就可以进行查看和上传到我们的server端,进行调试bug。

首先看看Thread.UncaughtExceptionHandler的定义:
Interface for handlers invoked when a Thread abruptly terminates due to an uncaught exception.

即:由于未捕获的异常,线程突然终止时调用的处理程序接口。

Des:

When a thread is about to terminate due to an uncaught exception the Java Virtual Machine will query the thread for its UncaughtExceptionHandler using getUncaughtExceptionHandler() and will invoke the handler's uncaughtException method, passing the thread and the exception as arguments. If a thread has not had its UncaughtExceptionHandler explicitly set, then its ThreadGroup object acts as its UncaughtExceptionHandler. If the ThreadGroup object has no special requirements for dealing with the exception, it can forward the invocation to the default uncaught exception handler.

即:
当线程即将终止,由于未捕获的异常,Java虚拟机将使用getUncaughtExceptionHandler()向其线程查询其UncaughtExceptionHandler,并将调用处理程序的uncaughtException方法,将线程和异常作为参数传递。 如果一个线程没有显式地设置它的UncaughtExceptionHandler,那么它的ThreadGroup对象作为它的UncaughtExceptionHandler。 如果ThreadGroup对象没有处理异常的特殊要求,它可以将调用转发给默认的未捕获的异常处理程序。

接口的源代码定义如下:

    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * 当线程终止由于发生了未捕获的异常时调用
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

接下来开始真真正的实现如何在我们项目中进行使用:

首先我们定义CrashHandler去实现UncaughtExceptionHandler ,然后重写里面的uncaughtException()方法。
具体实现如下:

public class MyCrashHandler implements Thread.UncaughtExceptionHandler {

    private Context mContext;

    private String TAG = "MyCrashHandler";

    private static MyCrashHandler mInstance=new MyCrashHandler();

    private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;

    private MyCrashHandler() {
    }

    public static MyCrashHandler getInstance(){
        return mInstance;
    }

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

    /**
     * 当程序中有未被捕获的异常,系统会自动调用uncaughtException()这个方法
     * @param t 为出现未捕获异常的线程
     * @param ex 为未捕获的异常,包含异常信息
     */
    @Override
    public void uncaughtException(Thread t, Throwable ex) {
        //导出异常信息到sd卡
        dumpExceptionToSdCard(ex);
        showToast(mContext, "很抱歉,程序异常即将退出!");

        //延时退出
        try {
            t.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //将异常信息上传到web server
        upLoadFileToServer();

        //如果系统提供了默认的异常处理器,则交给系统去结束程序,否则就有自己结束自己
        if (mDefaultUncaughtExceptionHandler!=null){
            mDefaultUncaughtExceptionHandler.uncaughtException(t,ex);
        }else{
            Process.killProcess(Process.myPid());
        }
    }


    private void dumpExceptionToSdCard(Throwable ex) {
        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            Log.i(TAG, "dumpExceptionToSdCard: sdcard not found please try again !");
            return;
        }

        //创建目录
        File dir =new File(Common.ROOT_PATH);
        if (!dir.exists()){
            dir.mkdirs();
        }

        long current = System.currentTimeMillis();
        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(current));

        File file = new File(Common.PATH + Common.FILE_NAME + time + Common.FILE_NAME_SUFFIX);
        try {
            PrintWriter pw=new PrintWriter(new BufferedOutputStream(new FileOutputStream(file)));

            pw.println(time);

            pw.println(ex.getMessage());

            dumpPhoneInfo(pw);
            ex.printStackTrace(pw);
            pw.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

    }

    /**
     * 将手机信息堆入到流中
     * @param pw
     */
    private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
        PackageManager pm = mContext.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
        pw.print("App Version" );
        pw.print(pi.versionName);
        pw.print('_');
        pw.print(pi.versionCode);

        //Android 版本号
        pw.print("OS Version:");
        pw.print(Build.VERSION.RELEASE);
        pw.print('_');
        pw.print(Build.VERSION.SDK_INT);

        //手机制造商
        pw.print("Vemdpr:");
        pw.print(Build.MANUFACTURER);

        //手机型号
        pw.print("Model:");
        pw.print(Build.MODEL);

         //CPU架构
        pw.print("CPU ABI: ");
        pw.println(Build.SUPPORTED_ABIS);
    }


    private void upLoadFileToServer() {
        //1.判断网络的状态
        //2.在合适的网络状态下进行上传到服务器
    }


    //线程中展示Toast
    private void showToast(final Context context, final String msg) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }).start();
    }

}

接着我们在全局的application中调用。

public class MyApp extends Application {

    public static MyApp mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance=this;
        init();
    }

    private void init() {
        MyCrashHandler crashHandler=MyCrashHandler.getInstance();
        crashHandler.init(this);
    }

    public static MyApp getInstance(){
        return mInstance;
    }
}

最后记得加权限

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

最后看看运行效果:
Thread.UncaughtExceptionHandler获取应用的crash信息

看看我们的文件
Thread.UncaughtExceptionHandler获取应用的crash信息

注意因为国内手机厂商的定制,有时我们看不见生成的文件,或者sdcard目录,我们可以借助Android studio中的Device File Explorer菜单栏进行查看

Thread.UncaughtExceptionHandler获取应用的crash信息

最后在实际的开发中,更多的使用第三方的一些bug管理工具,比如
腾讯的Bugly,或者友盟统计等。