当前位置: 首页 > news >正文

【Android】消息机制

首先要清楚的是,Android 的 UI 线程(也就是主线程)不能执行长耗时操作,否则会造成 ANR 程序无法响应异常,在子线程也无法直接更新 UI 元素,所以要多线程机制的参与,Android 提供了消息机制,其上层接口就是 Handler。

消息机制的 Handler 体系主要包括 Handler、Looper 和 MessageQueue,Handler 是消息的生产者和消费者,是消息的提交和处理入口;Looper 是消息的循环者,负责从 MessageQueue 取出 Message 消息,调用消息目标的 Handler 去处理;MessageQueue 则是消息队列,负责消息的自然排序存储。

1 个线程至多持有 1 个 Looper,每个 Looper 实例持有 1 个 MessageQueue,所以 Thread、Looper 和 MessageQueue 是对应的映射关系,不同的 Message 应该有不同的处理逻辑,所以 MessageQueue 允许注册多个 Handler。

Android 的消息机制概述

普通线程 Thread 初始化后不持有 Looper 实例,要通过调用 Looper.prepare 方法来获得 Looper 实例,并调用 Looper.loop 方法使 Looper 进入轮询循环,不断从 MessageQueue 获取信息。

new Thread(v -> {Looper.prepare();Looper.loop();
}).start();

Handler 的构造方法有 3 种,分别是

  1. 空参构造,内部会使用 Looper.myLooper 方法来获取当前线程的 Looper 并绑定
  2. Handler(Looper looper),明确指定消息的执行线程
  3. 带 Callback 构造,可以自定义消息处理逻辑,不用重写 handlerMessage 方法

所以使用原始 Thread 的消息队列构造应该是

Handler handler;
new Thread(v -> {Looper.prepare();handler = new Handler() {@Overrideboolean handleMessage(Message msg) {// 消息处理逻辑}}Looper.loop();
}).start();

这种初始化方法的缺陷是外部 handler 的生命周期依赖于匿名线程,handler 在 Looper.prepare 前是空指针,更优雅的方式是使用 HandlerThread

HandlerThread handlerThread = new HandlerThread("handlerThread"); // 线程名称
handlerThread.start();  // 启动 HandlerThreadHandler handler = new Handler(handlerThread.getLooper()) {@Overridepublic void handleMessage(Message msg) {// 消息处理逻辑}
};

初始化消息循环后,可以调用 handler 实例的 sendMessage 或 post 来提交任务,前者提交 Message 实例,执行并返回结果;后者直接提交 Runnable 直接执行逻辑,不关心消息标识。提交任务相当于投递到 MessageQueue,由 Looper 线程循环处理,调度执行提交 Message 的 Handler 的 dispatchMessage 方法,最后可能由 Runnable 执行,或根据构造 Handler 时提供的 Callback 执行,如果二者都为 null,则执行 handlerMessage 逻辑。Handler 消息机制默认是 Callback 的,也就是不支持返回值,可以通过回调接口或回复 Message 来获取执行结果。

持有关系上,Thread 持有 Looper 实例,Looper 持有外部 Thread 引用和 MessageQueue 实例,Message 实体类持有 int 类的消息类型句柄 what、Object 类的数据 obj、发送自身的 Handler 实例和可指定的处理方式 Callback,Handler 持有 Looper 和 MessageQueue 的引用。

Android 的消息机制分析

ThreadLocal 的工作原理

ThreadLocal 是线程内部的数据存储类,只有在指定线程中才可以获取到存储的数据,类似 MINECRAFT 的末影箱,当某些数据是以线程为作用域且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。

public void set(T value) {Thread currenThread = Thread.currentThread();Values values = values(currentThread);if (values == null) {values = initializeValues(currentThread);}values.put(this, value);
}

Thread 类内部有专门存储 ThreadLocal 数据的 ThreadLocal.Values localValues,在 localValues 内部有 Object 类型的数组 table,ThreadLocal 的值就存在 table 数组中。在现代 Android 中,存储 ThreadLocal 数据的字段变成 ThreadLocalMap threadLocals,本质是映射表,key 是 ThreadLocal 对象本身的弱引用,value 是该线程存储的具体对象,所以代码块中最后有向 Map 加键值对的 put 方法,覆盖具体对象 value 变成新值。因为 set 方法局限在独立线程内,所以 Thread 存储的 ThreadLocal 数据往往只存储 1 个键值对,也就是当前 ThreadLocal 弱引用和该线程的存储数据。

ThreadLocal 在消息机制中起到什么作用,让我们看 Looper 的 prepare 简化方法流程,其中的 sThreadLocal 就是 ThreadLocal 实例,Looper 内部使用它来保存线程对应的 Looper,创建 Looper 后将其绑定到当前线程的 ThreadLocal,Looper.myLooper 方法也是通过访问 ThreadLocal 来获得当前线程的 Looper。

private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();public static void prepare() {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper per thread");}sThreadLocal.set(new Looper());
}

消息队列的工作原理

消息队列即 MessageQueue,内部实现是单链表,首先我们要理解消息队列的特性,消息按时间顺序发送和处理,也就是 FIFO 先进先出,消息随时可能被插入或删除,因此消息队列的长度是动态变化的。

Android 的 MessageQueue 没有单独维护尾指针,所以插入普通消息要遍历至链表尾插入,时间复杂度 O(n),插入延时消息 MessageQueue 会从链表头指针开始按时间比较和遍历,找到延时消息的插入位置,取消息直接取链表头节点即可,所以是常数时间复杂度。

Handler 的工作原理

在概述部分我们知道,提交的消息事务会经过 Handler 的 dispatchMessage 方法,Message 消息本身如果通过 post 方法提交会带有 Runnable callback 执行体,也就是代码块的 handlerCallback 方法,Handler 会优先处理 post 消息,其次 Handler 会处理构造方法中若提供的 Handler.Callback mCallback 的 handleMessage 方法,这种方式避免重写 Handler 本身的 handleMessage 方法,方便复用,最后才会执行 Handler 本身的 handleMessage 方法。

值得注意的是,mCallback 的 handleMessage 方法会返回 boolean 来表示是否已经完全处理消息,类似 View 的事件分发机制,如果返回 false 则 dispatchMessage 方法会继续将消息传播到 Handler 本身的 handleMessage 来执行。

public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
private static void handleCallback(Message message) {message.callback.run();
}

主线程的消息循环

Android 的主线程就是 ActivityThread,主线程的入口方法是 main,该方法内系统会通过 Looper.prepareMainLooper 来创建主线程的 Looper 和消息队列,并通过 Looper.loop 开启消息循环。用户点击等事件和我们在 Activity 类代码中设定的任务都会作为消息事务进入主线程的消息队列,再由 Looper 不断分发交付处理。

public static void main(String[] args) {...Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}AsyncTask.init();if (false) {Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));}Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}
http://www.dtcms.com/a/562015.html

