您的位置:首页 > 移动开发 > Android开发

【Android开发】UncaughtExceptionHandler接口详解

2015-09-14 20:49 302 查看

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(参看本博客单例模式设计:/article/7749579.html)。

**
* 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对线程进行设置

第一种情况,对于UI主线程,类APP继承Application,在此处对UI主线程进行设置即可。实例化Activity栈,同时对主线程设置ThreadCrashHandler。
/**
* 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);


对于第二种情况,子线程出现UncaughtException。则在new该子线程或者子线程的内部都可以进行设置。
thread.setUncaughtExceptionHandler(localApp.threadCrashHandler);//线程外部
Thread.currentThread().setUncaughtExceptionHandler();//线程内部</span>

注意,只要程序有新的线程,都需要进行这样的设置。

4.4实现UncaughtExceptionHandler

系统本身有一个默认的UncaughtExceptionHandler用于处理异常。
Thread.UncaughtExceptionHandler mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();

如果我们实现了UncaughtExceptionHandler接口,但是接口方法没有进行有效处理,例如关闭程序或者重新启动程序,则程序既不会崩溃,也重启,程序会处于卡死状态。所以,必须实现以下一种:
1)由默认的UncaughtExceptionHandler来处理异常;
2)程序重新启动;
3)程序关闭。

最后实现UncaughtExceptionHandler接口。其中,采用静态内部类实现单例(参看本博客单例模式设计:/article/7749579.html)。
/**
* 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();
}
}
}


5. 参考


[1]android 中处理崩溃异常并重启程序;


[2]android基础知识24:Android中处理崩溃异常;

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: