Android 的消息机制(UI线程的Looper 为啥不会阻塞?答案在后面)

2024-06-07 20:08

本文主要是介绍Android 的消息机制(UI线程的Looper 为啥不会阻塞?答案在后面),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说道 Android 的消息机制,其实说的就是 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工程过程。

一、 Handler 的运行机制

当 Handler 创建的时候,会采用当前线程的 Looper 来构建消息循环系统,如果当前线程没有 Looper 则会报错,当然,开始是 UI 线程,所以不用担心。
当然,当Looper 被创建的时候, MessageQueue 也被创建好了,这样 Looper 和 MessageQueue 就可以跟 Handler 一起工作了。

这里,先做一下 Handler 的工作流程:
当我们使用 Handler 的send 或者 post 的时候,它会调用 MessageQueue 的 qnqueueMessage 方法,将这个 消息放到消息队列中,然后 Looper 发现有新消息到来时,就会处理这个消息了;然后 Handler 的 handleMessage 方法就会被调用。

注意 Looper 是运行在创建 Handler 所在的线程中的,这样一来,Handler 中的业务逻辑就会被切换到创建 Handler 所在的县城中去执行了

过程可以用下图表示(图片来源):
在这里插入图片描述
接下来继续深入它。

1.2、消息队列的工作原理

消息队列在 Android 中指的是 MessageQueue,它其实不是队列,里面实现的单链表结构,它主要包含两个操作:插入和读取。
插入对应 enqueueMessage ,读取则对应 next,其中 enqueueMessage 表示往队列中插入一条消息,而 next 则从队列中取出消息,并将消息从队列中删除。

其中,需要注意的是它的 next 方法:

    Message next() {...for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}...}...

可以发现 next 方法是一个无线循环的方法,如果消息队列没有消息,那么next 方法会一直阻塞在这里。当有新消息到来时,next 方法会返回这条消息并将它从单链表中删除。

1.3 Looper 的工作原理

Looper 在 Android 的消息机制中扮演者消息队列的角色,具体来说,你会不停地从 MessageQueue 中查看是否有新的消息,如果有则取出消息,如果没有,则一直阻塞在那里。

在 Looper 的构造方法中,会创建一个 MessageQueue ,然后将当前线程保存起来。

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

然后,Handler 的创建是需要 Looper 的,没有Looper 是会报错的,那 Looper 如何创建呢?
很简单,在线程中,通过 Looper.prepare() 为线程开启一个 Looper,接着使用 Looper.loop() 来开启消息循环模式,如下所示:

new Thread(){@Overridepublic void run() {Looper.prepare();super.run();Looper.loop();}
};

当然,这个是普通线程的,如果是 UI 线程,还提供了 prepareMainLooper 方法,这个方法主要是给 ActivityThread 创建Looper 使用,由于主线程比较特殊,也可以使用 Looper.getMainLooper() 来获取主线程Looper。

比如常用在共用类中:

 public static Handler HANDLER = new Handler(Looper.getMainLooper());

1.3.1 Looper 的退出

Looper 也是可以退出的,有 quit() 和 quitSafely() 两个方法;quit() 会直接退出,二 quitSalely() 则是已有消息都处理完毕,才会退出。

如果是自己定义的 Looper ,则建议要执行退出操作,否则这个子线程就会一直处于等待状态。容易造成内存泄漏,当然主线程就不用我们操心了。

Looper 的重要方法是 loop 方法,只有调用了 loop 后,消息循环系统才会真正起作用,它的实现如下:

    public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();...boolean slowDeliveryDetected = false;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;try {msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}... ...

可以看到,只有 queue.next() 为null 时,才会退出这个循环;当执行了 quit 或者 quitSafely 来通知 Looper 退出,它就会调用 MessageQueue 来退出,这样 queue.next() 就会返回null,looper 也就退出了。
否则就会一直阻塞在这里,一直等到 next 有新的消息,则 msg.target.dispatchMessage(msg); 就会被执行,这样 Handler 的dispatchMessage 就会被执行了。可以看到 Handler 的 dispatchMessage 方法如下:

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