相关文章:

  • 资料分析-平均数(和比重很像,可以对比学习)
  • 注解(内置注解、元注解、自定义注解)
  • nginx安装与升级
  • 开网站卖茶要怎么做设计很好的视觉很棒的网站
  • Day02计算机网络网络层学习总结:从协议到路由全解析
  • 网站建设公司 预算培训机构前端开发
  • 文献管理 Mendeley合并两个论文数据库
  • 泰兴网站推广东阳厂家高端网站设计
  • 如何利用 DeepSeek 提升工作效率-test
  • 青岛开发区做网站设计的wordpress猜你喜欢插件
  • Windows 10安装Linux虚拟机完整指南:三种方法详解
  • mysql数据库的sql优化以及explain周期字段详解案例【爽文】
  • wordpress 站点语言优秀网站h5案例分享
  • 建网站要多长时间功能最多的wordpress主题
  • 计算机图形学·5 OpenGL编程2 完整程序
  • 透明化战场:俄罗斯如何适应数字战争时代
  • 网站程序语言那个好网站建设合同封面
  • a站是指哪个网站深圳网站建设公司推荐乐云seo
  • C语言内功强化之const修饰指针
  • spiderdemo第八题
  • 青州网站搭建重庆安全建设工程信息网
  • 会议网站建设方案模板布吉网站的建设
  • MIT-大整数相乘和大矩阵相乘
  • 网站建设分析书引言恩施哪里有做网站的
  • php手机网站开发长春电商网站建设公司排名
  • 微信网站怎么做的淮北哪有做网站的
  • 基于 Qemu 的内核模块开发初体验
  • 网站建设是前端么一级a做爰片免费网站孕交视频
  • 如何下载纯净版操作系统
  • 计网5.3.2 TCP报文段