Android中的异常事件处理器

作者: 根艮哏艮根 | 来源:发表于2017-11-30 09:43 被阅读96次

Android应用不可避免的发生异常,即系统奔溃,这不是任何人能避免的,无论你的程序写的多么完美,它也不会完全避免异常的发生,其中的原因我们就不去研究了。当异常发生后,系统就会kill掉正在运行的程序,现象就是发生闪退或者提示用户程序无响应和停止运行,这是我们长达程序猿同胞们最不愿意见到的场景。更糟糕的是,当你上线后的应用发生异常,开发者确无法得知程序为何奔溃了,更谈不上去处理它了。幸好,Android提供了处理这类问题的方法,即是Thread类中的一个方法setDefaultUncaughtExceptionHandler

/**
     * Set the default handler invoked when a thread abruptly terminates
     * due to an uncaught exception, and no other handler has been defined
     * for that thread.
     *
     * <p>Uncaught exception handling is controlled first by the thread, then
     * by the thread's {@link ThreadGroup} object and finally by the default
     * uncaught exception handler. If the thread does not have an explicit
     * uncaught exception handler set, and the thread's thread group
     * (including parent thread groups)  does not specialize its
     * <tt>uncaughtException</tt> method, then the default handler's
     * <tt>uncaughtException</tt> method will be invoked.
     * <p>By setting the default uncaught exception handler, an application
     * can change the way in which uncaught exceptions are handled (such as
     * logging to a specific device, or file) for those threads that would
     * already accept whatever &quot;default&quot; behavior the system
     * provided.
     *
     * <p>Note that the default uncaught exception handler should not usually
     * defer to the thread's <tt>ThreadGroup</tt> object, as that could cause
     * infinite recursion.
     *
     * @param eh the object to use as the default uncaught exception handler.
     * If <tt>null</tt> then there is no default handler.
     *
     * @throws SecurityException if a security manager is present and it
     *         denies <tt>{@link RuntimePermission}
     *         (&quot;setDefaultUncaughtExceptionHandler&quot;)</tt>
     *
     * @see #setUncaughtExceptionHandler
     * @see #getUncaughtExceptionHandler
     * @see ThreadGroup#uncaughtException
     * @since 1.5
     */
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
         defaultUncaughtExceptionHandler = eh;
     }

