代码分析Android消息机制
作者:mmmmar 发布时间:2023-07-26 09:44:44
我们知道在编程时许多操作(如更新UI)需要在主线程中完成,而且,耗时操作(如网络连接)需要放在子线程中,否则会引起ANR。所以我们常使用Handler来实现线程间的消息传递,这里讨论的也就是Handler的运行机制。
Handler的运行主要由两个类来支撑:Looper与MessageQueue。熟悉开发的朋友都知道在子线程中默认是无法创建Handler的,这是因为子线程中不存在消息队列。当需要创建一个与子线程绑定的Handler时,标准代码如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
在创建Handler前,需要先调用Looper.prepare()方法,之后再调用Looper.loop()方法。也就是说Handler的功能实现建立在Looper之上。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
final Thread mThread;
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
由于Looper的消息循环是一个死循环,一个线程最多只能有一个Looper,所以Looper.prepare()函数首先检查该线程是否已经拥有一个Looper,如果有则抛出异常。Looper通过ThreadLocal类为每个线程储存独立的Looper实例,简单说一下ThreadLocal的实现原理:
Java并发编程:深入剖析ThreadLocal
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
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;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
在4行可以看到一个我们熟悉的异常信息,说明并没有Looper与当前线程相关联,也就无法进行消息传递。Looper.loop()方法本身是一个死循环,不断在MessageQueue中取出Message对象进行处理,然后调用Message.recycleUnchecked()方法对其回收,这也是为什么官方推荐使用Message.obtain()方法来获取Message实例,而不是直接新建对象。当没有消息可处理时,MessageQueue.next()方法将阻塞,直到新的消息到来。
对于MessageQueue,我们只需要关注两个函数即可,一个是MessageQueue.enqueueMessage()另一个是MessageQueue.next(),它们分别对应着队列的插入与取出操作。MessageQueue中队列是使用单链表实现的,由Message.next属性指向其下一个元素。
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
return true;
}
向MessageQueue中插入元素时,需要根据Message.when属性的大小决定插入的位置,它代表了Meesage需要被处理的时间,拿Handler.sendMessage()函数为例。
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
从调用流程来看,Handler.sendMessage()函数其实就是向MessageQueue的消息队列中插入了一个Message.when属性为当前时间的元素。
对于MessageQueue.next()函数,简单来说它的作用就是在MessageQueue的头部取出元素,然后执行Handler.dispatchMessage()函数。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
如果我们使用Handler.post()函数发送一个Runnable对象,那么最终Runnable对象会在Handler.handleCallback()函数中执行。如果是一个普通Message,那么它会被分发到一个我们熟悉的函数中,Handler.handleMessage(),这就是为什么一般我们都需要重写这个函数对消息进行处理。
来源:https://www.cnblogs.com/mmmmar/p/8595723.html
猜你喜欢
- 本文实例讲述了C++实现的O(n)复杂度内查找第K大数算法。分享给大家供大家参考,具体如下:题目:是在一组数组(数组元素为整数,可正可负可为
- 本文实例讲述了C#创建临时文件的方法。分享给大家供大家参考。具体分析如下:C#可以通过Path.GetTempFileName获得一个临时文
- 1. strlen —— 求字符串长度1.1 strlen 的声明与用处strlen ,我们有一些英
- 一、简介ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:Task支持线程的
- 背景大家在使用Selenium + Chromedriver爬取网站信息的时候,以为这样就能做到不被网站的反爬虫机制发现。但是实际上很多参数
- 背景实际开发中,常常需要将比较复杂的 JSON 字符串转换为对应的 Java 对象。这里记录下解决方案。如下所示,是入侵事件检测得到的 JS
- 什么是FTPFTP(File Transfer Protocol)是TCP/IP网络上两台计算机传送文件的协议,使得主机间可以共享文件.可以
- 前言这里主要简单介绍如何使用Camera+SurfaceView自定义相机拍照,如果是Camera2或者是TextureView的可以前往主
- [LeetCode] 5. Longest Palindromic Substring 最长回文子串Given a string
- 一 :问题背景问题:当查询接口较复杂时候,数据的获取都需要[远程调用],必然需要花费更多的时间。 假如查询文章详情页面,需要如下标注的时间才
- 本文介绍了Spring Boot Admin监控服务上下线邮件通知,分享给大家,具体如下:微服务架构下,服务的数量少则几十,多则上百,对服务
- 内容简介本篇将介绍 Flutter 中如何完成图片上传,以及上传成功后的表单提交。涉及的知识点如下:图片选择插件wechat_assets_
- Android 微信摇一摇功能实现,最近学习传感器,就想实现摇一摇的功能,上网查了些资料,就整理下。如有错误,还请指正。开发环境Androi
- maven配置项目的jdk版本无效排查最近在配置项目的jdk的时候发现在pom.xml中配置的1.8版本无效,maven更新后就变成了1.7
- 本文实例讲述了Android基于SoftReference缓存图片的方法。分享给大家供大家参考,具体如下:Java中的SoftReferen
- 最近在做上传文件的服务,简单看了网上的教程。结合实践共享出代码。由于网上的大多数没有服务端的代码,这可不行呀,没服务端怎么调试呢。Ok,先上
- 最近的项目中要实现一个聊天的功能,类似于斗鱼TV的聊天室功能,与服务器端人商量后决定用WebSocket来做,但是在这之前我只知道Socke
- 前言上一篇我们认识了Kotlin编程语言,也搭建好开发环境。本篇就进入Kotlin的基础语法介绍,与其他编程语言一样,Kotlin也有自己的
- 前言gps定位服务的学习是这段时间gps课程的学习内容,之前老师一直在将概念,今天终于是实践课(其实就是给了一个案例,让自己照着敲).不过在
- 本文实例为大家分享了Android实现图片设置圆角形式的具体代码,供大家参考,具体内容如下1.自定义的图片圆角形式CircleImageVi