1. API解释-
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 abstract void uncaughtException (Thread thread, Throwable ex)
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.
Parameters
thread :the thread that has an uncaught exception
ex :the exception that was thrown
—————————————————————————————————————— —————
当线程被uncaught exception事件终止之后,该接口用于处理后续事件。
2. 什么是uncaught exception
uncaught exception即不能被try…catch捕获的异常,比如:
int i=1/0;//空指针异常
或者
throw new ThreadDeath();//进程死亡
throw new NullPointerException();//空指针异常
throw new IllegalAccessError();
……
而可以捕获的异常必须添加try…catch,否则不能通过编译,将出现警告。例如:
File file=new File("/cao/cao");
try {
InputStream inputStream=new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
这里如果file不存在,则这个异常不会引起程序崩溃,属于caught exception。
注意下面这种情况:
InputStream inputStream= null;
try {
inputStream.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
这里虽然有
try…catch
,但是由于
inputStream
为
null
,会先抛出空指针异常,所以仍然是
uncaught exception
,会导致程序崩溃。
3. 一个简单的JAVA实例
实现UncaughtExceptionHandler接口,并将其加入到对应Thread。如果该线程出现uncaught exception事件,则进入UncaughtExceptionHandler接口的uncaughtException (Thread thread, Throwable ex)进行处理。
例如下面新建了一个线程,并为该线程提供了一个UncaughtExceptionHandler接口,该接口函数重启该线程。注意,有两种方式为Thread添加UncaughtExceptionHandler接口。可以在主线程采用:
thread.setUncaughtExceptionHandler(new CrashHandler());
也可以在线程内部采用:
Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler());
下面是一个小例子。
public class MyException {
static Thread thread;
static Runnable runnable;
static int count=0;
public static void main(String[] args) {
// TODO Auto-generated method stub
if (args!=null) {
runnable=new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
// Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler());
if (count==0) {
count++;
// throw new ThreadDeath();//进程死亡
// throw new NullPointerException();//空指针异常
// throw new IllegalAccessError();
// throw new IllegalArgumentException("");
File file=null;
try {
InputStream inputStream=new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
int i=1/0;
}
while (true) {
System.out.println("线程运行中");
}
}
};
thread=new Thread(runnable);
thread.setUncaughtExceptionHandler(new CrashHandler());
thread.start();
}
}
static class CrashHandler implements UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread arg0, Throwable arg1) {
// TODO Auto-generated method stub
System.out.println("系统崩溃!");
thread=new Thread(runnable);
thread.start();
}
}
}
4. Android程序
4.1处理逻辑
在Android程序中,全局的Application和Activity、Service都同属于UI主线程,线程名称默认为“main”。所以,在Application中可以为UI主线程添加UncaughtExceptionHandler,这样整个程序中的Activity、Service中出现的UncaughtException事件都可以被处理。
对于程序中new出来的Thread,可以在其内部,也可以在定义它的线程(例如UI主线程)为其添加UncaughtExceptionHandler。最好的用户体验是在发生程序崩溃的时候弹出一个AlterDialog窗口,用户可以选择重新启动或者结束程序,在一定时间内不选择则自动重启该崩溃的程序。AlterDialog创建过程需要Context类型的参数(或者Activity类型的参数),如下:
new AlertDialog.Builder(context).setTitle("提示").setMessage("线程异常导致程序崩溃--").setPositiveButton("重启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
……
}
}).setNegativeButton("结束", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
……
}
}).create().show();
但是,从全局的
Application
获取的
Context
不能建立
AlterDialog
窗口,这是由于该
Context
与从
Activity
获取的
Context
存在差异。从全局的
Application
获取的
Context
也不能建立任何可视化组件,但可以建立
Toast
。这样,如果需要弹出
AlterDialog
窗口,就必须知道程序出现
UncaughtException
事件时,当前的
Activity
是哪个。
4.2建立全局的Activity栈
考虑建立一个单例类型的栈来存储所有的Activity,这样,当前Activity一定是栈顶元素。这里建立单例的ActivityStack类,单例的实现方式是enum(参看本博客单例模式设计:http://blog.youkuaiyun.com/brillianteagle/article/details/39060483)。
**
* Created by CaoYanfeng on 2015/9/10.
*/
public enum ActivityStack {
INSTANCE;//唯一单例
private static Stack<Activity> stack = new Stack<Activity>();
/**
* 添加元素
*/
public synchronized void push(Activity activity) {
stack.push(activity);
}
/**
* 获取stack顶的Activity,但是不去除
*/
public synchronized Activity peek() {
return stack.peek();
}
/**
* 去除并杀死栈顶的Activity
*/
public synchronized void pop() {
Activity activity = stack.pop();
if (activity != null) {
activity.finish();
activity = null;
}
}
/**
* 去除并杀死某个Activity(stack虽然是栈,但是它其实是继承了Vector,stack.remove(activity)其实是Vector的功能)
*/
public synchronized void killActivity(Activity activity) {
if (activity != null) {
stack.remove(activity);
activity.finish();
activity = null;
}
}
private synchronized boolean isEmpty() {
return stack.isEmpty();
}
public synchronized void clear() {
while (!isEmpty()) {
pop();
}
}
}
4.3对线程进行设置
/**
* Created by CaoYanfeng on 2015/9/9.
*/
public class App extends Application {
ThreadCrashHandler threadCrashHandler;
ActivityStack activityStack=ActivityStack.INSTANCE;
@Override
public void onCreate() {
super.onCreate();
initThreadCrashHandler();
}
public void initThreadCrashHandler(){
threadCrashHandler=ThreadCrashHandler.SingletonHoler.getInstance();
Thread.currentThread().setUncaughtExceptionHandler(threadCrashHandler);//所有Activity和普通的Service都属于UI线程
}
}
这样,在每一个 Activity 的 onCreate() 中将其添加到全局的 ActivityStack 中。
APP localApp=(App)getApplication();
localApp.activityStack.push(this);
在销毁Activity的地方,例如在
onDestory()
、finish()函数、后退键的都要使用
killActivity(Activity activity)
函数
,否则
ActivityStack
会异常。
localApp.activityStack.killActivity(this);
thread.setUncaughtExceptionHandler(localApp.threadCrashHandler);//线程外部
Thread.currentThread().setUncaughtExceptionHandler();//线程内部</span>
4.4实现UncaughtExceptionHandler
系统本身有一个默认的 UncaughtExceptionHandler用于处理异常。 Thread.UncaughtExceptionHandler mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
如果我们实现了
UncaughtExceptionHandler接口,但是接口方法没有进行有效处理,例如关闭程序或者重新启动程序,则程序既不会崩溃,也重启,程序会处于卡死状态。所以,必须实现以下一种:
/**
* Created by CaoYanfeng on 2015/9/9.
* 对于UncaughtException进行了两种处理:
* <p>1、将异常信息写入本地日志</p>
* <p>2、重启程序</p>
*/
public class ThreadCrashHandler implements Thread.UncaughtExceptionHandler {
private Activity activity;
private Map<String, String> deviceInfo = new HashMap<String, String>();
private String crashFilePath = "/mycrash/";
private String fileName = "crash.log";
/**
* private关键字阻止ThreadCrashHandler的new语法
*/
private ThreadCrashHandler() {
}
/**
* 静态内部类实现单例模式
*/
public static class SingletonHoler {
private static final ThreadCrashHandler INSTANCE = new ThreadCrashHandler();
public static ThreadCrashHandler getInstance() {
return INSTANCE;
}
}
/**
* 重写uncaughtException(Thread thread, Throwable ex)函数
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
/* 获取默认的UncaughtExceptionHandler,如果没有自己处理,则依然调用默认的处理逻辑*/
Thread.UncaughtExceptionHandler mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
if (!handleException(thread, ex) && mDefaultHandler != null) {
mDefaultHandler.uncaughtException(thread, ex);//默认的处理逻辑
}
}
/**
* 处理异常事件
*/
private boolean handleException(Thread thread, Throwable ex) {
if (ex == null) return false;
activity = ActivityStack.INSTANCE.peek();
if (activity == null) return false;
new Thread(new MyRunnable(thread)).start();
ex.printStackTrace();//向控制台写入异常信息
saveCrashInfo2LocalFile(thread, ex);//将异常信息写入文件
return true;
}
/**
* 获取设备信息
*
* @return 设备所有信息
*/
private StringBuffer collectDeviceInfo() {
PackageManager packageManager = activity.getPackageManager();
PackageInfo packageInfo = null;
try {
packageInfo = packageManager.getPackageInfo(activity.getPackageName(), packageManager.GET_ACTIVITIES);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (packageInfo != null) {
String versionName = packageInfo.versionName == null ? "null" : packageInfo.versionName;
String versionCode = packageInfo.versionCode + "";
deviceInfo.put("versionName", versionName);//定义在 <manifest> tag's versionName attribute
deviceInfo.put("versionCode", versionCode); //<manifest> tag's versionCode attribute
}
/**
* 通过反射的方法获得Build的所有域
*/
Field[] fields = Build.class.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
deviceInfo.put(field.getName(), field.get(null).toString());
} catch (IllegalAccessException e) {
}
}
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> enty : deviceInfo.entrySet()) {
String key = enty.getKey();
String value = enty.getValue();
sb.append(key + "=" + value + "\n");
}
return sb;
}
/**
* 收集crash信息
*
* @return 完整的chrash信息
*/
private StringBuffer crashInfo(Thread thread, Throwable ex) {
StringBuffer sb = new StringBuffer();
long timeStamp = System.currentTimeMillis();
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
String time = formatter.format(new Date());
sb.append(time + ":").append(timeStamp).append("\n");
sb.append("problem appears at thread:").append(thread.getName() + ",its ID:" + thread.getId() + "\n");
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();
sb.append(result);
return sb;
}
/**
* 将完整的chrash信息写入本地文件
* 如果外部储存可用则存储到外部SD卡,否则存储到内部
*/
private void saveCrashInfo2LocalFile(Thread thread, Throwable ex) {
boolean isFirstWrite = true;//是否是第一次建立文件
File dir = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
dir = new File(activity.getExternalFilesDir(null) + crashFilePath);
} else {
dir = new File(activity.getFilesDir() + crashFilePath);
}
if (!dir.exists() && !dir.isDirectory()) dir.mkdirs();
File file = new File(dir, fileName);
if (file.exists()) {
isFirstWrite = false;
}
OutputStream outStream = null;
try {
outStream = new BufferedOutputStream(new FileOutputStream(file, true));
if (isFirstWrite) {
outStream.write(collectDeviceInfo().toString().getBytes());
}
outStream.write(crashInfo(thread, ex).toString().getBytes());
outStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private class MyRunnable implements Runnable {
Thread thread;
TextView textView = new TextView(activity);//用来加入到AlertDialog中,注意AlertDialog一旦建立不能更改其message
String message = "程序崩溃中:6s";
MyRunnable(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
Looper.prepare();
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
message = "程序崩溃中:" + msg.what + "s";
textView.setText(message);
}
};
textView.setGravity(Gravity.CENTER_HORIZONTAL);
textView.setText(message);
new AlertDialog.Builder(activity).setTitle("提示").setView(textView).setPositiveButton("重启", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Log.i("TAG", "uncaughtException");
Intent intent = new Intent(activity, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent);
if (thread.getName().equals("main"))
android.os.Process.killProcess(android.os.Process.myPid());
}
}).setNegativeButton("结束", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
android.os.Process.killProcess(android.os.Process.myPid());
}
}).create().show();
/**
* 6s倒计时,倒计时结束关闭程序
*/
new CountDownTimer(6000, 1000) {
@Override
public void onFinish() {
android.os.Process.killProcess(android.os.Process.myPid());
}
@Override
public void onTick(long millisUntilFinished) {
Message message = handler.obtainMessage();
message.what = (int) (millisUntilFinished / 1000);
message.sendToTarget();
}
}.start();
Looper.loop();
}
}
}