实现方法:让我们的类继承Thread.UncaughtExceptionHandler,然后重写它里面的uncaughtException方法就可以了
当异常发生的时候,系统就会回调UncaughtExceptionHandler的uncaughtException的方法,在uncaughtException方法中我们就可以获取到异常信息,我们可以选择把异常信息保存在SD卡中,然后在合适的时机上传到网络服务器中,这样开发者就可以分析用户的错误然后在后面的版本中去修复这个bug,当然,我们为了给用户一个良好的体验,可以选择弹出一个对话框甚至跳转一个界面告诉用户程序奔溃了,然后在退出,这样做比闪退的体验好了不止一倍了。
下面就是一个异常处理器的实现类:

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "CrashHandler";
    private static final boolean DEBUG = true;
    private static final String PATH = Environment.getExternalStorageDirectory()
            .getAbsolutePath()
            + File.separator
            + File.separator;

    private static final String FILE_NAME = "crashText";
    private static final String FILE_NAME_SUFFIX = ".log";


    private static CrashHandler sInstance = new CrashHandler();
    private Thread.UncaughtExceptionHandler mDefaultCrasHandler;

    private Context mContext;

    public CrashHandler() {

    }

    public static CrashHandler getsInstance() {
        return sInstance;
    }

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

    /**
     * 这是最关键的函数,当程序中有未捕获的异常,系统将会自动调用这个方法,
     * thread为出现未捕获异常的线程,ex为为未捕获的异常,有了这个ex,我们就可以得到
     * 异常信息了
     *
     * @param thread
     * @param ex
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {

        //导出异常信息到SD卡中
        try {
            dumpExceptionToSDCard(ex);
            //上传异常信息到服务器,便于开发人员分析日志从而解决bug
            uploadExceptionToServer();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
            ex.printStackTrace();
        //如果系统提供了默认的异常处理器,则交给系统去结束程序,否则就由自己结束自己
        if (mDefaultCrasHandler != null) {
            mDefaultCrasHandler.uncaughtException(thread, ex);
        } else {
            Process.killProcess(Process.myPid());
        }


    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private void dumpExceptionToSDCard(Throwable ex) throws IOException {
        //如果SD卡不存在或无法使用,则无法把异常信息写入SD卡中
        if (!Environment.getExternalStorageState()
                .equals(Environment.MEDIA_MOUNTED)) {
            if (DEBUG) {
                Log.i(TAG, "dumpExceptionToSDCard: SD卡不存在");
                return;
            }
        }
        File dir = new File(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(PATH + FILE_NAME);
        //先删除之前的异常信息
        if (file.exists()) {
            DeleteFile(file);
        }
        if (!file.exists()) {
            file.mkdirs();
        }
        StackTraceElement[] stackTrace = ex.getStackTrace();

        String error_text = "错误:" + ex.toString() + "  \n  ";
        for (int i = 0; i < stackTrace.length; i++) {
            error_text += stackTrace[i].getFileName() + " class:"
                    + stackTrace[i].getClassName() + " method:"
                    + stackTrace[i].getMethodName() + " line:"
                    + stackTrace[i].getLineNumber() + "  \n  ";
        }
        try {
            PrintWriter pw = new PrintWriter(
                    new BufferedWriter(
                            new FileWriter(file + File.separator
                                    + time + FILE_NAME_SUFFIX)));
            pw.println(time);
            dumpPhoneInfo(pw);
            pw.println();
            ex.printStackTrace(pw);
            pw.close();
        } catch (Exception e) {
            e.printStackTrace();
            Log.i(TAG, "dumpExceptionToSDCard: " + e);
        }
    }

    /**
     * 写入手机的基本信息
     *
     * @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.println(pi.versionCode);

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

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

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

        //CUP架构
        pw.print("CUP ABI: ");
        pw.println(Build.CPU_ABI);

        //奔溃发生时间
        pw.print("CURRENT DATE: ");
        Date currentDate = new Date();
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
        pw.println(dateFormat.format(currentDate));
    }

    private void uploadExceptionToServer() {
        //TODO Upload Exception Message To Your Web Server
    }

当应用奔溃的时候,CrashHandler会将异常信息及设备信息保存到SD卡中,接着将异常交给系统去处理,系统会帮助我们中止程序。
那么该如何去使用CrashHandler呢?很简单,我们在Application初始化的时候设置CrashHandler,如下所示:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler crashHandler = CrashHandler.getsInstance();
        crashHandler.init(this);
    }
}

这样我们就可以很方便的到服务器上去查看用户的奔溃信息了,需要注意的是,代码中被catch的异常是不会交给CrashHandler处理的,CrashHandler只能收到那些没有被捕获的异常,下面我们测试一下,如下所示,我们抛出一个异常。

public class MainActivityextends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        int s = 2 / 0;//除数不能为0
    }
}

在这里,我们为了不为用户增加内存的负担,当发生奔溃的时候,将以前保存在SD卡中的异常信息删除,这样就不会每次出现奔溃信息后就创建一个文件,节省内存。

  /**
     * 递归删除文件和文件夹
     * @param file    要删除的根目录
     */
    public void DeleteFile(File file){
        if(file.isFile()){
            file.delete();
            return;
        }
        if(file.isDirectory()){
            File[] childFile = file.listFiles();
            if(childFile == null || childFile.length == 0){
                file.delete();
                return;
            }
            for(File f : childFile){
                DeleteFile(f);
            }
            file.delete();
        }
    }

