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

相关文章

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

Maven 配置中的 <mirror>绕过 HTTP 阻断机制的方法

《Maven配置中的<mirror>绕过HTTP阻断机制的方法》:本文主要介绍Maven配置中的<mirror>绕过HTTP阻断机制的方法,本文给大家分享问题原因及解决方案,感兴趣的朋友一... 目录一、问题场景:升级 Maven 后构建失败二、解决方案:通过 <mirror> 配置覆盖默认行为1. 配置示

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

SpringBoot3中使用虚拟线程的完整步骤

《SpringBoot3中使用虚拟线程的完整步骤》在SpringBoot3中使用Java21+的虚拟线程(VirtualThreads)可以显著提升I/O密集型应用的并发能力,这篇文章为大家介绍了详细... 目录1. 环境准备2. 配置虚拟线程方式一:全局启用虚拟线程(Tomcat/Jetty)方式二:异步

如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socket read timed out的问题

《如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socketreadtimedout的问题》:本文主要介绍解决Druid线程... 目录异常信息触发场景找到版本发布更新的说明从版本更新信息可以看到该默认逻辑已经去除总结异常信息触发场景复

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio