前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Handler 源码解析(Java 层)

Handler 源码解析(Java 层)

作者头像
haifeiWu
发布2020-02-10 17:58:02
3710
发布2020-02-10 17:58:02
举报

从很早开始就认识到 Handler 了,只不过那时修为尚浅,了解的不够深刻,也没有应用自如。不过随着工作时间的增长,对 Handler 又有了更深层次的认识,于是有了这篇博客,希望尽可能的总结出多的知识点。

Handler 在 Java 层源码主要有 4 个类:Looper、MessageQueue、Message、Handler。我归纳了他们的几个主要知识点:

  • Looper:sThreadLocal、Looper.loop();
  • Message:数据结构、消息缓存池;
  • MessageQueue:enqueueMessage、next、管道等待、同步消息隔离、idleHandler;
  • Handler:send/post、dispatchMessage 消息处理优先级。

Looper

Looper 数据结构

代码语言:javascript
复制
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue;

// sThreadLocal
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { throw Exception ... }
    sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

// sMainLooper
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) { throw Exception ...}
        sMainLooper = myLooper();
    }
}
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

// mQueue
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
}
  • sThreadLocal:静态常量,保证一个线程只有一个 Looper;
  • sMainLooper:静态变量,在 prepareMainLooper 中赋值当前线程 Looper;
  • mQueue:变量,Looper 构造函数中初始化,因为一个线程只有一个 Looper,所以也同样只有一个 mQueue。

通过以上分析,我们可以总结出一下特性:

  1. Looper、MessageQueue 是线程唯一的;
  2. 一个进程只有一个 sMainLooper;
  3. 根据 ThreadLocal 的特性,可通过 myLooper 方法获取当前线程的 Looper。

Looper.loop()

代码语言:javascript
复制
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) { 
        Message msg = queue.next();
        ...
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();
    }
}

Looper.loop() 方法虽然看起来很多,其实他主要就做了三件事:

  1. 从消息队列中获取下一个消息;
  2. msg.target 就是 handler,通过 dispatchMessage 方法把消息分发下去,这个方法下面会有说到;
  3. 消息回收,放到消息缓存池里。这里需要注意的是 Message 对象并没有释放,会缓存起来。

Message

Message 数据结构

代码语言:javascript
复制
public int what, arg1, arg2;
public Object obj;
public Messenger replyTo;
int flags;
long when;      // 消息发送时间
Bundle data;
Handler target;
Runnable callback;

Message next;

private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;

private static final int MAX_POOL_SIZE = 50;
  • 看到 next 变量,我们会想到单链表,其实 Message 就相当于单链表的 node,MessageQueue 就是一个单链表了,会持有表头的引用;
  • what、arg1、arg2、obj、data 就是我们发送的一些信息;
  • 值得注意的是 target,他是 Handler 类型,就是本消息的 Handler,会在 Handler 发送消息的时候赋值;
  • 后面的四个对象,都是和消息缓存池有关的。

Message 消息缓存池

代码语言:javascript
复制
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();
}

void recycleUnchecked() {
    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++;
        }
    }
}
  • 事实上缓存池的数据结构也是一个链表,sPool 为链表头引用,最大容量为 50;
  • 回收消息时,会把消息里面所有参数重置,并把当前消息设为链表头;
  • 获取消息时,返回当前链表头,并把 next 置空。

MessageQueue

插入队列

代码语言:javascript
复制
boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        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 {
            // 根据时间顺序,插入链表中间
            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; // 插入消息
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

主要作为插入队列的方法,有下列几个特性:

  • 把消息加入消息队列,如果当前表头为空,则把消息作为表头引用;如果不为空,则会根据时间的顺序,插入到对应的时间中;
  • nativeWake 是调用底层在管道中写操作以唤醒,在队列不是阻塞的状态下是不需要唤醒的;
  • 另外注意其中用了 synchronized 关键字,说明消息队列的插入是线性安全的,删除也是线性安全的,之后我们会说到。

MessageQueue.next()

代码语言:javascript
复制
for (;;) {
    nativePollOnce(ptr, nextPollTimeoutMillis);
    synchronized (this) {
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
            // 如果有同步消息隔离,则会优先查找异步消息
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());
        }
        if (msg != null) {
            if (now < msg.when) {
                // 计算距离下一个消息的时间
                nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
            } else {
                // Got a 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();
                return msg;
            }
        } else {
            // 没有更多消息的时候,nextPollTimeoutMillis 会置为 1。
            nextPollTimeoutMillis = -1;
        }

        ...
    }

    // 如果目前没有消息,已经处在空闲状态,则执行 idler.queueIdle
    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);
            }
        }
    }
    ...
}

此方法会从消息队列中读取下一个消息返回,主要做了以下操作:

  • nativePollOnce 函数会调用底层管道操作函数,nextPollTimeoutMillis 为 -1 时,会阻塞,为 0 时不会阻塞,大于 0 时,会阻塞相应的时间;
  • 如果有同步消息隔离,则会优先查找异步消息;
  • 获取当前时间队列的消息,并返回;
  • 如果队列没有任何消息,则会执行 idler.queueIdle,通知监听者当前队列处于空闲状态。

同步消息隔离

上面我们有提到了同步消息隔离,这里我们介绍一下。同步隔离,有时候也可以叫异步消息,说的是一个知识点。在源码中主要用于优先更新 UI。

代码语言:javascript
复制
private IdleHandler[] mPendingIdleHandlers;

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // 向消息队列中加入一个 handler 为空的消息
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

如上 postSyncBarrier 函数中会向消息队列中加入一个 handler(即 Message 的 target) 为空的消息作为标识。在我们上面 MessageQueue.next() 的函数中,当 msg.target == null 时,会优先获取异步消息并返回。 因此想要使用同步消息隔离有两个条件:

  1. 消息为异步消息,即 msg.isAsynchronous() 返回 false;
  2. 需要获取当前队列并运行 postSyncBarrier() 函数。

IdleHandler

Handler 还提供了消息队列空闲状态通知。

代码语言:javascript
复制
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

IdleHandler 的源码比较简单,就是一个 ArrayList,然后进行增加删除操作。注意,这个也是线性安全的。

Handler

post/sendMessage

代码语言:javascript
复制
public final boolean post(Runnable r){
    return sendMessageDelayed(getPostMessage(r), 0);
}

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

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • sendMessage 和 post 最本质的区别是之后处理任务时的优先级,post 会处理 Runnable 中的任务,而 sendMessage 会回调给 handler 处理;
  • 他们最终都会走 enqueueMessage 方法,并设置当前 Handler 为 msg.target。

dispatchMessage

代码语言:javascript
复制
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

任务执行时就会运行这个函数,主要是一个优先级的问题:

  1. callback 优先级最高,也就是 post 发送的消息
  2. mCallback.handleMessage(msg),优先级第二
  3. handleMessage(msg),优先级第三
作 者:ChanghuiN 原文链接:https://www.hchstudio.cn
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Looper
    • Looper 数据结构
      • Looper.loop()
      • Message
        • Message 数据结构
          • Message 消息缓存池
          • MessageQueue
            • 插入队列
              • MessageQueue.next()
                • 同步消息隔离
                  • IdleHandler
                  • Handler
                    • post/sendMessage
                      • dispatchMessage
                      相关产品与服务
                      消息队列 CMQ 版
                      消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档