本文由船员 ChangeHui  自荐,转载发布

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

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

  • Looper:sThreadLocal、Looper.loop();

  • Message:数据结构、消息缓存池;

  • MessageQueue:enqueueMessage、next、管道等待、同步消息隔离、idleHandler;

  • Handler:send/post、dispatchMessage 消息处理优先级。

Looper

Looper 数据结构

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue;

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

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

// mQueueprivate 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()

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 数据结构

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 消息缓存池

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

插入队列

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

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。

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 还提供了消息队列空闲状态通知。

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

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

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),优先级第三

(原文完)

提前祝大家周末愉快,下周见。

转载于:https://blog.51cto.com/13360987/2386015

Handler 源码解析(Java 层)相关推荐

  1. [Android] Handler源码解析 (Java层)

    之前写过一篇文章,概述了Android应用程序消息处理机制.本文在此文基础上,在源码级别上展开进行概述 简单用例 Handler的使用方法如下所示: Handler myHandler = new H ...

  2. Handler源码分析 - Java层

    Handler最常见的使用场景就是下载回调,为了不影响用户体验Android不支持在主线程中进行耗时时操作,长时间的耗时操作会产生ANR异常,而下载无疑是耗时操作,所以我们会在子线程中进行下载.但,下 ...

  3. Handler 源码解析——Handler的创建

    前言 Android 提供了Handler和Looper来来满足线程间的通信,而前面我们所说的IPC指的是进程间的通信.这是两个完全不同的概念. Handler先进先出原则,Looper类用来管理特定 ...

  4. Handler全家桶之 —— Handler 源码解析

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 本文首发于本人简书 前言 好记性不如烂笔头. 这是一个系列文章,将会包括: Handler全家桶之 -- Handler 源码解析 ...

  5. Handler源码解析2

    Handler源码解析1 https://blog.csdn.net/qq_44076155/article/details/110676740 Handler源码解析2 https://blog.c ...

  6. dubbo源码解析-逻辑层设计之服务降级

    Dubbo源码解析系列文章均来自肥朝简书 前言 在dubbo服务暴露系列完结之后,按计划来说是应该要开启dubbo服务引用的讲解.但是现在到了年尾,一些朋友也和我谈起了明年跳槽的事.跳槽这件事,无非也 ...

  7. Android之异步消息处理机制Handler源码解析

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/76083113 本文出自:[顾林海的博客] 个人开发的微信小程序,目前功 ...

  8. 点击页面上的按钮后更新TextView的内容,谈谈你的理解?(阿里面试题 参照Alvin笔记 Handler源码解析)

    阿里面试题: 点击页面上的按钮后更新TextView的内容,谈谈你的理解? 首先,这个一个线程间通信的问题,可以从Handler的角度进行解释,可以从五个角度分析这个问题: 1.需要在主线程更新UI, ...

  9. Java源码解析——Java IO包

    一.基础知识: 1. Java IO一般包含两个部分:1)java.io包中阻塞型IO:2)java.nio包中的非阻塞型IO,通常称为New IO.这里只考虑到java.io包中堵塞型IO: 2. ...

最新文章

  1. 初试poi HssfWorkBook导出excel
  2. 前端学习(1641):前端系列实战课程之js的组成部分
  3. 06旋转数组的最小数字
  4. 请问spfa+stack 和spfa+queue 是什么原理
  5. mysql外键可以是空吗_带外键的表列可以为NULL吗?
  6. 常用的C语言编程工具
  7. 安装软件提示需要重启电脑的处理方法
  8. iOS 获取 appid
  9. 论文:Object-centric Auto-encoders and Dummy Anomalies for Abnormal Event Detection in Video阅读遇到的问题及解答
  10. 当软件定义存储(SDS)遇见区块链(BlockChain)
  11. 云原生、工业互联网之浅见
  12. 阿里8年测试老鸟教你软件测试工程师简历,技术栈,项目经验怎么写
  13. 八.deepin V20.6安装mysql8.0.30
  14. 今天是我在csdn的1265天
  15. 以 gensim 訓練中文詞向量
  16. 对图片进行锐化处理,通俗易懂!
  17. 机器人学:齐次变换矩阵
  18. SQL语句查询不同年龄段人数
  19. 使用YMIR生产基于yolov5的头盔检测模型
  20. PDF如何在线压缩?PDF在线压缩方法介绍

热门文章

  1. C++求二叉树的最大高度差
  2. postgresql数据库备份与还原
  3. GreenPlum的并行查询优化策略
  4. 优酷世博频道上线 拍客牛人导航上海世博
  5. effective java ---读书笔记一
  6. 连续 4 年成为“开发者最喜欢的语言”,这门编程语言你了解过吗?
  7. 机器学习教会我们的六件事
  8. 【年度盘点】10个热门Python练习项目
  9. 计算机组成原理国防科大课件,中科大计算机组成原理课件ppt.pdf
  10. SpringSecurity相关jar包的介绍