💪
AndroidCollect
  • 写在前面
  • 计算机基础
    • 计算机组成原理
    • 算法
      • 查找
        • 二分查找
      • 排序
        • 简单排序
        • 高级排序
        • 特殊排序
      • 海量数据
      • 思想
        • 贪心
        • 分治
        • 动态规划
        • 回溯
      • 哈希算法
    • 数据结构
      • 队列
        • 知识点
        • 相关题目
          • 用两个栈实现队列
          • 实现循环队列
          • 用链表实现队列
          • 用数组实现队列
      • 栈
        • 相关算法题目
          • 用链表实现栈
          • 用数组实现栈
      • 链表
        • 知识点梳理
        • 相关算法题目
          • 删除倒数第n个结点
          • 合并两个有序链表
          • 检测单链表是否有环
          • 获取中间结点
          • 反转链表
      • 跳表
      • 哈希表
      • 树
        • 二叉树
        • 二叉查找树
        • AVL 树
        • Trie 树
        • 红黑树
      • 堆
        • 存储
        • 堆的应用
      • 图
    • 网络
      • 应用层协议
        • DNS
        • HTTP
        • HTTPS
      • 传输层协议
        • TCP
        • UDP
      • 输入网址后发生了什么
    • 操作系统
      • 内存
    • 数据库
  • 软件工程
    • 编程思想
    • 设计模式
      • 状态模式
      • 装饰器模式
      • 代理模式
      • 责任链模式
      • 建造者模式
      • 单例模式
      • 观察者模式
  • Java
    • 基础
    • 异常
    • 并发编程
      • ThreadLocal
      • 线程池
      • 理解 volatile
      • AbstractQueuedSynchronizer
    • 集合
      • LinkedHashMap 源码
      • HashMap 源码
    • 注解
    • 反射
      • JDK 动态代理
    • JVM
      • 自动内存管理机制
      • Class 文件格式
      • 类加载机制
      • Java 内存模型(JMM)
      • 字节码指令
      • HotSpot 虚拟机实现细节
    • 源码与原理
    • 各版本主要特性
  • Android
    • 基础组件
      • Context
      • Activity
        • 生命周期
        • 启动模式与任务栈
        • 启动流程
      • Service
      • ContentProvider
      • BroadcastReceiver
      • Fragment
      • View
        • 常用控件问题总结
          • RecyclerView
          • ViewPager2
        • CoordinatorLayout
        • SurfaceView
        • 事件分发
        • 绘制流程
        • 自定义 View
        • Window
    • 数据存储
      • 存储结构
      • Sqlite
      • 序列化
      • SharedPreferences
    • 资源
      • 图片加载
    • 动画
      • 属性动画
    • 线程和进程
      • Binder 机制
      • 跨进程通信
        • AIDL
    • 内部原理
      • 消息循环机制
      • Binder
      • Window
      • SparseArray
      • ArrayMap
      • RecyclerView
      • App 启动流程
    • 性能优化
      • 内存
        • 内存使用优化
        • 内存泄漏
      • 启动优化
      • 缩减包大小
      • 布局优化
      • ANR
    • 打包构建
      • dex 文件
      • APK 打包流程
      • APK 签名流程
    • 架构
      • 运行时
      • Android 系统架构
      • 应用项目架构
    • 开源框架源码或原理
      • RxJava
        • 使用笔记
        • 源码解析
      • Retrofit
      • ButterKnife
      • BlockCanary
      • LeakCanary
      • OkHttp
      • 图片加载
        • Glide
        • Picasso
    • 碎片化处理
      • 屏幕适配
    • 黑科技
      • 热修复
    • Jetpack
      • Lifecycle
      • Room
      • WorkManager
    • 新动态
      • AndroidX
      • 各系统版本特性
  • 开发工具
    • 正则表达式
    • ADB
    • Git
  • Kotlin
  • Flutter
  • 关于作者
  • 致谢
由 GitBook 提供支持
在本页
  • 消息循环流程
  • 各个类的作用
  • Message
  • MessageQueue
  • Looper
  • Handler
  • 各个类和方法调用关系图
  • 主线程 Looper 设置
  • Looper 线程的等待和唤醒(JNI 层)
  • MessageQueue 的 nativeInit 方法
  • MessageQueue 的 next 方法
  • MessageQueue 的 enqueueMessage
  • 总结
  • IdleHanlder
  • 相关问题
  • 为什么系统不建议在子线程访问UI?
  • 一个Thread可以有几个Looper?几个Handler?
  • 可以在子线程直接new一个Handler吗?那该怎么做?
  • Message可以如何创建?哪种效果更好,为什么?
  • 这里的 ThreadLocal 有什么作用?
  • 主线程中 Looper 的轮询死循环为何没有阻塞主线程?
  • 链接

