• Handler机制实现原理总结


    Handler一般用于线程间通信,如常用的子线程使用handler让主线程更新UI。那么这是怎么实现的呢?
    我们先把这个大问题分解成多个小问题:

    1. post();postDelayed();sendMessage();sendEmptyMessage();等方法有什么不同?
    2. Handler为什么需要一个Looper,为什么它不能为空?
    3. Handler为什么可以做到线程间通信?
    4. postDelayed()为什么可以让线程延迟执行?

    接下来带着这些疑惑去寻找答案。

    post();postDelayed();sendMessage();sendEmptyMessage();等方法有什么不同?

    它们最终都是调用同一个方法:sendMessageAtTime(),只是参数不同,Handler帮我们进行了一下封装。

    先看post();postDelayed();这两个方法,查看源码可以发现,这两个方法都是调用sendMessageDelayed(Message, long)。只是post()的时间这个参数是0。
    代码如下:

    public final boolean post(@NonNull Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
    }

    关键是将Runable任务封装成Message的这个getPostMessage()
    这里并不是简单地将Runable封装成Message,这里还有一个Message回收池机制的实现。将在下文展开介绍。

    再看sendMessage();sendEmptyMessage();这两个方法:

    public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
    }
    public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
    }
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
    }

    可以看到这四个方法最终都是调用sendMessageDelayed():

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

    上面sendMessageDelayed()的实现都很简单,但需要注意的是这里使用了SystemClock.uptimeMillis(),它返回的是设备的开机时间(不包括息屏睡眠时间),这个时间和Handler的消息可以延迟触发有关。将在后面详细介绍。

    Handler为什么需要一个Looper,为什么它不能为空?

    因为MessageQueue是通过Looper获取到的。
    而Message需要通过MessageQueue来等待执行。
    在创建Handler时如果检测到Looper为空,将会抛出NullPointerException错误

    如果不是通过构造函数传入的Looper,比如Handler#Callback,构造方法会通过Looper.myLooper()获取到当前线程的Looper。

    Looper.myLooper()可以获取到当前线程的Looper是因为ThreadLocal的特性。

    代码如下:

    public Handler(@Nullable Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
    throw new RuntimeException(
    "Can't create handler inside thread " + Thread.currentThread()
    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
    }

    Handler为什么可以做到线程间通信?

    Handler最长使用的大概是子线程通知UI线程更新UI吧。

    我们通过post();sendMessage();等方法提交一个Message时,这个Message会被放入MessageQueue。在创建Handler时会得到一个Looper,Looper会循环从MessageQueue取出Message处理,而每个Looper属于一个线程,如果该Looper是UI线程的,那Message就是在UI线程处理。

    知其然亦应知其所以然。

    我们从Handler#post()这个方法开始研究。
    在上面已经讲过,post()最终调用的是sendMessageAtTime(),这个方法首先是获取了与该Handler绑定Looper的MessageQueue对象,然后通过一些参数设置,最后执行MessageQueue#enqueueMessage()方法。

    MessageQueue#enqueueMessage()

    看方法名就知道这个方法主要工作是对Message排队处理:

    boolean enqueueMessage(Message msg, long when) {
    //...
    synchronized (this) {
    if (msg.isInUse()) {
    throw new IllegalStateException(msg + " This message is already in use.");
    }
    //...
    msg.markInUse();
    msg.when = when;
    Message p = mMessages;
    boolean needWake;
    if (p == null || when == 0 || when < p.when) {
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
    } else {
    Message prev;
    for (;;) {
    prev = p;
    p = p.next;
    if (p == null || when < p.when) {
    break;
    }
    //...
    }
    msg.next = p; // invariant: p == prev.next
    prev.next = msg;
    }
    //...
    }
    return true;
    }

    我去掉了一些和排队无关的代码,上面这段代码并不难,就是以链表的形式将Message进行排列。
    首先判断这个Message是否正在被使用。

    Message什么情况下是被使用状态呢?

    其实这个也和Message回收池有些关系。
    我们new的对象和回收池中取出的Message默认状态是0,当Message进入MessageQueue等待处理时就是被使用状态。从MessageQueue取出,被处理完成回收的Message其状态又会被重置为0 。

    回到Message排队问题,然后判断三个条件:

    1. Message链表是否为空
    2. when == 0
      1. 这种情况只有调用Handler#sendMessageAtFrontOfQueue()才会出现
    3. when < p.when
      1. when表示消息将在什么时候执行,数字越小的排在前面

    当其中一个条件满足时,将当前Message插入链表头部。

    既然Message时怎么放入MessageQueue这块已经弄清楚了,那接着看一下Looper。看看是怎么取出Messages,又是怎么处理的

    Looper

    如果使用过Looper就应该知道,这是一个循环。
    查看这个类的注释,可以看到它提供了一个简单的用法。

    Looper.prepare();
    Looper.loop();

    主要有两个方法:

    1. 第一个在当前线程创建Looper对象并放入ThreadLocal中,
    2. 第二个循环MessageQueue,取出其中的Message在当前线程处理

    Looper实现在这里不进行深入,只讲一下和MessageQueue有关的。
    先看loop():

    //Looper
    public static void loop() {
    final Looper me = myLooper();
    //...
    for (;;) {
    if (!loopOnce(me, ident, thresholdOverride)) {
    return;
    }
    }
    }
    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;
    }
    //。。。
    msg.target.dispatchMessage(msg);
    //。。。
    msg.recycleUnchecked();
    return true;
    }
    //Handler
    public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
    handleCallback(msg);
    } else {
    if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
    return;
    }
    }
    handleMessage(msg);
    }
    }

    可以看到loop()里面用for写了一个死循环,它执行了loopOnce(),它是真正取出Message并执行的方法。
    dispatchMessage()方法内,首先判断callback是否为空,它是Message的Runable,就是我们使用post();postDelayed();提交到Runable。
    然后判断Handler#Callback,是否为空,这个是在Handler构造方法传入的Callback,这里也解释了当我们实现了callback时可以跨线程通信的原因。

    next()返回null会结束for循环。我们Android主线程没有消息为什么还可以继续运行?

    我们创建Looper对象都是通过其静态方法来创建的,而Looper的构造方法有一个参数quitAllowed,这个参数为True时MessageQueue不会因为消息为空而退出。

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

    postDelayed()为什么可以让线程延迟执行?

    让我们回到上面Looper#loopOnce()这个方法,我在上面没有介绍怎么从MessageQueue取出Message就是留给这个问题的。
    线上关键代码:

    //MessageQueue
    Message next() {
    for (;;) {
    synchronized (this) {
    //获取当前的开机时间
    final long now = SystemClock.uptimeMillis();
    Message prevMsg = null;
    Message msg = mMessages;
    if (msg != null) {
    //判断当前开机时间是否小于msg的开机时间
    //如果为false表示这条消息应该被拿出去处理了
    if (now < msg.when) {
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    } else {
    // Got a message.
    if (prevMsg != null) {
    prevMsg.next = msg.next;
    } else {
    mMessages = msg.next;
    }
    msg.next = null;
    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
    msg.markInUse();
    return msg;
    }
    }
    //。。。
    }
    }
    }

    这块代码还是很多的,我去掉了和该问题无关的代码。
    这里先获取了当前开机时间,然后和Message的when对比,如果当前开机时间比when大,表示这条消息到了处理时间,直接return。
    我们使用postDelayed(Runnable,0)时,执行到sendMessageDelayed()后会加上SystemClock.uptimeMillis()最后变成when值,也就是SystemClock.uptimeMillis()+0
    因此MessageQueue取出消息时,该Message会被立即执行,而延迟xxx时间是一样的道理。

    Message回收池是怎么回事?

    让我们回到上面展示的Looper#loopOnce()这个方法,可以看到当消息被取出来处理后,调用了msg.recycleUnchecked();回收当前Message:

    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 = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;
    synchronized (sPoolSync) {
    if (sPoolSize < MAX_POOL_SIZE) {
    next = sPool;
    sPool = this;
    sPoolSize++;
    }
    }
    }

    这个方法会重置Message参数,然后判断当前回收池有没有达到上限,上限是50个,没有达到会把这个Message插入链表等待再次使用。

    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();
    }

    Message.obtain()会检查回收池,如果回收池不为空,从链表头部取出一个对象并返回。
    在Google官方文档也能看到,Google建议我们通过Message.obtain()获取一个新的Message对象,而不是直接new。

    总结

    handler的这个机制是由几个类一起协作共同实现的。它们分别是:

    1. Handler
      • Handler:负责协调各个类的工作,以达到这个机制的功能。
    2. Looper
      • Looper:一个Looper对象属于一个线程,由它来管理此线程里的MessageQueue(消息队列)
    3. MessageQueue
      • MessageQueue:消息队列,负责管理Message,以及延迟Message处理
    4. Message
      • Message:用于存放需要发送的数据,将数据包装为消息对象。管理回收池等。

    扩展知识

    1. MessageQueue
  • 相关阅读:
    Python 如何实现适配器设计模式?什么是适配器(Adapter)设计模式?
    HTTP状态码的含义;并且实现:如何实现多组输入
    wireshark 流量抓包例题
    ACM. HJ16 购物单 ●●
    使用单元测试提高代码质量与可维护性
    混合背包问题
    给Fetch添加超时功能
    2、phpstudy本地搭建网站
    前端二倍图
    Feign实现文件上传下载
  • 原文地址:https://www.cnblogs.com/VoidCom/p/17479950.html