我们在开发中,经常会遇到各种各样的异常,造成我们的程序崩溃,系统原生的异常处理粗暴的退出,用户体验很差,那么我们能不能自己来处理为捕获异常呢,以供我们来达到更好的用户体验,以及保存异常,甚至是发送给我们的邮箱。下面就介绍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();
}
}
}
五、在uncaughtException()方法中,来处理我们为捕获异常。如果异常处理类不为空,而且我们自定义的也没有处理(也就是在自定义方法中的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,而是提示出来了我们自定义的消息,是不是体验更好了。效果如图。欢迎评论,互相学习。