这有帮助吗?

  1. Android
  2. 内部原理

消息循环机制

上一页内部原理下一页Binder

最后更新于5年前

这有帮助吗?

消息循环流程

Android 消息循环主要涉及Looper、Handler、MessageQueue和Message 四个类,他们之间的关系如下图所示:

简单总结就是:Message 由 Handler 发送,按照发生时间升序保存在 MessageQueue 中,然后 Looper 不断从 MessageQueue 中取出下一条待处理的 Message ,并通知发送它的 Handler 进行处理。

各个类的作用

Message

代表一个消息(或事件),可以携带数据,持有发送它的Handler的引用。

MessageQueue

消息队列,以单链表的形式按照发生时间升序排列 Message。

提供了 enqueueMessage()用于把新消息插入到MessageQueue中,也提供了next()方法用于获取下一条待处理消息。(当队列无消息或者消息处理时间未到时,会阻塞等待。)

Looper

消息循环的入口,提供 Looper.prepare() 用于创建 Looper 线程,以及 Looper.loop()方法进入消息循环,持有 MessageQueue 的引用。

Looper.loop() 方法就是通过 MessageQueue的next()方法获取下一条待处理消息,然后通知消息的 Handler 处理消息。

public static void loop() {
    //获取当前线程的 looper
    final Looper me = myLooper();
    //如果在调用 loop 之前没有调用 prepare,就会抛出一个异常
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //获取 Looper 对应的 MessageQueue
    final MessageQueue queue = me.mQueue;

    for (;;) {
        //通过 MessageQueue 的 next 方法获取下一个消息
        //在没有消息、或消息处理时间未到时可能会阻塞
        Message msg = queue.next();

        //消息队列为空,退出循环
        if (msg == null) {
            return;
        }

        try {
            //通知 msg 对应的 Handler 处理消息
            msg.target.dispatchMessage(msg);
        } finally {
            //...
        }
        //...

        //回收 msg
        msg.recycleUnchecked();
    }
}

Handler

用于发送和处理消息。提供了一系列 send 方法来将 Message 插入到 MessageQueue中,最终调用 enqueueMessage 方法。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //给 msg 的 target 赋值为当前这个 Handler
    msg.target = this;
    //如果 Handler 是否异步来设置 Message
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //调用 MessageQueue 的 enqueueMessage
    return queue.enqueueMessage(msg, uptimeMillis);
}

提供了 dispatchMessage(Message msg) 方法用于处理消息,具体逻辑如下:

对应源码:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

各个类和方法调用关系图

主线程 Looper 设置

Looper 线程的等待和唤醒(JNI 层)

MessageQueue 的 nativeInit 方法

MessageQueue 的构造方法中调用了 nativeInit 方法,这个方法在JNI层创建了一个 NativeMessageQueue ,并将它保存在 Java 层 MessageQueue 的 mPrt 变量中。

在创建 NativeMessageQueue 过程中,也在 Native 层创建了一个 Looper,Looper 内部创建了一个管道,用于控制线程的等待和唤醒。当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。

管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。这里借助了 Linux 系统的epoll 机制。

MessageQueue 的 next 方法

Message next() {
    //...
    //用于 native 层的变量
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    //取下一条消息前需要等待的时间
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //...
        
        //查看是否有消息
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            //获取当前时间
            final long now = SystemClock.uptimeMillis();           
            Message prevMsg = null;
            Message msg = mMessages;          
            //...
            if (msg != null) {
                if (now < msg.when) {
                    // 下一条消息分发时间比当前晚,设定等待时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 取出下一条msg
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 队列中已没有更多消息
                nextPollTimeoutMillis = -1;
            }
            //....
        }//end sychronized
    }//end for
}

主要的方法是 nativePollOnce(mPtr, nextPollTimeoutMillis),这个方法会调用NativeMessageQueue 的 pollOnce 方法,实际调用的是 Native 层的 Looper 的 pullOnce 方法,

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
	int result = 0;
	for (;;) {
		......
 
		if (result != 0) {
			......
 
			return result;
		}
 
		result = pollInner(timeoutMillis);
	}
}

。

原文链接:https://blog.csdn.net/luoshengyang/article/details/6817933

pollOnce 主要通过 pollInner 的返回值来判断是否需要返回。pollInner 方法通过 epoll_wait 来检查管道是否有写入事件,如果有或者等待超时,那么就会返回;否则就会在epoll_wait中进入睡眠。

