【安卓学习笔记】Android Handler 消息机制探究

一、概述

1.android消息机制的含义:

Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper , ThreadLocal 共同配合

因此所谓的消息机制,其实主要讲的就是 Handler ,MessageQueue ,Looper ,ThreadLocal 四者的关系

2.概述Handler工作流程:
  1. 首先是构造 Handler ,这时候会通过 ThreadLocal 找到当前线程的 Looper ,并将Handler与之绑定

  2. 然后(在另一线程)Handler 发出 Message,这个 Message 会加入 MessageQueue 中

  3. 然后 Handler 绑定的 Looper 会循环从 MessageQueue 中取出 Message

  4. 最后将取出的这个 Message 交由 Handler 的 handleMessage 等方法执行

3.延申-为什么规定只能在主线程更新UI(个人理解):
  1. 因为Android的UI控件并不是线程安全的,实际上控件是线程安全的也没有用,准确来说问题是整个UI绘制流程不是线程安全的。而要保证对UI控件的访问是线程安全的,那控件的逻辑无疑需要变的更加复杂,难以理解。

  2. 同时如果实现这种保证,从之前JAVA的学习中我们可以知道,很可能需要用到 阻塞式同步,这会导致线程的挂起和唤醒。

  3. 对于一个面向用户的终端系统,UI线程毫无疑问是不能够被挂起的。

  4. 因此,当时而言(目前也是),单线程的UI模型,是最好最方便的解决此问题的方法。

二、ThreadLocal 分析

概念解释:

  • ThreadLocal 是 JAVA 中设计用来在多线程场景下,访问线程隔离变量的工具。
1.ThreadLocal 使用方法

如下所示,在需要每个线程独享变量的时候,可以使用 ThreadLocal
【安卓学习笔记】Android Handler 消息机制探究_第1张图片

2.ThreadLocal 源码浅析

ThreadLocal 中实际是通过它的静态内部类:ThreadLocalMap 来进行数据存储的

  • 需要注意,不是说 ThreadLocal 持有一个 Map 的对象,实际是每个 Thread 都会持有一个 Map 对象

ThreadLocalMap

  • 是一个以数组的方式实现的哈希表,类似于一个 HashMap
  • 这篇重点是讲消息机制,就不对 ThreadLocal 展开了,这个讲的很清楚:Java并发编程之ThreadLocal详解

实现方案对比:

  • 最简单的实现方案:ThreadLocal 中持有一个线程安全的 ThreadLocalMap 对象,然后以 Thread 做为key值,隔离不同线程的变量值。(也是我一开始以为的方案)
  • 实际JDK里的实现方案:ThreadLocal 不持有 ThreadLocalMap 对象,而是 map 的访问入口,每个 Thread 持有一个 ThreadLocalMap 对象,ThreadLocal 做为 key 值

JDK实现的优点:

  • 空间占用小:方案一里,每个 ThreadLocal对象都会有一个 ThreadLocalMap ,而每一个线程又会在这些map里占用一个Entry。 方案二里 ,一个线程只会有一个 ThreadLocalMap ,ThreadLocalMap 中的 Entry 数量是 ThreadLocal 变量的数量。毫无疑问前者占用空间要多一些。
  • 便于管理:方案一里,ThreadLocalMap 分散到不同的 ThreadLocal中,线程结束后,要回收这些内存无疑是比较麻烦的。而方案二里,线程结束了,ThreadLocalMap 自然也都跟着销毁了。

3.消息机制中的 ThreadLocal

Handler中:

【安卓学习笔记】Android Handler 消息机制探究_第2张图片

Looper中:

image.png

浅析(个人理解):

  • 在 Android 中,ThreadLocal 刚好符合消息机制的使用场景,所以被拿来使用了。
  • 利用 ThreadLocal ,每一个线程都可以绑定一个 Looper( 构造方法中绑定当前线程,且这里的ThreadLocal 对象是静态变量),利用 Looper 中的循环,就可以在不同的线程间不停的 发送-接收消息了。

三、MessageQueue 分析(重要)

概念解释

  • MessageQueue 是消息机制中的消息队列,负责插入消息和读取消息,所有消息在队列中按执行时间从早到晚排好序

实现

  • 虽然名字是叫 Queue ,但是它实际上是一个单链表
  • (其实 Message 就是一个链表节点,它有一个 Next 方法

1. messageQueue.next 方法
Message next() {
   
    ...
    int nextPollTimeoutMillis = 0;//距离继续下次轮询的时间
    //nextPollTimeoutMillis=-1:表示一直阻塞
    //nextPollTimeoutMillis=0:表示不需要阻塞
    //nextPollTimeoutMillis>0:表示阻塞的时间
    for (;;) {
   //开启一个无限循环
        if (nextPollTimeoutMillis != 0) {
   //不为0说明需要阻塞
            Binder.flushPendingCommands();//阻塞前调用此方法,可以释放一些对象的引用,防止阻塞期间一直持有待处理对象。
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);//调用本地方法,内部会根据nextPollTimeoutMillis的值来阻塞当前线程,这里也是nativeWake(mPtr),即唤醒线程回调的地方。

        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 {
   
                    //消息到执行时间,取出一条消息
                    mBlocked = false;
                    

你可能感兴趣的:(android,android,java,ui)