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

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

poj 3104 二分答案

题意: n件湿度为num的衣服,每秒钟自己可以蒸发掉1个湿度。 然而如果使用了暖炉,每秒可以烧掉k个湿度,但不计算蒸发了。 现在问这么多的衣服,怎么烧事件最短。 解析: 二分答案咯。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <c

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分