Android捕获全局异常其实利用的就是Thread捕获异常,Android中的UI线程其实就是一个普通的Thread,不过它里面维护了一个Handler,这个Handler对Android的整个应用的运行起到了很大的作用,可以这么说Android应用的运行就是基于Handler。
接下来让我们来看看Thread类是如何捕获异常的,Thread中有一个接口,如下:
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
这个接口的作用就是当有异常发生在这个线程中时会调用到这个方法,这样我们就可以实现这个接口然后注入进去就可以了。
这里我对这个做了一个简易的封装,功能就是捕获线程异常然后打印到sdcard,当然也可以把捕获到的异常发送到服务器:
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private final Thread.UncaughtExceptionHandler defaultHandler;
private static final String CRASH_DIR = "_crash.log";
private CrashHandler() {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
private static class InstanceHold{
public static CrashHandler instance = new CrashHandler();
}
public static CrashHandler getInstance(){
return InstanceHold.instance;
}
@Override
public void uncaughtException(Thread t, Throwable ex) {
if (!handleExceptionMessage(ex) && defaultHandler != null){
defaultHandler.uncaughtException(t,ex);
}else {
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
Process.killProcess(Process.myPid());
System.exit(1);
}
}
public boolean handleExceptionMessage(Throwable e){
StringWriter sw = new StringWriter();
sw.append("这是自定义抛异常\n");
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
File file;
try {
file = new File(getCrashDir());
FileWriter fw = new FileWriter(file);
fw.write(sw.toString());
fw.close();
} catch (IOException e1) {
e1.printStackTrace();
return false;
}
Log.e(TAG, sw.toString());
pw.close();
return true;
}
public String getCrashDir(){
StringBuffer buffer = new StringBuffer();
buffer.append(Environment.getExternalStorageDirectory()+File.separator).append(BuildConfig.APPLICATION_ID).append(getTime()).append(CRASH_DIR);
return buffer.toString();
}
public String getTime(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String time = format.format(new Date());
return time;
}
}
当你想要在哪个线程中监听异常时,直接在该线程调用
CrashHandler.getInstance();
这样,该线程有异常发生时就会打印到sdcard,如果想要监听UI线程,直接在Application的onCreate()中调用该方法即可。
对于日志的查看,我个人比较喜欢在Android studio的Terminal中查看,比较的方便,Terminal中查看首先你得做以下几步:
1、首先在环境变量中配置adb,不会的可自行百度;
2、在Terminal中输入adb shell;
3、使用cd命令跳转到对应的目录先,我这里用到的是cd sdcard跳转到sdcard目录下,如果手机没有root权限,其他目录是cd不了的。
4、接下来就是找到我们打印的文件,可以使用 ls -l *_crash.log这个命令,就会列出当前目录下以_crash.log结尾的得所有文件,我保存的文件就是以_crash.log结尾;
root@generic_x86:/sdcard # ls -l *.log
-rwxrwx--- root sdcard_r 725 2018-04-10 03:12 com.example.ubt.myapplication2018-04-10_crash.log
5、找到文件后就剩打开文件了,使用cat filename即可查看,如下:
root@generic_x86:/sdcard # cat com.example.ubt.myapplication2018-04-10_crash.log
6、如果出现答应中文是乱码的情况,说明编码方式不对,退出shell的模式,使用chcp 65001换成utf-8编码,默认情况下是gbk编码,如果想改回gbk编码,可以使用chcp 936.
使用cat命令后输出的信息如下:
java.lang.NullPointerException
at com.example.ubt.myapplication.TestStudioActivity$1.onClick(TestStudioActivity.java:53)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19866)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
使用这样的方式查看日志就不必从sdcard中拷贝出来,而且看起来也很舒服。