首先,先检查 msg.callback 是否为null,Message 的callback 是一个 RUnnable 对象,实际上就是 Hnadler 的post 方法所传递的 Runnable 。所以,如果我们使用 了 Handler.post ,则不会走 handleMessage(msg) ;
如果不是,则检查 mCallback 是否为null,mCalback 其实就是个接口:

    public interface Callback {/*** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/public boolean handleMessage(Message msg);}

如果不为空,最终就会调用到我们熟悉的 handleMessage(msg) 方法了。

这样,Handler 的消息机制就分析完了。

这里,面试官就会问了,既然 looper 这里是个无线循环,为啥不会阻塞UI线程?

为了回答这个问题,首先,我们先去到主线程的消息队列

二、主线程的消息队列

Android 的主线程就是 ActivityThread,主线程的入口为 main 方法:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");.... Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}

可以看到,main() 方法会通过 Looper.prepareMainLooper() 来创建 Looper 和 MessageQueue,然后创建了 ActivityThread线程,最后通过 Looper.loop() 开启循环。

而它的 Handler 就是 ActivityThread.H ,里面对应着 Activity 的启动等消息:
在这里插入图片描述

那这还是不能说明UI线程为啥不会阻塞啊?!!

别急,除了这个 ActivityThread 这个线程,其实还有个 ApplicationThread 线程,它里面创建了 Binder 方法,当 ApplicationThread 与 AMS 进行进程间通信完成后,就会通过 ApplicationThread 会向 H 发送消息,H 收到消息之后,就会将 ApplicationThread 中的逻辑切换到 ActivityThread 中取执行,即切换到主线程去执行了。
比如 Activity 的启动最后就通过 ApplicationThread 发送 LUANCHER_ACTIVITY 给 H 去启动的。

所以,UI线程并不会因为looper而阻塞,但如果我们主线程中做了一些耗时操作,导致导致 MessageQueue 的消息过多,等待执行,造成 ANR。

自此,我们的 Android 消息机制就分析完了。

参考 Android 艺术开发 第10章

这篇关于Android 的消息机制(UI线程的Looper 为啥不会阻塞?答案在后面)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040171

相关文章

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程

Android使用java实现网络连通性检查详解

《Android使用java实现网络连通性检查详解》这篇文章主要为大家详细介绍了Android使用java实现网络连通性检查的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录NetCheck.Java(可直接拷贝)使用示例(Activity/Fragment 内)权限要求

Java中自旋锁与CAS机制的深层关系与区别

《Java中自旋锁与CAS机制的深层关系与区别》CAS算法即比较并替换,是一种实现并发编程时常用到的算法,Java并发包中的很多类都使用了CAS算法,:本文主要介绍Java中自旋锁与CAS机制深层... 目录1. 引言2. 比较并交换 (Compare-and-Swap, CAS) 核心原理2.1 CAS

SpringBoot+Vue3整合SSE实现实时消息推送功能

《SpringBoot+Vue3整合SSE实现实时消息推送功能》在日常开发中,我们经常需要实现实时消息推送的功能,这篇文章将基于SpringBoot和Vue3来简单实现一个入门级的例子,下面小编就和大... 目录前言先大概介绍下SSE后端实现(SpringBoot)前端实现(vue3)1. 数据类型定义2.

Spring Boot 集成 mybatis核心机制

《SpringBoot集成mybatis核心机制》这篇文章给大家介绍SpringBoot集成mybatis核心机制,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值... 目录Spring Boot浅析1.依赖管理(Starter POMs)2.自动配置(AutoConfigu

2025最新版Android Studio安装及组件配置教程(SDK、JDK、Gradle)

《2025最新版AndroidStudio安装及组件配置教程(SDK、JDK、Gradle)》:本文主要介绍2025最新版AndroidStudio安装及组件配置(SDK、JDK、Gradle... 目录原生 android 简介Android Studio必备组件一、Android Studio安装二、A

Redis的安全机制详细介绍及配置方法

《Redis的安全机制详细介绍及配置方法》本文介绍Redis安全机制的配置方法,包括绑定IP地址、设置密码、保护模式、禁用危险命令、防火墙限制、TLS加密、客户端连接限制、最大内存使用和日志审计等,通... 目录1. 绑定 IP 地址2. 设置密码3. 保护模式4. 禁用危险命令5. 通过防火墙限制访问6.

深入理解Redis线程模型的原理及使用

《深入理解Redis线程模型的原理及使用》Redis的线程模型整体还是多线程的,只是后台执行指令的核心线程是单线程的,整个线程模型可以理解为还是以单线程为主,基于这种单线程为主的线程模型,不同客户端的... 目录1 Redis是单线程www.chinasem.cn还是多线程2 Redis如何保证指令原子性2.

C++实现一个简易线程池的使用小结

《C++实现一个简易线程池的使用小结》在现代软件开发中,多线程编程已经成为提升程序性能的常见手段,本文主要介绍了C++实现一个简易线程池的使用小结,感兴趣的可以了解一下... 在现代软件开发中,多线程编程已经成为提升程序性能的常见手段。无论是处理大量 I/O 请求的服务器,还是进行 CPU 密集型计算的应用

JDK21对虚拟线程的几种用法实践指南

《JDK21对虚拟线程的几种用法实践指南》虚拟线程是Java中的一种轻量级线程,由JVM管理,特别适合于I/O密集型任务,:本文主要介绍JDK21对虚拟线程的几种用法,文中通过代码介绍的非常详细,... 目录一、参考官方文档二、什么是虚拟线程三、几种用法1、Thread.ofVirtual().start(