fred-ye / summary

my blog
43 stars 9 forks source link

[Android]异常的捕获 Thread.UncaughtExceptionHandler #24

Open fred-ye opened 10 years ago

fred-ye commented 10 years ago

Android开发中,由于生产环境的复杂性,各种各样的问题难免会造成App的Crash。为此,我们通常需要定义一个CrashHandler来从全局的角度去捕获各种异常。

Android开发中,从功能来说有两种类型的线程,主线程(UI线程)和子线程。由于线程的本质特性,我们不能捕获从线程中逃逸出来的异常,一旦异常逃出任务的run()方法,便会向外传播。如下:

public class TestExceptionThread {
    public static void main(String args[]) {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new ExceptionThread());
    }
}
class ExceptionThread implements Runnable {
    public void run () {
        throw new RuntimeException();
    }
}

毫无疑问,这段代码执行的时候肯定会报错。异常信息如下:

Exception in thread "pool-1-thread-1" java.lang.RuntimeException
    at ExceptionThread.run(TestExceptionThread.java:13)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

于是,我们将代码做一下改动,添加一个try ... catch试试,改动如下:

public class TestExceptionThread {
    public static void main(String args[]) {
        ExecutorService exec = Executors.newCachedThreadPool();
        try {
            exec.execute(new ExceptionThread());
        } catch (RuntimeException e) {
            System.out.println("Exception has been handler");
        }
    }
}

运行程序,运行的结果和上面一下,异常信息还是没有捕获到。

此时,就该用Thread.UncaughtExceptionHandler了。它是Java SE5中提供的一个接口,允许在每个Thread对象上附着一个异常处理器,该接口中的uncaughtException()方法会在线程因未捕获到的异常而临近死亡前被调用。

第一种方式

代码如下:

public class TestExceptionThread {
    public static void main(String args[]) {
        try {
            Thread t = new Thread(new ExceptionThread());
            t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
            t.start();
        } catch (RuntimeException e) {
            System.out.println("Exception has been handler");
        }
    }
}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("got unCaughtException");
    }

}
class ExceptionThread implements Runnable {
    @Override
    public void run () {
        throw new RuntimeException();
    }
}

输出结果是

got unCaughtException

但是,如果我们将main(String args[])方法中的内容改成这样:

    public static void main(String args[]) {
        ExecutorService exec = Executors.newCachedThreadPool();
        Thread t = new Thread(new ExceptionThread());
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        exec.execute(t);
    }

发现运行结果会是这样:

Exception in thread "pool-1-thread-1" java.lang.RuntimeException
    at ExceptionThread.run(TestExceptionThread.java:31)
    at java.lang.Thread.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

也就是说: 如果这样写,线程中的异常还是没有捕捉到。

第二种方式

public class TestExceptionThread {
    public static void main(String args[]) {
        ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
        Thread t = new Thread(new ExceptionThread());
        exec.execute(t);
    }
}
class HandlerThreadFactory implements ThreadFactory{

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        System.out.println("Create thread t");
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        System.out.println("eh=" + t.getUncaughtExceptionHandler());
        return t;
    }
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("got unCaughtException");
    }
}
class ExceptionThread implements Runnable {
    @Override
    public void run () {
        System.out.println("eh=" + Thread.currentThread().getUncaughtExceptionHandler());
        throw new RuntimeException();
    }
}

运行结果如下:

Create thread t
eh=MyUncaughtExceptionHandler@10d448
eh=MyUncaughtExceptionHandler@10d448
got unCaughtException

第三种方式

我们可以采用在Thread类中设置一个静态域,代码如下:

public class TestExceptionThread {
    public static void main(String args[]) {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        Thread t = new Thread(new ExceptionThread());
        t.start();
    }
}

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("got unCaughtException");
    }
}

class ExceptionThread implements Runnable {
    @Override
    public void run () {
        throw new RuntimeException();
    }
}

同样会发现,异常会捕捉到。需要注意一点是这个处理器只有在不存在线程专有的末捕获异常处理器的情况下才会被调用。系统会检查线程专有版本,如果没有发现,则检查线程组是否有其专有的uncaughtException()方法,如果也没有,再调用defaultUncaughtExceptionHandler。

public class CrashHandler implements UncaughtExceptionHandler{
    private static CrashHandler INSTANCE ;

    private CrashHandler(){
    }
    //一个简单的单例模式
    public static synchronized CrashHandler getInstance(){
        if (INSTANCE == null) {
            INSTANCE = new CrashHandler();
        }
        return INSTANCE;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        //一些自定义的需要,比如,将crash的异常信息发给服务器。
    }
}
public class MyApplication extends Application {
    @Override  
    public void onCreate() {  
        super.onCreate();  
        CrashHandler handler = CrashHandler.getInstance();  
        Thread.setDefaultUncaughtExceptionHandler(handler);  
    }  
}

完成