如果 epoll_wait 返回值是 0,即等待超时;如果大于0,则说明有写入事件发生,这时会清空管道内容并返回。

MessageQueue 的 enqueueMessage

final boolean enqueueMessage(Message msg, long when) {
		......
 
		final boolean needWake;
		synchronized (this) {
			......
 
			msg.when = when;
			Message p = mMessages;
			if (p == null || when == 0 || when < p.when) {
				msg.next = p;
				mMessages = msg;
				needWake = mBlocked; 
			} else {
				Message prev = null;
				while (p != null && p.when <= when) {
					prev = p;
					p = p.next;
				}
				msg.next = prev.next;
				prev.next = msg;
				needWake = false; // still waiting on head, no need to wake up
			}
 
		}
		if (needWake) {
			nativeWake(mPtr);
		}
		return true;


————————————————

版权声明:本文为CSDN博主「罗升阳」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/luoshengyang/article/details/6817933

在将消息放入消息队列后,根据情况判断是否需要唤醒。如果消息队列不为空,那么就不需要唤醒;如果消息队列为空,那么此时线程还在epoll_wait上空闲等待,就需要进行唤醒。

唤醒调用了native方法 nativeWake,这个调用NativeMessageQueue的wake方法,进而调用Native 层Looper 的wake方法。Native Looper 的wake 就是往管道中写入一个"W"字符串,这时在管道读的一端 epoll_wait 上等待的线程就会被唤醒,从而在Java 层MessageQueue 的next方法中调用的 nativePollOnce返回,此时消息队列中已经放入了新的消息,程序就会拿到消息队列的消息进行处理或等待。

总结

Looper 线程的等待和唤醒是在 native 层通过管道实现的。

在创建 MessageQueue 的时候在 Native 层也创建了对应的 NativeMessageQueue 和 Looper。NativeMessageQueue 主要用于Java 层的 MessageQueue 调用,保存在它的 mPtr 变量中。而 Native 的 Looper 则是基于管道和 epoll 机制来实现等待和唤醒的。

当通过 MessageQueue 的 next 方法获取下一条消息时,会检查是否需要等待,如果需要,就调用 epoll_wait 等待管道的写入事件,直到超时或者有写入事件时会被唤醒。

当通过 MessageQueue 的 enqueueMessage 插入消息时,如果此时线程处于空闲等待中,则会进行唤醒。唤醒通过 Native 的 Looper 的 wake 方法,向管道中写入一个“W”字符,此时正在等待的线程就回被唤醒,nativeOncePoll 方法返回,程序开始检查消息队列中的消息决定是否处理。

IdleHanlder

/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

调用MessageQueue#next() 方法且第一次循环消息队列为空或者有消息但是时间没到时执行。

相关问题

为什么系统不建议在子线程访问UI?

  • View 并不是线程安全的,多线程访问可能使界面状态不可预测

  • 允许多线程需要额外的同步工作,开发成本高且性能不好(借助锁)

一个Thread可以有几个Looper?几个Handler?

一个Thread 只能有一个 Looper ,如果重复调用Looper的prepare方法,会报错。

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

一个 LooperThread 可以有多个Handler。

可以在子线程直接new一个Handler吗?那该怎么做?

不可以。创建Handler时会通过当前线程的Looper来初始化内部变量和MessageQueue,如果线程还没有对应的Looper则会抛出异常。

先调用Looper.prepare()方法,然后再创建Handler,最后通过Looper.loop()进入消息循环。

Message可以如何创建?哪种效果更好,为什么?

通过 Message 的 obtain 方法创建更改,Message 类内部有静态缓存池,会缓存之前已经分发并处理过的消息对象。这样可以实现对象复用,避免多次创建对象带来的内存开销和垃圾回收。

这里的 ThreadLocal 有什么作用?

将当前线程及其对应的 Looper 进行映射,方便直接通过 Looper.myLooper()获取当前线程对应的 Looper 对象。

主线程中 Looper 的轮询死循环为何没有阻塞主线程?

如果消息队列中没有消息或者当前消息的处理时间未到,此时线程会在native 的epoll_wait函数中空闲等待,不会占用CPU资源。

当其他线程发送消息或者等待超时后,主线程被唤醒,开始检查消息队列中的消息是否需要处理。

链接

Looper.prepare() 方法用于给当前线程设置Looper,通过 将当前线程和它的 Looper 进行绑定,每个线程只能有一个Looper。

ThreadLocal
Android 消息机制原理解析
Android应用程序消息处理机制(Looper、Handler)分析
Android 消息机制图
Handler 的 dispatchMessage 流程
消息循环涉及的类和方法以及调用关系