我们在开发中,经常会遇到各种各样的异常,造成我们的程序崩溃,系统原生的异常处理粗暴的退出,用户体验很差,那么我们能不能自己来处理为捕获异常呢,以供我们来达到更好的用户体验,以及保存异常,甚至是发送给我们的邮箱。下面就介绍UncaughtExceptionHandler类的使用。
一、我们先来看API中对UncaughtExceptionHandler类的描述。
java.lang 接口 Thread.UncaughtExceptionHandler 所有已知实现类: ThreadGroup 正在封闭类: Thread public static interface Thread.UncaughtExceptionHandler 当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。 当某一线程因未捕获的异常而即将终止时,Java 虚拟机将使用 Thread.getUncaughtExceptionHandler() 查询该线程以获得其 UncaughtExceptionHandler 的线程,并调用处理程序的 uncaughtException 方法,将线程和异常作为参数传递。如果某一线程没有明确设置其 UncaughtExceptionHandler,则将它的 ThreadGroup 对象作为其 UncaughtExceptionHandler。如果 ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给默认的未捕获异常处理程序。 从以下版本开始: 1.5 另请参见: Thread.setDefaultUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler), Thread.setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler), ThreadGroup.uncaughtException(java.lang.Thread, java.lang.Throwable) 方法摘要 void uncaughtException(Thread t, Throwable e) 当给定线程因给定的未捕获异常而终止时,调用该方法。 方法详细信息 uncaughtException void uncaughtException(Thread t, Throwable e) 当给定线程因给定的未捕获异常而终止时,调用该方法。 Java 虚拟机将忽略该方法抛出的任何异常。 参数: t - 线程 e - 异常二、从API描述中可以看出 UncaughtExceptionHandler类是当线程因为捕获异常而突然中止时,调用此接口。并实现uncaughtException()方法处理此异常。这样我们就可以得知,当我们的程序出现为捕获异常时候,我们可以实现此接口,重写中的uncaughtException()方法来处理我们的异常,实现我们自己的处理异常功能。
public class MyExceptionHandler implements Thread.UncaughtExceptionHandler{ @Override public void uncaughtException(Thread thread, Throwable throwable) { } }三、实现我们自定义异常处理类的构造函数,初始化系统默认的异常处理类,并设置为当前线程处理。下面代码中的MyApplication是我自定义的application类,以便在我们自己的应用中实现我们的异常处理类,稍后会讲解。
/** * 项目名称:MeiJianFang * 类描述: * 创建人:cdy * 创建时间:2016/3/22 * 修改人:cdy * 修改时间:16:37 * 修改备注: */ public class MyExceptionHandler implements Thread.UncaughtExceptionHandler{ /** 声明系统默认的UncaughtException处理类 */ private Thread.UncaughtExceptionHandler mDefaultHandler; //声明我们自定义的application MyApplication application; public MyExceptionHandler(MyApplication application) { //初始化系统的异常处理类。 this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); //设置此异常处理类为当前的线程处理。 Thread.setDefaultUncaughtExceptionHandler(this); this.application = application; } //实现异常处理的方法 @Override public void uncaughtException(Thread thread, Throwable throwable) { } }四、我们自定义一个处理异常的方法来处理传进来的异常,传进来异常信息类Throwable ex。如果ex不为空则表示系统未处理,我们执行下面我们自行的处理信息方法,提示客户一句话,“应用发生异常,程序即将退出”,返回false。如果为空,择表示系统处理了,返回true。代码中顺便把保存错误日志信息的方法也粘出来,供大家参考。
/** * @brief 自定义错误处理,收集错误信息 * @details 发送错误报告等操作均在此完成 * @param ex 异常信息 * @return true:如果处理了该异常信息;否则返回false。 */ private boolean handleException(final Throwable ex) { //拿到程序的异常,如果是属于程序可以自行处理的,就返回true,如果是程序未处理的,就执行我们的操作。 //当然了我们的关键点是程序未处理的异常。当程序未处理的时候,提示下面的错误信息。 if (ex == null) { return true; } ex.printStackTrace(); // 提示错误消息 new Thread() { @Override public void run() { Looper.prepare(); Toast.makeText(application.getApplicationContext(), "应用发生异常,即将退出!", Toast.LENGTH_LONG).show(); Looper.loop(); } }.start(); // 保存错误报告文件 saveCrashInfoToFile(ex); return true; } /** * @brief 保存错误信息到文件中 * @param ex 异常 */ private void saveCrashInfoToFile(Throwable ex) { //返回一个代表该线程的堆栈转储堆栈跟踪元素的数组,有哪位可以通俗点的讲解一下这句话,欢迎评论。 //下面的for循环可以拿到遍历,来让异常消息换行。 final StackTraceElement[] stack = ex.getStackTrace(); final String message = ex.getMessage(); /* 准备错误日志文件 */ //FileUtil.APP_LOG_PATH 是获取的当前sd卡根目录,创建我们将要存储日志的文件。 File logFile = new File(FileUtil.APP_LOG_PATH + LOG_NAME); if (!logFile.getParentFile().exists()) { logFile.getParentFile().mkdirs(); } /* 写入错误日志 */ FileWriter fw = null; final String lineFeed = "\r\n"; try { fw = new FileWriter(logFile, true); //StringUtil.currentTime(StringUtil.FORMAT_YMDHMS).toString() 我自定义的获取系统当前日期的方法。 fw.write(StringUtil.currentTime(StringUtil.FORMAT_YMDHMS).toString() + lineFeed + lineFeed); fw.write(message + lineFeed); for (int i = 0; i < stack.length; i++) { fw.write(stack[i].toString() + lineFeed); } fw.write(lineFeed); fw.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (null != fw) fw.close(); } catch (IOException e) { e.printStackTrace(); } } }五、在 u ncaughtException()方法中,来处理我们为捕获异常。如果异常处理类不为空,而且我们自定义的也没有处理(也就是在自定义方法中的return true),那么就交由系统自行处理。如果我们处理了,那么线程休息3秒钟,杀死进程。休息三秒钟以供我们可以有时间弹出我们自定义的异常处理消息。
//实现异常处理的方法 @Override public void uncaughtException(Thread thread, Throwable ex) { // 首先使用我们自定义的异常处理类来处理,如果是未捕获异常,那么我们自行处理,如果不是那么不处理,返回true,没有处理则让系统默认的异常处理器来处理, if (!handleException(ex) && mDefaultHandler != null) { mDefaultHandler.uncaughtException(thread, ex); } else { // 等待会后结束程序 try { Log.i(LOG_NAME, "exit start"); Thread.sleep(3000); android.os.Process.killProcess(android.os.Process.myPid()); System.exit(10); application.finishActivity(); Log.i(LOG_NAME,"exit end"); } catch (InterruptedException e) { e.printStackTrace(); } } }
package com.meijianfang.appliction; import android.os.Looper; import android.util.Log; import android.widget.Toast; import com.meijianfang.tool.FileUtil; import com.meijianfang.tool.StringUtil; import java.io.File; import java.io.FileWriter; import java.io.IOException; /** * 项目名称:MeiJianFang * 类描述: * 创建人:cdy * 创建时间:2016/3/22 * 修改人:cdy * 修改时间:16:37 * 修改备注: */ public class MyExceptionHandler implements Thread.UncaughtExceptionHandler{ /** 声明系统默认的UncaughtException处理类 */ private Thread.UncaughtExceptionHandler mDefaultHandler; //声明我们自定义的application MyApplication application; /** 错误日志文件名称 */ static final String LOG_NAME = "/crash.txt"; public MyExceptionHandler(MyApplication application) { //初始化系统的异常处理类。 this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); //设置此异常处理类为当前的线程处理。 Thread.setDefaultUncaughtExceptionHandler(this); this.application = application; } //实现异常处理的方法 @Override public void uncaughtException(Thread thread, Throwable ex) { // 首先使用我们自定义的异常处理类来处理,如果是未捕获异常,那么我们自行处理,如果不是那么不处理,返回true,没有处理则让系统默认的异常处理器来处理, if (!handleException(ex) && mDefaultHandler != null) { mDefaultHandler.uncaughtException(thread, ex); } else { // 等待会后结束程序 try { Log.i(LOG_NAME, "exit start"); Thread.sleep(3000); android.os.Process.killProcess(android.os.Process.myPid()); System.exit(10); application.finishActivity(); Log.i(LOG_NAME,"exit end"); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * @brief 自定义错误处理,收集错误信息 * @details 发送错误报告等操作均在此完成 * @param ex 异常信息 * @return true:如果处理了该异常信息;否则返回false。 */ private boolean handleException(final Throwable ex) { //拿到程序的异常,如果是属于程序可以自行处理的,就返回true,如果是程序未处理的,就执行我们的操作。 //当然了我们的关键点是程序未处理的异常。当程序未处理的时候,提示下面的错误信息。 if (ex == null) { return true; } ex.printStackTrace(); // 提示错误消息 new Thread() { @Override public void run() { Looper.prepare(); Toast.makeText(application.getApplicationContext(), "应用发生异常,即将退出!", Toast.LENGTH_LONG).show(); Looper.loop(); } }.start(); // 保存错误报告文件 saveCrashInfoToFile(ex); return true; } /** * @brief 保存错误信息到文件中 * @param ex 异常 */ private void saveCrashInfoToFile(Throwable ex) { //返回一个代表该线程的堆栈转储堆栈跟踪元素的数组,有哪位可以通俗点的讲解一下这句话,欢迎评论。 //下面的for循环可以拿到遍历,来让异常消息换行。 final StackTraceElement[] stack = ex.getStackTrace(); final String message = ex.getMessage(); /* 准备错误日志文件 */ //FileUtil.APP_LOG_PATH 是获取的当前sd卡根目录,创建我们将要存储日志的文件。 File logFile = new File(FileUtil.APP_LOG_PATH + LOG_NAME); if (!logFile.getParentFile().exists()) { logFile.getParentFile().mkdirs(); } /* 写入错误日志 */ FileWriter fw = null; final String lineFeed = "\r\n"; try { fw = new FileWriter(logFile, true); //StringUtil.currentTime(StringUtil.FORMAT_YMDHMS).toString() 我自定义的获取系统当前日期的方法。 fw.write(StringUtil.currentTime(StringUtil.FORMAT_YMDHMS).toString() + lineFeed + lineFeed); fw.write(message + lineFeed); for (int i = 0; i < stack.length; i++) { fw.write(stack[i].toString() + lineFeed); } fw.write(lineFeed); fw.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (null != fw) fw.close(); } catch (IOException e) { e.printStackTrace(); } } } }七、如何在程序中使用呢,这下就用到了我们自定义的Appliction类了。首先实现系统的Appliction接口,重写onCreate()方法,在方法中初始化我们刚刚自定义的异常捕获类。
package com.meijianfang.appliction; import android.app.Application; /** * 项目名称:MeiJianFang * 类描述: * 创建人:cdy * 创建时间:2016/3/22 * 修改人:cdy * 修改时间:17:01 * 修改备注: */ public class MyApplictionHandler extends Application { @Override public void onCreate() { super.onCreate(); /* 全局异常崩溃处理 */ MyExceptionHandler catchExcep = new MyExceptionHandler(this); Thread.setDefaultUncaughtExceptionHandler(catchExcep); } }八、在我们应用的AndroidManifest.xml文件中引用我们自己定义的Application类,作为应用的application类来使用。
<application android:name=".appliction.MyApplictionHandler" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme">最后我们运行我们的程序,我这里在首页写了一个空指针的异常。系统并没有直接close,而是提示出来了我们自定义的消息,是不是体验更好了。效果如图。欢迎评论,互相学习。