Android面试之Handler简述

作为Android开发者,在面试的过程中,handler这个知识点很大概率会被问到,今天通过源码来了解下这个handler。

Looper和MessageQueue

准备

要了解Handler的原理,我们先来了解两个类,分别是Looper,MessageQueue。因为只有这两个类都准备好的前提下,handler才有作用。
在Android应用中,我们都知道,其入口是ActivityThread的main函数

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();

    ...

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

首先我们跟进prepareMainLooper函数内部,代码并不多,主要是创建Looper。
相关代码如下:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

在私有构造函数中看到,Looper其内部有个mQueue的变量是MessageQueue类型的,此时Looper,MessageQueue都已经准备好了。

Looper和MessageQueue如何运行的

回到main函数的Looper.loop();这个方法

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }   
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }
    ...
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

首先第2~4行,异常message说的很明确,同时前面笔者所说的只有在Looper和MessageQueue都准备好,handler才有效,也是这个原因。而且MessageQueue是Looper中的一个成员变量,所以Looper.prepare()内部都已准备好。
最后是一个for循环,再跟进去看,我们暂时只关注handler相关的部份,主要代码如下:

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    ...
    
    try {
        msg.target.dispatchMessage(msg);
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
        if (observer != null) {
            observer.dispatchingThrewException(token, msg, exception);
        }
        throw exception;
    } finally {
        ThreadLocalWorkSource.restore(origWorkSource);
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }
    ...

    msg.recycleUnchecked();

    return true;
}

通过MessageQueue.next方法取出消息,对该消息进行处理,并在最后释放消息内容。
到这里,我们了解到了,在Android应用程序启动是,首先调用了Looper.prepareMainLooper进行主线程Looper的准备工作,然后在调用Looper的loop方法进入死循环,在循环内部通过MessageQueue的next方法获取消息并进行处理。在Looper中值得注意的是两个变量,分别是sThreadLocalmQueuesThreadLocal是保证了当前looper只属于当前线程,而mQueue是looper的消息队列。

注: 留意这里的msg.target.dispatchMessage(msg);,后面会提到

Handler的原理

Handler的使用

首先我们需要初始化一个Handler

private val handler = Handler(Looper.getMainLooper()) {
    println("===== callback thread: ${Thread.currentThread().name}")
    println("===== ${it.what}")
    println("===== ${it.data.getString("Content")}")
    true
}

在子线程send messge

GlobalScope.launch(Dispatchers.IO) {
    println("===== send thread: ${Thread.currentThread().name}")
    handler.sendMessage(Message.obtain().apply {
        this.data = bundleOf("Content" to "Hello world!!!")
        this.what = 1
    });
}

注:这里的代码在Activity/Fragment中的,handler也是通过主线程创建的

现在我们知道handler如何使用了,接下来跟踪代码来理解,首先来看看sendMessage方法,相关代码全部贴出来

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

看到最终调用的是enqueueMessage方法,注意msg.target = this;queue.enqueueMessage(msg, uptimeMillis);
先来看queue.enqueueMessage(msg, uptimeMillis);,这个queue就是mQueue,而mQueue在初始化Handler的时候赋值的mQueue = mLooper.mQueue;,也就是说调用sendMessage方法最终会通过looper的messageQueue将这个消息入队列之中。再根据前面我们说的,loop函数会一直查看消息队列的消息,出队列时做处理。
现在看msg.target = this;,为什么要关注这个呢? 首先这个方法是在Handler类之中的,这个this指的就是当前的handler。
前文提到looper会冲消息队列通过next出队并处理,其中就有一行代码msg.target.dispatchMessage(msg);
现在来看Handler的dispatchMessage(msg)方法

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

使用例子中的case,msg是没有传callback的,所以其实这里调用了mCallback.handleMessage(msg)。也就是我们创建Handler实例的回调。
到这里,Handler与Looper、MessageQueue、Message之间的关系讲完了。

扩展

  1. 子线程能否使用handler?如果可以需要注意什么?如果没有消息后应该如何处理?
    Handler其实就是一个类,当然可以在子线程中使用。子线程handler创建的时候一定需要设定一个Looper给这个handler(Looper.prepare()),否则会在调用loop() 的时候抛出Runtime异常。主要是如下代码

    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }   

    当没有消息的时候,我们应该调用looper.quit() 方法释放资源,否则会因为死循环导致线程内存泄漏。

  2. Handler在等待新消息的时候会阻塞吗? 如果会,那会不会导致ANR?
    Handler在等待新消息的时候其实就是在死循环的过程,也称为looper阻塞。所以是会阻塞的。但不会导致ANR,因为Android系统本来就是事件驱动的,looper.loop不断接受事件和处理事件,如果它停止了,App也就停止了,所以这个问题本身就是假命题。
    另一种说法就是ANR本来就是类似定时炸弹一样的设计,在事件发送时埋下定时炸弹,如果在规定的时间内没有收到处理完的通知,则判定为超时,引爆炸弹(ANR),收到则拆除炸弹。而Looper在loop的过程并没埋下这个定时炸弹

总结

通过跟踪源码,我们清晰了解到了Handler处理消息的过程。

  1. 首先在程序入口调用Looper.prepareMainLooper()方法,提供主线程Looper和MessageQueue
  2. 然后继续调用Looper的loop方法进入死循环,通过MessageQueue的next方法取得消息并处理
  3. 取得的消息怎么来的? Handler send/post(post方式会传入Runable给message)来的
  4. 在send过程中,handler会被赋给message.target
  5. 取得的消息中,Message拿到target,即为handler,处理消息就是通过handler.dispatchMessage方法调用回调方法返回主线程处理(因为这里的handler是在主线程创建的)