Android 消息机制详解及实例代码
发布时间 - 2026-01-10 23:08:37 点击率:次Android 消息机制

1.概述
Android应用启动时,会默认有一个主线程(UI线程),在这个线程中会关联一个消息队列(MessageQueue),所有的操作都会被封装成消息队列然后交给主线程处理。为了保证主线程不会退出,会将消息队列的操作放在一个死循环中,程序就相当于一直执行死循环,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数(handlerMessage),执行完成一个消息后则继续循环,若消息队列为空,线程则会阻塞等待。因此不会退出。如下图所示:
Handler 、 Looper 、Message有啥关系?
在子线程中完成耗时操作,很多情况下需要更新UI,最常用的就是通过Handler将一个消息Post到UI线程中,然后再在Handler的handlerMessage方法中进行处理。而每个Handler都会关联一个消息队列(MessageQueue),Looper负责的就是创建一个MessageQueue,而每个Looper又会关联一个线程(Looper通过ThreadLocal封装)。默认情况下,MessageQueue只有一个,即主线程的消息队列。
上面就是Android消息机制的基本原理,如果想了解更详细,我们从源码开始看。
2.源码解读
(1)ActivityThread主线程中启动启动消息循环Looper
public final class ActivityThread {
public static void main(String[] args) {
//代码省略
//1.创建消息循环的Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
//2.执行消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
ActivityThread通过Looper.prepareMainLooper()创建主线程的消息队列,最后执行Looper.loop()来启动消息队列。Handler关联消息队列和线程。
(2)Handler关联消息队列和线程
public Handler(Callback callback, boolean async) {
//代码省略
//获取Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//获取消息队列
mQueue = mLooper.mQueue;
}
Handler会在内部通过Looper.getLooper()方法来获取Looper对象,并且与之关联,并获取消息队列。那么Looper.getLooper()如何工作的呢?
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
public static void prepare() {
prepare(true);
}
//为当前线程设置一个Looper
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));
}
//设置UI线程的Looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
在Looper类中,myLooper()方法,通过sThreadLocal.get()来获取的,在prepareMainLooper()中调用prepare()方法,在这个方法中创建了一个Looper对象,并将对象设置了sThreadLocal()。这样队列就和线程关联起来了。通过sThreadLocal.get()方法,保证不同的线程不能访问对方的消息队列。
为什么要更新UI的Handler必须在主线程中创建?
因为Handler要与主线程的消息队列关联上,这样handlerMessage才会执行在UI线程,此时UI线程才是安全的。
(3)消息循环,消息处理
消息循环的建立就是通过Looper.loop()方法。源代码如下:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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.");
}
//1.获取消息队列
final MessageQueue queue = me.mQueue;
//2.死循环,即消息循环
for (;;) {
//3.获取消息,可能阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//4.处理消息
msg.target.dispatchMessage(msg);
//回收消息
msg.recycleUnchecked();
}
}
从上述程序我们可以看出,loop()方法的实质上是建立一个死循环,然后通过从消息队列中逐个取出消息,最后处理消息。对于Looper:通过Looper.prepare()来创建Looper对象(消息队列封装在Looper对象中),并且保存在sThreadLocal中,然后通过通过Looper.loop()进行消息循环,这两步通常成对出现。
public final class Message implements Parcelable {
//target处理
Handler target;
//Runnable类型的callback
Runnable callback;
//下一条消息,消息队列是链式存储的
Message next;
}
从源码中可以看出,target是Handler类型。实际上就是转了一圈,通过Handler发送消息给消息队列,消息队列又将消息分发给Handler处理。在Handle类中:
//消息处理函数,子类覆写
public void handleMessage(Message msg) {
}
private static void handleCallback(Message message) {
message.callback.run();
}
//分发消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
从上述程序可以看出,dispatchMessage只是一个分发的方法,如果Run nable类型的callback为空,则执行handleMessage来处理消息,该方法为空,我们会将更新UI的代码写在该函数中;如果callback不为空,则执行handleCallback来处理,该方法会调用callback的run方法。其实这是Handler分发的两种类型,比如post(Runnable callback)则callback就不为空,当我们使用Handler来sendMessage时通常不设置callback,因此,执行handlerMessage。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public String getMessageName(Message message) {
if (message.callback != null) {
return message.callback.getClass().getName();
}
return "0x" + Integer.toHexString(message.what);
}
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;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
从上述程序可以看到,在post(Runnable r)时,会将Runnable包装成Message对象,并且将Runnable对象设置给Message对象的callback,最后会将该对象插入消息队列。sendMessage也是类似实现:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
不管是post一个Runnable还是Message,都会调用sendMessageDelayed(msg, time)方法。Handler最终将消息追加到MessageQueue中,而Looper不断地从MessageQueue中读取消息,并且调用Handler的dispatchMessage分发消息,这样消息就源源不断地被产生、添加到MessageQueue、被Handler处理,Android应用就运转起来了。
3.检验
new Thread(){
Handler handler = null;
public void run () {
handler = new Handler();
};
}.start();
上述代码有问题吗?
Looper对象是ThreadLocal的,即每个线程都用自己的Looper,这个Looper可以为空。但是,当在子线程中创建Handler对象时,如果Looper为空,那么会出现异常。
public Handler(Callback callback, boolean async) {
//代码省略
//获取Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//获取消息队列
mQueue = mLooper.mQueue;
}
当mLooper为空时,抛出异常。这是因为Looper对象没有创建,因此,sThreadLocal.get()会返回null。Handler的基本原理就是要与MessageQueue建立关联,并且将消息投递给MessageQueue,如果没有MessageQueue,则Handler没有存在的必要,而MessageQueue又被封住在Looper中,因此创建Handler时,Looper一定不能为空。解决办法如下:
new Thread(){
Handler handler = null;
public void run () {
//为当前线程创建Looper,并且绑定到ThreadLocal中
Looper.prepare()
handler = new Handler();
//启动消息循环
Looper.loop();
};
}.start();
如果只创建Looper不启动消息循环,虽然不抛出异常,但是通过handler来post或者sendMessage()也不会有效。因为虽然消息会被追加到消息队列,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# Android
# 消息机制
# 消息机制详细介绍
# 消息机制分析
# android异步消息机制 从源码层面解析(2)
# android异步消息机制 源码层面彻底解析(1)
# 代码分析Android消息机制
# Android异步消息机制详解
# android线程消息机制之Handler详解
# android利用消息机制获取网络图片
# Android的消息机制
# Android消息机制Handler的工作过程详解
# 深入剖析Android消息机制原理
# Android 消息机制以及handler的内存泄露
# 从源码角度分析Android的消息机制
# 为空
# 会将
# 可以看出
# 在这个
# 链式
# 抛出
# 基本原理
# 自己的
# 类中
# 这是
# 情况下
# 也不
# 放在
# 也就
# 才是
# 子类
# 起来了
# 就不
# 才会
# 会在
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
消息称 OpenAI 正研发的神秘硬件设备或为智能笔,富士康代工
详解jQuery中基本的动画方法
UC浏览器如何设置启动页 UC浏览器启动页设置方法
bootstrap日历插件datetimepicker使用方法
Laravel如何实现用户注册和登录?(Auth脚手架指南)
今日头条AI怎样推荐抢票工具_今日头条AI抢票工具推荐算法与筛选【技巧】
Laravel如何实现全文搜索功能?(Scout和Algolia示例)
使用豆包 AI 辅助进行简单网页 HTML 结构设计
标题:Vue + Vuex 项目中正确使用 JWT 进行身份认证的实践指南
使用C语言编写圣诞表白程序
如何批量查询域名的建站时间记录?
Laravel如何生成和使用数据填充?(Seeder和Factory示例)
Laravel如何处理异常和错误?(Handler示例)
如何在搬瓦工VPS快速搭建网站?
Laravel如何配置.env文件管理环境变量_Laravel环境变量使用与安全管理
如何基于云服务器快速搭建个人网站?
Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)
laravel怎么在请求结束后执行任务(Terminable Middleware)_laravel Terminable Middleware请求结束任务执行方法
Python结构化数据采集_字段抽取解析【教程】
香港服务器WordPress建站指南:SEO优化与高效部署策略
微信h5制作网站有哪些,免费微信H5页面制作工具?
INTERNET浏览器怎样恢复关闭标签页_INTERNET浏览器标签恢复快捷键与方法【指南】
Laravel项目怎么部署到Linux_Laravel Nginx配置详解
如何在香港服务器上快速搭建免备案网站?
香港服务器租用费用高吗?如何避免常见误区?
大连企业网站制作公司,大连2025企业社保缴费网上缴费流程?
laravel怎么为API路由添加签名中间件保护_laravel API路由签名中间件保护方法
图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?
Laravel表单请求验证类怎么用_Laravel Form Request分离验证逻辑教程
在centOS 7安装mysql 5.7的详细教程
Laravel Asset编译怎么配置_Laravel Vite前端构建工具使用
Python正则表达式进阶教程_复杂匹配与分组替换解析
Laravel与Inertia.js怎么结合_使用Laravel和Inertia构建现代单页应用
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
宙斯浏览器文件分类查看教程 快速筛选视频文档与图片方法
Laravel如何实现API版本控制_Laravel版本化API设计方案
如何用美橙互联一键搭建多站合一网站?
Laravel软删除怎么实现_Laravel Eloquent SoftDeletes功能使用教程
Laravel如何使用withoutEvents方法临时禁用模型事件
Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道
Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】
javascript中数组(Array)对象和字符串(String)对象的常用方法总结
高防服务器:AI智能防御DDoS攻击与数据安全保障
个人摄影网站制作流程,摄影爱好者都去什么网站?
Laravel Blade模板引擎语法_Laravel Blade布局继承用法
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
如何快速查询网站的真实建站时间?
,怎么在广州志愿者网站注册?
Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道
jimdo怎样用html5做选项卡_jimdo选项卡html5实现与切换效果【指南】

