Android中的消息机制 - 密切协作的 Handler,Looper,MessageQueue

2024-05-13 21:18

本文主要是介绍Android中的消息机制 - 密切协作的 Handler,Looper,MessageQueue,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android中的消息机制 - 密切协作的 Handler,Looper,MessageQueue


1.消息机制概述

知其然,知其所以然 ,既然说到消息机制,那么我们就要弄明白了,为什么需要用到handle机制?
我们知道,Android中的消息机制主要用来更新ui操作 ,可是为什么更新ui操作一定要放到消息机制来实现呢?
void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}
我们知道在更新ui的时候,程序会去检查当前线程是否是当前线程,也就是 ui 线程,否则就会抛出异常。
至于为什么google的Android工程师不允许我们再子线程中更新ui呢,那是因为Android中的ui操作不是线程安全的,如果在多线程下去操作ui而不做任何保护措施,那么就会导致 ui 操作导致不可预期的效果 。
如何解决这种并发的操作ui的问题,1,可以通过加锁,加锁的方式虽然也能够实现,但是使用枷锁却又两个问题存在 
a.加锁会降低ui的访问效率
  b.会让ui操作的逻辑变得复杂
大概是基于这种考虑吧(个人猜测) ,Android 系统工程师 在 ui的更新上,采取了 handler 的机制

2.Android中的消息机制实现

提到Android中的消息机制,不可避免的就要提到handler ,handler作为系统提供给我们使用的上层接口,是我们操作ui的主要交互对象。

2.1.handler的用法

		Handler handler = new Handler(){
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case 1:
					//执行更新ui操作
					break;
				}
			}
		};
		
		new Thread(){
			public void run() {
				
				//执行复杂的计算逻辑
				Message obtain = Message.obtain();
				obtain.what = 1;
				obtain.obj = "更新ui";
				handler.sendMessage(obtain);
			};
		}.start();
如上,当我们在子线程中计算一个复杂的操作,然后需要更新ui的时候,那么我们可以通过handler的handleMessage方法来更新ui操作,两者密切配合,完成更新操作。

2.2.在子线程中使用handler

我们看下面这个代码
		new Thread(){public void run() {Handler handler = new Handler();};}.start();
当子线程的run方法执行的时候,我们满怀期待的去new 一个handler 突然就会发现,what a fuck ,竟然报错了

通过异常信息发现我们没有调用 looper.prepare() ,这是什么鬼 ,通过观察源码发现,

我们接着看 myLooper()的实现




sThreadLocal 是什么 ? ThreadLocal ,我们暂且不管,现在从 sThreadLocal 中取不到looper实例,那么既然 异常信息提示我们调用 Looper.prepare() 我们暂且去看看,


噢,原来是在这里。那我们现在尝试 去调用吧 。
		new Thread(){public void run() {Looper.prepare();Handler handler = new Handler();};}.start();
好了,到此为止,我们终于可以在子线程中 创建一个handler实例了,注意,这里我说的是创建实例,并不代表这样就可以正常使用了。

2.3.子线程中handler的正确使用姿势


以上就是在子线程中创建handler的正确姿势,我们发现 多了一行 Looper.loop() 方法,首先我们看下他的实现

通过looper的代码实现我们可以发现,looper.loop()方法 通过不断的循环,不停的从messageQueue中获取message 进行分发,最终 通过 handler的 dispatchMessage 分发出去我们的消息

我们通过重写handler的 handMessage方法实现自己的业务逻辑

3.Handler,Looper,MessageQueue 之间协作关系和工作流程


图片来源:http://gityuan.com/2015/12/26/handler-message-framework/

以上就是 handler通信机制的流程图,感谢 gitYuan 的图片。

一次handler通信的过程可以分为如下几个步骤 

1.handler 通过sendMessage 方法 发出一个message对象,然后将message对象加入内部的MessageQueue中(链表实现)

2.Looper.looper() 通过不停的循环,处理消息 ,然后将message分发出去交给对应的handler处理

3.使用者通过重写handleMessage 方法处理业务逻辑

4.关于threadLocal 

threadLocal一个泛型类,通过threadLocal我们可以在指定的线程中保存数据。他可以实现线程隔离,分别为不同的线程保存数据副本,做到互不干扰。

4.1  threadLocal在handler中的使用

Looper 类 中维护着一个变量数据
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal 为每一个线程保存了一份looper对象实例
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}
同时呢,looper又是一个私有的构造方法,也就意外着我们不能够在外部显示的构造looper实例。在上面的例子中,我们通过looper.prepare()在线程中使用handler,
下面是prepare的方法实现
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));}
通过sThreadLocal去获取一个looper对象,如果looper不为空抛出异常(也就说明,每一个线程中只能存在一个looper对象),否则呢 就新增一个looper。
我们先看下这个没有任何参数的get方法
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null)return (T)e.value;}return setInitialValue();}
首先会去获取当前线程的ThreadLocalMap 对象,这个对象中维护了一个Entry数组,entry中有维护了value 对象, 也就是 我们的 looper对象。

5.关于handler的认识和理解

1.每个线程中只能有一个looper对象

2.子线程中使用handler必须首先创建looper

3.一个线程中可以有多个handler对象,message对象维护了分发的handler对象实例,所以多个handler的分发不会有问题

4.threadLocal机制保证了每个线程内部只会维护了一个looper对象


问题: 既然looper维护了一个无限循环的队列,为什么主线程不会因此而假死或者卡死呢 ? 同样求解

6.参考

1. gityuan: Android消息机制1
2.《Android开发艺术探索》
3. handler,looper,message,threadLocal源码



这篇关于Android中的消息机制 - 密切协作的 Handler,Looper,MessageQueue的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/986852

相关文章

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

MySQL中的锁和MVCC机制解读

《MySQL中的锁和MVCC机制解读》MySQL事务、锁和MVCC机制是确保数据库操作原子性、一致性和隔离性的关键,事务必须遵循ACID原则,锁的类型包括表级锁、行级锁和意向锁,MVCC通过非锁定读和... 目录mysql的锁和MVCC机制事务的概念与ACID特性锁的类型及其工作机制锁的粒度与性能影响多版本

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超