在文章的开头我提到过,为了给用户好的体验,我决定跳转一个界面来提示用户发生了异常,如图所示这样的界面,用户的体验是不是会上一层楼呢?
我们在uncaughtException方法中调用跳转界面

    new Thread() {
            @Override
            public void run() {

                Looper.prepare();
                try {
                    Intent intent = new Intent();
                    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent.setAction("com.error.text.NOTIFY_ERROR");
                    mContext.startActivity(intent);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                Looper.loop();
            }
        }.start();

然后在AndroidManifest.xml中配置Activity,记住这里是隐式启动Activity,代码如下所示:

     <activity
            android:name=".ErrorActivity"
            android:configChanges="orientation|keyboardHidden"
            android:screenOrientation="portrait"
            >
            <intent-filter>
                <action android:name="com.error.text.NOTIFY_ERROR" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
     </activity>

界面大致是这样的:


error.png

完全退出帮助类

public class CompleteQuit extends Application {

    private Stack<Activity> activityStack;
    private static CompleteQuit instance;

    private CompleteQuit() {
    }

    /**
     * 单例模式中获取唯一的CompleteQuit实例
     */
    public static CompleteQuit getInstance() {
        if (null == instance) {
            instance = new CompleteQuit();
        }
        return instance;

    }

    /**
     * 添加Activity到容器中
     */

    public void pushActivity(Activity activity) {
        if (activityStack == null) {
            activityStack = new Stack<Activity>();
        }
        activityStack.add(activity);
    }

    /**
     * 退出栈中所有Activity(唯一列外)
     */

    @SuppressWarnings("rawtypes")
    public void exitAllButOne(Class cls) {
        while (true) {
            Activity activity = currentActivity();
            if (activity == null) {
                break;
            }
            if (activity.getClass().equals(cls)) {
                break;
            }
            popActivity(activity);
        }
    }

    /**
     * 退出栈中所有Activity
     */
    public void exitAll(boolean exit) {
        while (true) {
            Activity activity = currentActivity();
            if (activity == null) {
                break;
            }
            popActivity(activity);
        }
        if (exit) {
            System.exit(0);
        }
    }

    /**
     * 获得当前栈顶Activity
     */
    public Activity currentActivity() {
        Activity activity = null;
        if (activityStack != null && !activityStack.empty())
            activity = activityStack.lastElement();
        return activity;
    }

    /**
     * 退出栈顶Activity
     */
    public void popActivity(Activity activity) {
        if (activity != null) {
            // 在从自定义集合中取出当前Activity时,也进行了Activity的关闭操作
            activity.finish();
            activityStack.remove(activity);
            activity = null;
        }
    }
}

到此我们的异常事件处理器就完成了,快快为你的应用加上默认的异常事件处理器吧!!!


生成的日志文件.jpg 日志内容.jpg

附上Github地址

相关文章

  • Android中的异常事件处理器

    Android应用不可避免的发生异常,即系统奔溃,这不是任何人能避免的,无论你的程序写的多么完美,它也不会完全避免...

  • uboot-step 1 设置异常向量表

    uboot-step 1 设置异常向量表 何为异常 异常是指处理器的正常运行被内部或者外部事件所打断导致处理器去处...

  • 捕获Android中未捕获到的异常,防止程序崩溃界面不友好

    序言:本篇的笔记篇,用于记录我目前使用的异常处理器,以便后期查找使用; 在Android开发中,我们的程序偶尔会出...

  • 异常控制流2

    中断和异常的处理: 发生异常和中断事件后,系统将进入os内核对响应的事件进行处理,即改变处理器状态(用户态->内核...

  • 全局异常处理

    首先,处理思路,嗯对,就是想想: 其次,创建全局异常处理器,开写 最后,Springmvc中配置异常处理器 ok!...

  • SpringMVC异常处理和拦截器

    配置异常处理器组件1.配置自定义异常类 extend Exception 2.编写异常处理器 implement ...

  • FreeRTOS学习笔记(10)——中断管理

    一、基本概念 1.1 异常 异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理,轻则系统出...

  • iOS异常浅析

    异常简介 处理器和系统内核中有设计标识不同事件的状态码,这些状态被编码为不同的位和信号。每次处理器和内核检测到状态...

  • iOS异常浅析

    异常简介 处理器和系统内核中有设计标识不同事件的状态码,这些状态被编码为不同的位和信号。每次处理器和内核检测到状态...

  • springMVC(3)异常处理与拦截器与SSM整合

    7. 异常处理步骤:7.1 编写自定义异常类(提示信息) 7.2 编写异常处理器 7.3 配置异常处理器(跳转到错...

网友评论

    本文标题:Android中的异常事件处理器

    本文链接:https://www.haomeiwen.com/subject/mxipbxtx.html