UncaughtExceptionHandler使用分析

本文介绍了一种在Android应用中捕获未处理异常的方法,通过实现UncaughtExceptionHandler接口并设置异常处理类,可以在应用崩溃时收集相关信息,便于后续的错误定位与修复。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在做一个项目过程中,集成了一个收集崩溃日志的sdk,方便收集app发布出去后的崩溃信息,以便在迭代过程中修复老版本的bug。

1、解决方案

为了捕获应用运行时异常,可实现UncaughtExceptionHandler接口,并通过Thread.getDefaultUncaughtExceptionHandler()接口将异常处理类设置到线程上即可。

a) 自定义异常处理类CrashHandler。

public class CrashHandler implements UncaughtExceptionHandler {
	private UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;
	
	public CrashHandler() {
		mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
		Thread.setDefaultUncaughtExceptionHandler(this);
	}

	@Override
	public void uncaughtException(Thread arg0, Throwable arg1) {
		Log.d("crashhandler", arg0.getThreadGroup().getName(), arg1);
		if (mDefaultUncaughtExceptionHandler != null) {
			mDefaultUncaughtExceptionHandler.uncaughtException(arg0, arg1);
		}
	}

}

b) 设置异常处理类。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        CrashHandler ch = new CrashHandler();
        
        findViewById(R.id.main).setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				throw new NullPointerException("test CrashHandler");
			}
		});
    }
}

2、分析方案

解决方案很简单,但是不是能完全解决问题呢?
我们一起来看看关于java.lang.Thread.UncaughtExceptionHandler接口在Android官网的描述:
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.
大概意思是实现UncaughtExceptionHandler接口可以处理因未捕获的异常导致线程终止的情况。这种情况下,handler会被告知被终止的线程和出现的异常信息。如果没有明确设置handler则thread group就是默认的handler。

问题1:handler的机制是什么?

分析这个问题,不妨从源码上着手。因为我们的代码都是在线程中执行,因此可以从Thread类开始分析。
    public synchronized void start() {
        if (hasBeenStarted) {
            throw new IllegalThreadStateException("Thread already started."); // TODO Externalize?
        }

        hasBeenStarted = true;

        VMThread.create(this, stackSize);
    }
Thread都是调用start()函数开始,函数内部判断线程未开始运行会调用VMThread.create()函数,而VMThread.create()是个native方法,c++代码实现在java_lang_VMThread.cpp。
static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult)
{
    Object* threadObj = (Object*) args[0];
    s8 stackSize = GET_ARG_LONG(args, 1);

    /* copying collector will pin threadObj for us since it was an argument */
    dvmCreateInterpThread(threadObj, (int) stackSize);
    RETURN_VOID();
}
VMThread.create()进而调用dvmCreateInterpThread()函数(位于Thread.cpp)。
bool dvmCreateInterpThread(Object* threadObj, int reqStackSize)
{
    ......

    pthread_t threadHandle;
    int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart,
                            newThread);
    
	......
}
dvmCreateInterpThread()函数内部创建了线程,线程入口是interpThreadStart()。
static void* interpThreadStart(void* arg)
{
    ......

    /*
     * Execute the "run" method.
     *
     * At this point our stack is empty, so somebody who comes looking for
     * stack traces right now won't have much to look at.  This is normal.
     */
    Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run];
    JValue unused;

    LOGV("threadid=%d: calling run()", self->threadId);
    assert(strcmp(run->name, "run") == 0);
    dvmCallMethod(self, run, self->threadObj, &unused);
    LOGV("threadid=%d: exiting", self->threadId);

    /*
     * Remove the thread from various lists, report its death, and free
     * its resources.
     */
    dvmDetachCurrentThread();

    return NULL;
}
interpThreadStart()内部完成了线程初始化操作,并调用run方法,之后调用dvmDetachCurrentThread()释放资源。
void dvmDetachCurrentThread()
{
    ......

    /*
     * Do some thread-exit uncaught exception processing if necessary.
     */
    if (dvmCheckException(self))
        threadExitUncaughtException(self, group);

    ......
}
vmDetachCurrentThread()函数中发现存在异常,调用threadExitUncaughtException()函数。
/*
 * The current thread is exiting with an uncaught exception.  The
 * Java programming language allows the application to provide a
 * thread-exit-uncaught-exception handler for the VM, for a specific
 * Thread, and for all threads in a ThreadGroup.
 *
 * Version 1.5 added the per-thread handler.  We need to call
 * "uncaughtException" in the handler object, which is either the
 * ThreadGroup object or the Thread-specific handler.
 *
 * This should only be called when an exception is pending.  Before
 * returning, the exception will be cleared.
 */
