欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

深入理解 Handler 消息机制

程序员文章站 2023-01-01 23:07:55
记得很多年前的一次面试中,面试官问了这么一个问题, 他的本意应该是考察 RxJava 的使用,只是我的答案是 ,他也就没有再追问下去了。在早期 Android 开发的荒芜时代,Handler 的确承担了项目中大部分的线程切换工作,通常包括子线程更新 UI 和消息传递。不光在我们自己的应用中,在整个 ......

记得很多年前的一次面试中,面试官问了这么一个问题,你在项目中一般如何实现线程切换? 他的本意应该是考察 rxjava 的使用,只是我的答案是 handler,他也就没有再追问下去了。在早期 android 开发的荒芜时代,handler 的确承担了项目中大部分的线程切换工作,通常包括子线程更新 ui 和消息传递。不光在我们自己的应用中,在整个 android 体系中,handler 消息机制也是极其重要的,不亚于 binder 的地位。 activitythread.java 中的内部类 h 就是一个 handler,它内部定义了几十种消息类型来处理一些系统事件。

handler 的重要性毋庸置疑,今天就通过 aosp 源码来深入学习 handler。相关类的源码包含注释均已上传到我的 github 仓库 :

handler.java

looper.java

message.java

messagequeue.java

handler

handler 用来发送和处理线程对应的消息队列 messagequeue 中存储的 message。每个 handler 实例对应一个线程以及该线程的消息队列。当你创建一个新的 handler,它会绑定创建它的线程和消息队列,然后它会向消息队列发送 message 或者 runnable,并且在它们离开消息队列时执行。

handler 有两个主要用途:

  1. 规划 message 或者 runnable 在未来的某个时间点执行
  2. 在另一个线程上执行代码

以上翻译自官方注释。说白了,handler 只是安卓提供给开发者用来发送和处理事件的,而消息如何存储,消息如何循环取出,这些逻辑则交给 messagequeuelooper 来处理,使用者并不需要关心。但要真正了解 handler 消息机制,认真读一遍源码就必不可少了。

构造函数

handler 的构造函数大致上可以分为两类,先来看第一类:

public handler() {
    this(null, false);
}

public handler(callback callback) {
    this(callback, false);
}

public handler(callback callback, boolean async) {
    // 如果是匿名类、内部类、本地类,且没有使用 static 修饰符,提示可能导致内存泄漏
    if (find_potential_leaks) {
        final class<? extends handler> klass = getclass();
        if ((klass.isanonymousclass() || klass.ismemberclass() || klass.islocalclass()) &&
                (klass.getmodifiers() & modifier.static) == 0) {
            log.w(tag, "the following handler class should be static or leaks might occur: " +
                klass.getcanonicalname());
        }
    }

    // 从当前线程的 threadlocal获取 looper
    mlooper = looper.mylooper();
    if (mlooper == null) {  // 创建 handler 之前一定要先创建 looper。主线程已经自动为我们创建。
        throw new runtimeexception(
            "can't create handler inside thread " + thread.currentthread()
                    + " that has not called looper.prepare()");
    }
    mqueue = mlooper.mqueue; // looper 持有一个 messagequeue
    mcallback = callback; // handlemessage 回调
    masynchronous = async; // 是否异步处理
}

这一类构造函数最终调用的都是两个参数的方法,参数中不传递 looper,所以要显式检查是否已经创建 looper。创建 handler 之前一定要先创建 looper,否则会直接抛出异常。在主线程中 looper 已经自动创建好,无需我们手动创建,在 activitythread.javamain() 方法中可以看到。looper 持有一个消息队列 messagequeue,并赋值给 handler 中的 mqueue 变量。callback 是一个接口,定义如下:

public interface callback {
    public boolean handlemessage(message msg);
}

通过构造器参数传入 callback 也是 handler 处理消息的一种实现方式。

再回头看一下在上面的构造函数中是如何获取当前线程的 looper 的?

 mlooper = looper.mylooper(); // 获取当前线程的 looper

这里先记着,回头看到 looper 源码时再详细解析。

看过 handler 的第一类构造函数,第二类其实就很简单了,只是多了 looper 参数而已:

public handler(looper looper) {
    this(looper, null, false);
}
    
public handler(looper looper, callback callback) {
    this(looper, callback, false);
}
    
public handler(looper looper, callback callback, boolean async) {
    mlooper = looper;
    mqueue = looper.mqueue;
    mcallback = callback;
    masynchronous = async;
}

直接赋值即可。

除此之外还有几个标记为 @hide 的构造函数就不作说明了。

发送消息

发送消息大家最熟悉的方法就是 sendmessage(message msg) 了,可能有人不知道其实还有 post(runnable r) 方法。虽然方法名称不一样,但最后调用的都是同一个方法。

sendmessage(message msg)
sendemptymessage(int what)
sendemptymessagedelayed(int what, long delaymillis)
sendemptymessageattime(int what, long uptimemillis)
sendmessageattime(message msg, long uptimemillis)

几乎所有的 sendxxx() 最后调用的都是 sendmessageattime() 方法。

post(runnable r)
postattime(runnable r, long uptimemillis)
postattime(runnable r, object token, long uptimemillis)
postdelayed(runnable r, long delaymillis)
postdelayed(runnable r, object token, long delaymillis)

所有的 postxxx() 方法都是调用 getpostmessage() 将 参数中的 runnable 包装成 message,再调用对应的 sendxxx() 方法。看一下 getpostmessage() 的代码:

private static message getpostmessage(runnable r) {
    message m = message.obtain();
    m.callback = r;
    return m;
}

private static message getpostmessage(runnable r, object token) {
    message m = message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}

主要是把参数中的 runnable 赋给 message 的 callback 属性。

殊途同归,发送消息的重任最后都落在了 sendmessageattime() 身上。