static void threadExitUncaughtException(Thread* self, Object* group)
{
    Object* exception;
    Object* handlerObj;
    Method* uncaughtHandler;

    LOGW("threadid=%d: thread exiting with uncaught exception (group=%p)",
        self->threadId, group);
    assert(group != NULL);

    /*
     * Get a pointer to the exception, then clear out the one in the
     * thread.  We don't want to have it set when executing interpreted code.
     */
    exception = dvmGetException(self);
    assert(exception != NULL);
    dvmAddTrackedAlloc(exception, self);
    dvmClearException(self);

    /*
     * Get the Thread's "uncaughtHandler" object.  Use it if non-NULL;
     * else use "group" (which is an instance of UncaughtExceptionHandler).
     * The ThreadGroup will handle it directly or call the default
     * uncaught exception handler.
     */
    handlerObj = dvmGetFieldObject(self->threadObj,
            gDvm.offJavaLangThread_uncaughtHandler);
    if (handlerObj == NULL)
        handlerObj = group;

    /*
     * Find the "uncaughtException" method in this object.  The method
     * was declared in the Thread.UncaughtExceptionHandler interface.
     */
    uncaughtHandler = dvmFindVirtualMethodHierByDescriptor(handlerObj->clazz,
            "uncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V");

    if (uncaughtHandler != NULL) {
        //LOGI("+++ calling %s.uncaughtException",
        //     handlerObj->clazz->descriptor);
        JValue unused;
        dvmCallMethod(self, uncaughtHandler, handlerObj, &unused,
            self->threadObj, exception);
    } else {
        /* should be impossible, but handle it anyway */
        LOGW("WARNING: no 'uncaughtException' method in class %s",
            handlerObj->clazz->descriptor);
        dvmSetException(self, exception);
        dvmLogExceptionStackTrace();
    }

    /* if the uncaught handler threw, clear it */
    dvmClearException(self);

    dvmReleaseTrackedAlloc(exception, self);

    /* Remove this thread's suspendCount from global suspendCount sum */
    lockThreadSuspendCount();
    dvmAddToSuspendCounts(self, -self->suspendCount, 0);
    unlockThreadSuspendCount();
}
从threadExitUncaughtException()函数中可知道,优先使用当前thread设置的uncaughtHandler,如果未设置使用线程所属的ThreadGroup,因为ThreadGrooup实现了UncaughtExceptionHandler
从上面的函数调用关系可以分析出,线程执行完后,如果出现异常,最终都会走到UncaughtExceptionHandler中,至于是哪个异常处理类,还要视情况而定,这个后面会继续分析。

问题2:设置handler只对当前线程生效吗?

是否只对当前线程有效,视情况而定,我们先一起看一下设置UncaughtExceptionHandler的接口。
public class Thread implements Runnable {
    /**
     * <p>
     * Sets the uncaught exception handler. This handler is invoked in case this
     * Thread dies due to an unhandled exception.
     * </p>
     *
     * @param handler
     *            The handler to set or <code>null</code>.
     */
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
        uncaughtHandler = handler;
    }
	
    /**
     * Sets the default uncaught exception handler. This handler is invoked in
     * case any Thread dies due to an unhandled exception.
     *
     * @param handler
     *            The handler to set or null.
     */
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) {
        Thread.defaultUncaughtHandler = handler;
    }
}
从代码注释上即可看出,setUncaughtExceptionHandler()设置的handler只对当前线程有效,而setDefaultUncaughtExceptionHandler()设置的handler是对所有线程生效的。从问题1的分析中也可得知,会优先使用setUncaughtExceptionHandler()设置的handler来处理未捕获的异常事件,不然会使用ThreadGroup作为默认的异常处理类。那setDefaultUncaughtExceptionHandler()怎么保证设置的handler对所有线程生效呢,答案在ThreadGroup里。
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    /* the VM uses these directly; do not rename */
    static final ThreadGroup mSystem = new ThreadGroup();
    static final ThreadGroup mMain = new ThreadGroup(mSystem, "main");
	
    public ThreadGroup(String name) {
        this(Thread.currentThread().getThreadGroup(), name);
    }

    public ThreadGroup(ThreadGroup parent, String name) {
        if (parent == null) {
            throw new NullPointerException("parent == null");
        }
        this.name = name;
        this.parent = parent;
        if (parent != null) {
            parent.add(this);
            this.setMaxPriority(parent.getMaxPriority());
            if (parent.isDaemon()) {
                this.setDaemon(true);
            }
        }
    }

    private ThreadGroup() {
        this.name = "system";
        this.parent = null;
    }
	
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else if (Thread.getDefaultUncaughtExceptionHandler() != null) {
            // TODO The spec is unclear regarding this. What do we do?
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            // No parent group, has to be 'system' Thread Group
            e.printStackTrace(System.err);
        }
    }
}
从ThreadGroup的uncaughtException可以分析出,会优先让parent处理,如果parent==null的时候交给DefaultUncaughtExceptionHandler来处理。而最终的mSystem这个ThreadGroup实例的parent==null,可以保证setDefaultUncaughtExceptionHandler可以对所有线程生效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值