public boolean sendmessageattime(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(messagequeue queue, message msg, long uptimemillis) {
    msg.target = this;
    if (masynchronous) {
        msg.setasynchronous(true);
    }
    return queue.enqueuemessage(msg, uptimemillis); // 调用 messagequeue 的 enqueuemessage() 方法
}

handler 就是一个撒手掌柜,发送消息的任务转手又交给了 messagequeue 来处理。

再额外提一点,enqueuemessage() 方法中的参数 uptimemillis 并不是我们传统意义上的时间戳,而是调用 systemclock.updatemillis() 获取的,它表示自开机以来的毫秒数。

messagequeue

enqueuemessage()

message 的入队工作实际上是由 messagequeue 通过 enqueuemessage() 函数来完成的。

boolean enqueuemessage(message msg, long when) {
    if (msg.target == null) { // msg 必须有 target
        throw new illegalargumentexception("message must have a target.");
    }
    if (msg.isinuse()) { // msg 不能正在被使用
        throw new illegalstateexception(msg + " this message is already in use.");
    }

    synchronized (this) {
        if (mquitting) { // 正在退出,回收消息并直接返回
            illegalstateexception e = new illegalstateexception(
                    msg.target + " sending message to a handler on a dead thread");
            log.w(tag, e.getmessage(), e);
            msg.recycle();
            return false;
        }

        msg.markinuse();
        msg.when = when;
        message p = mmessages;
        boolean needwake;
        if (p == null || when == 0 || when < p.when) {
            // new head, wake up the event queue if blocked.
            // 插入消息队列头部,需要唤醒队列
            msg.next = p;
            mmessages = msg;
            needwake = mblocked;
        } else {
            // inserted within the middle of the queue.  usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needwake = mblocked && p.target == null && msg.isasynchronous();
            message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) { // 按消息的触发时间顺序插入队列
                    break;
                }
                if (needwake && p.isasynchronous()) {
                    needwake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // we can assume mptr != 0 because mquitting is false.
        if (needwake) {
            nativewake(mptr);
        }
    }
    return true;
}

从源码中可以看出来,messagequeue 是用链表结构来存储消息的,消息是按触发时间的顺序来插入的。

enqueuemessage() 方法是用来存消息的,既然存了,肯定就得取,这靠的是 next() 方法。

next()

message next() {
    // return here if the message loop has already quit and been disposed.
    // this can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mptr;
    if (ptr == 0) {
        return null;
    }

    int pendingidlehandlercount = -1; // -1 only during first iteration
    int nextpolltimeoutmillis = 0;
    for (;;) {
        if (nextpolltimeoutmillis != 0) {
            binder.flushpendingcommands();
        }

        // 阻塞方法,主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。
        // 如果 nextpolltimeoutmillis = -1,一直阻塞不会超时。
        // 如果 nextpolltimeoutmillis = 0,不会阻塞,立即返回。
        // 如果 nextpolltimeoutmillis > 0,最长阻塞nextpolltimeoutmillis毫秒(超时),如果期间有程序唤醒会立即返回。
        nativepollonce(ptr, nextpolltimeoutmillis);

        synchronized (this) {
            // try to retrieve the next message.  return if found.
            final long now = systemclock.uptimemillis();
            message prevmsg = null;
            message msg = mmessages;
            if (msg != null && msg.target == null) {
                // stalled by a barrier.  find the next asynchronous message in the queue.
                // msg.target == null表示此消息为消息屏障(通过postsyncbarrier方法发送来的)
                // 如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),
                // 所有同步消息都将忽略(平常发送的一般都是同步消息)
                do {
                    prevmsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isasynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 消息触发时间未到,设置下一次轮询的超时时间
                    // next message is not ready.  set a timeout to wake up when it is ready.
                    nextpolltimeoutmillis = (int) math.min(msg.when - now, integer.max_value);
                } else {
                    // got a message.
                    // 得到 message
                    mblocked = false;
                    if (prevmsg != null) {
                        prevmsg.next = msg.next;
                    } else {
                        mmessages = msg.next;
                    }
                    msg.next = null;
                    if (debug) log.v(tag, "returning message: " + msg);
                    msg.markinuse(); // 标记 flag_in_use
                    return msg;
                }
            } else {
                // no more messages.
                // 没有消息,会一直阻塞,直到被唤醒
                nextpolltimeoutmillis = -1;
            }

            // process the quit message now that all pending messages have been handled.
            if (mquitting) {
                dispose();
                return null;
            }

            // if first time idle, then get the number of idlers to run.
            // idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            // idle handle 仅当队列为空或者队列中的第一个消息将要执行时才会运行
            if (pendingidlehandlercount < 0
                    && (mmessages == null || now < mmessages.when)) {
                pendingidlehandlercount = midlehandlers.size();
            }
            if (pendingidlehandlercount <= 0) {
                // no idle handlers to run.  loop and wait some more.
                // 没有 idle handler 需要运行,继续循环
                mblocked = true;
                continue;
            }

            if (mpendingidlehandlers == null) {
                mpendingidlehandlers = new idlehandler[math.max(pendingidlehandlercount, 4)];
            }
            mpendingidlehandlers = midlehandlers.toarray(mpendingidlehandlers);
        }

        // run the idle handlers.
        // we only ever reach this code block during the first iteration.
        // 只有第一次循环时才会执行下面的代码块
        for (int i = 0; i < pendingidlehandlercount; i++) {
            final idlehandler idler = mpendingidlehandlers[i];
            mpendingidlehandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueidle();
            } catch (throwable t) {
                log.wtf(tag, "idlehandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    midlehandlers.remove(idler);
                }
            }
        }

        // reset the idle handler count to 0 so we do not run them again.
        // 将 pendingidlehandlercount 置零保证不再运行
        pendingidlehandlercount = 0;

        // while calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextpolltimeoutmillis = 0;
    }
}

next() 方法是一个死循环,但是当没有消息的时候会阻塞,避免过度消耗 cpu。nextpolltimeoutmillis 大于 0 时表示等待下一条消息需要阻塞的时间。等于 -1 时表示没有消息了,一直阻塞到被唤醒。

这里的阻塞主要靠 native 函数 nativepollonce() 来完成。其具体原理我并不了解,想深入学习的同学可以参考 gityuan 的相关文 android消息机制2-handler(native层)

messagequeue 提供了消息入队和出队的方法,但它自己并不是自动取消息。那么,谁来把消息取出来并执行呢?这就要靠 looper 了。

looper

创建 handler 之前必须先创建 looper,而主线程已经为我们自动创建了 looper,无需再手动创建,见 activitythread.javamain() 方法:

public static void main(string[] args) {
...
 looper.preparemainlooper(); // 创建主线程 looper
...
}

preparemainlooper()

public static void preparemainlooper() {
    prepare(false);
    synchronized (looper.class) {
        if (smainlooper != null) {
            throw new illegalstateexception("the main looper has already been prepared.");
        }
        smainlooper = mylooper();
    }
}

smainlooper 只能被初始化一次,也就是说 preparemainlooper() 只能调用一次,否则将直接抛出异常。

prepare()

public static void prepare() {
        prepare(true);
}

private static void prepare(boolean quitallowed) {
    // 每个线程只能执行一次 prepare(),否则会直接抛出异常
    if (sthreadlocal.get() != null) {
        throw new runtimeexception("only one looper may be created per thread");
    }
    // 将 looper 存入 threadlocal
    sthreadlocal.set(new looper(quitallowed));
}

主线程中调用的是 prepare(false),说明主线程 looper 是不允许退出的。因为主线程需要源源不断的处理各种事件,一旦退出,系统也就瘫痪了。而我们在子线程调用 prepare() 来初始化 looper时,默认调动的是 prepare(true),子线程 looper 是允许退出的。

每个线程的 looper 是通过 threadlocal 来存储的,保证其线程私有。

再回到文章开头介绍的 handler 的构造函数中 mlooper 变量的初始化:

mlooper = looper.mylooper();
public static @nullable looper mylooper() {
    return sthreadlocal.get();
}

也是通过当前线程的 threadlocal 来获取的。

构造函数

private looper(boolean quitallowed) {
    mqueue = new messagequeue(quitallowed); // 创建 messagequeue
    mthread = thread.currentthread(); // 当前线程
}

再对照 handler 的构造函数:

public handler(looper looper, callback callback, boolean async) {
    mlooper = looper;
    mqueue = looper.mqueue;
    mcallback = callback;
    masynchronous = async;
}

其中的关系就很清晰了。

  • looper 持有 messagequeue 对象的引用
  • handler 持有 looper 对象的引用以及 looper 对象的 messagequeue 的引用

loop()

看到这里,消息队列还没有真正的运转起来。我们先来看一个子线程使用 handler 的标准写法:

class looperthread extends thread {
    public handler mhandler;
  
    public void run() {
        looper.prepare();
  
        mhandler = new handler() {
            public void handlemessage(message msg) {
                // process incoming messages here
            }
        };
  
        looper.loop();
    }
}

让消息队列转起来的核心就是 looper.loop()

public static void loop() {
    final looper me = mylooper(); // 从 threadlocal 中获取当前线程的 looper
    if (me == null) {
        throw new runtimeexception("no looper; looper.prepare() wasn't called on this thread.");
    }
    final messagequeue queue = me.mqueue; // 获取当前线程的消息队列

   ...  // 省略部分代码

    for (;;) { // 循环取出消息,没有消息的时候可能会阻塞
        message msg = queue.next(); // might block
        if (msg == null) {
            // no message indicates that the message queue is quitting.
            return;
        }

        ...  // 省略部分代码
       

        try {
            msg.target.dispatchmessage(msg); // 通过 handler 分发 message
            dispatchend = needendtime ? systemclock.uptimemillis() : 0;
        } finally {
            if (tracetag != 0) {
                trace.traceend(tracetag);
            }
        }

        ...  // 省略部分代码

        msg.recycleunchecked(); // 将消息放入消息池,以便重复利用
    }
}

简单说就是一个死循环不停的从 messagequeue 中取消息,取到消息就通过 handler 来进行分发,分发之后回收消息进入消息池,以便重复利用。

从消息队列中取消息调用的是 messagequeue.next() 方法,之前已经分析过。在没有消息的时候可能会阻塞,避免死循环消耗 cpu。

取出消息之后进行分发调用的是 msg.target.dispatchmessage(msg)msg.target 是 handler 对象,最后再来看看 handler 是如何分发消息的。

public void dispatchmessage(message msg) {
    if (msg.callback != null) { // callback 是 runnable 类型,通过 post 方法发送
        handlecallback(msg);
    } else {
        if (mcallback != null) { // handler 的 mcallback参数 不为空时,进入此分支
            if (mcallback.handlemessage(msg)) {
                return;
            }
        }
        handlemessage(msg); // handler 子类实现的  handlemessage 逻辑
    }
}

private static void handlecallback(message message) {
    message.callback.run();
}
  • message 的 callback 属性不为空时,说明消息是通过 postxxx() 发送的,直接执行 runnable 即可。
  • handler 的 mcallback 属性不为空,说明构造函数中传入了 callback 实现,调用 mcallback.handlemessage(msg) 来处理消息
  • 以上条件均不满足,只可能是 handler 子类重写了 handlemessage() 方法。这好像也是我们最常用的一种形式。

message

之所以把 message 放在最后说,因为我觉得对整个消息机制有了一个完整的深入认识之后,再来了解 message 会更加深刻。首先来看一下它有哪些重要属性:

int what :消息标识
int arg1 : 可携带的 int 值
int arg2 : 可携带的 int 值
object obj : 可携带内容
long when : 超时时间
handler target : 处理消息的 handler
runnable callback : 通过 post() 发送的消息会有此参数

message 有 public 修饰的构造函数,但是一般不建议直接通过构造函数来构建 message,而是通过 message.obtain() 来获取消息。

obtain()

public static message obtain() {
    synchronized (spoolsync) {
        if (spool != null) {
            message m = spool;
            spool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            spoolsize--;
            return m;
        }
    }
    return new message();
}

spool 是消息缓存池,链表结构,其最大容量 max_pool_size 为 50。obtain() 方法会直接从消息池中取消息,循环利用,节约资源。当消息池为空时,再去新建消息。

recycleunchecked()

还记得 looper.loop() 方法中最后会调用 msg.recycleunchecked() 方法吗?这个方法会回收已经分发处理的消息,并放入缓存池中。

void recycleunchecked() {
    // mark the message as in use while it remains in the recycled object pool.
    // clear out all other details.
    flags = flag_in_use;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyto = null;
    sendinguid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (spoolsync) {
        if (spoolsize < max_pool_size) {
            next = spool;
            spool = this;
            spoolsize++;
        }
    }
}

总结

说到这里,handler 消息机制就全部分析完了,相信大家也对整个机制了然于心了。

  • handler 被用来发送消息,但并不是真正的自己去发送。它持有 messagequeue 对象的引用,通过 messagequeue 来将消息入队。
  • handler 也持有 looper 对象的引用,通过 looper.loop() 方法让消息队列循环起来。
  • looper 持有 messagequeue 对象应用,在 loop() 方法中会调用 messagequeue 的 next() 方法来不停的取消息。
  • loop() 方法中取出来的消息最后还是会调用 handler 的 dispatchmessage() 方法来进行分发和处理。

最后,关于 handler 一直有一个很有意思的面试题:

looper.loop() 是死循环为什么不会卡死主线程 ?

看起来问的好像有点道理,实则不然。你仔细思考一下,loop() 方法的死循环和卡死主线程有任何直接关联吗?其实并没有。

回想一下我们经常在测试代码时候写的 main() 函数:

public static void main(){
    system.out.println("hello world");
}

姑且就把这里当做主线程,它里面没有死循环,执行完就直接结束了,没有任何卡顿。但是问题是它就直接结束了啊。在一个 android 应用的主线程上,你希望它直接就结束了吗?那肯定是不行的。所以这个死循环是必要的,保证程序可以一直运行下去。android 是基于事件体系的,包括最基本的 activity 的生命周期都是由事件触发的。主线程 handler 必须保持永远可以相应消息和事件,程序才能正常运行。

另一方面,这并不是一个时时刻刻都在循环的死循环,当没有消息的时候,loop() 方法阻塞,并不会消耗大量 cpu 资源。

关于 handler 就说到这里了。还记得文章说过线程的 looper 对象是保存在 threadlocal 中的吗?下一篇文章就来说说 threadlocal 是如何保存 线程局部变量 的。

文章首发微信公众号: 秉心说 , 专注 java 、 android 原创知识分享,leetcode 题解。

更多最新原创文章,扫码关注我吧!

深入理解 Handler 消息机制