Android是基于事件驱动,每一个触摸事件或者是Activity的生命周期都是运行在Looper.looper()的控制之下,理解弄懂消息机制十分必要
Android的消息机制也就是handler机制,主要作用是用来在不同线程之间通信,通常使用在子线程执行完成一些耗时操作,需要回到主线程更新界面UI时,通过Handler将有关UI的操作切换到主线程
工作流程
- 当应用程序启动的时候,主线程默认调用了Looper.looper()方法,初始化Looper对象并绑定到当前线程中,并在Looper内部维护一个MessageQueue
- 调用handler.sendMessage()发送消息,会通过messageQueue.enqueueMessage()向messageQueue中添加一条消息
- 主线程调用Looper.looper()开启循环,不断轮询消息队列,取出消息
-
取出的message不为空则调用msg.target.dispatchMessage()传递分发消息,目标handler收到消息后对消息进行处理
-
主线程中创建一个 Looper,并进入无限循环状态,不断处理消息队列中的消息。
子线程中可以创建一个 Handler 对象,并将其与子线程关联。通过 Handler,子线程可以向主线程发送消息。
当子线程需要将消息发送到主线程时,它可以创建一个 Message 对象,并通过 Handler 的 sendMessage() 方法发送消息。
主线程中的 Looper 不断从消息队列中获取消息,并将其传递给关联的 Handler 进行处理。Handler 根据消息的类型,执行对应的处理逻辑,例如更新 UI 界面。
详细分析
Looper:
字面意思是“循环者“,被设计用来使一个普通线程变成Looper线程,即执行循环工作的线程
-
Looper的创建(Looper.prepare())
- 在应用程序的入口方法中系统已经帮我们创建好Looper对象
- 子线程的Looper需要我们自己手动创建
一个Thread只有一个Looper
创建的looper会被保存在线程本地存储区(TLS)中
Handler
handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息)
- Hanlder发出的message有以下特点:
- message.target为该handler对象确保looper执行到该message时可以找到处理它的handler
- handler处理消息
- handler可以在任意线程中发送消息,这些消息会被添加到关联的MQ上
- handler时在它关联的looper线程中处理消息的
Message
message又叫task,封装了任务携带的信息和处理该任务的handler
- 注意事项
- 尽管message有public的默认构造方法,但我们应该从Message的obtain()来从消息池中获的空消息对象,以节省资源
- 如果我们的message只需要携带简单的int信息,使用Message.arg1和Message.arg2来传递信息比用Bundle更省内存
- 擅用message.what来标识信息,以便用不同的方式处理message
问题思考
-
为什么一个handler只有一个looper
- 一个Handler只能与一个Looper对象关联,是因为Handler的作用是将消息发送到消息队列并进行处理。而Looper负责循环处理消息队列中的消息。
当我们创建一个Handler时,它会自动与当前线程的Looper对象进行关联。Handler通过Looper来访问消息队列,将消息发送到队列中或者从队列中取出消息进行处理。
一个Handler只能与一个Looper关联的原因是为了确保消息的有序处理。如果一个Handler可以与多个Looper关联,那么消息就会同时发送到多个消息队列中,造成混乱和不可预测的结果。
通过限制一个Handler与一个Looper的关联,可以确保消息的处理是有序的,保持了线程之间的隔离和可控性。这样设计的好处是可以简化代码逻辑,提高代码的可读性和可维护性。同时,也符合了单一职责原则,每个Handler负责处理特定Looper的消息,避免了多个线程之间的竞争和冲突。
- 一个Handler只能与一个Looper对象关联,是因为Handler的作用是将消息发送到消息队列并进行处理。而Looper负责循环处理消息队列中的消息。
-
为什么一个线程只有一个looper
-
在Android系统中,Looper是一种线程本地的消息循环机制,它允许在单独的线程中处理消息队列。当一个线程被创建时,默认情况下它是没有与之相关联的消息队列的,如果需要使用消息队列,就需要为这个线程创建一个Looper对象,并将其绑定到线程上。
每个Looper对象只与一个线程相关联,这是因为Looper实际上是通过ThreadLocal实现的。每个线程都有自己的ThreadLocal变量,因此每个线程都有自己的Looper实例。当我们调用Looper.prepare()时,实际上是向当前线程的ThreadLocal中存储一个Looper对象,而调用Looper.loop()时则是从当前线程的ThreadLocal中获取Looper对象,然后开始循环处理消息队列中的消息。
因为一个线程只有一个Looper对象与之相关联,所以一个线程只能拥有一个消息队列。多个Looper对象在同一线程中是不允许的,因为它们会相互干扰,导致消息处理出现问题。所以在一个线程中,只能有一个消息队列,因此也只需要一个Looper对象。
-
-
任何线程都能实例化Handler吗?有没有什么约束条件
-
在Android中,一个线程可以实例化Handler,但是需要遵循以下约束条件:
- 如果Handler用于与UI交互,则该Handler必须在UI线程中实例化,即在主线程中创建。
- 如果Handler用于与后台线程交互,则必须在后台线程中创建,因为Handler会与其所在线程的消息队列相关联。
- 如果在子线程中创建了Handler,则需要在该线程中创建Looper实例,以便为Handler提供消息队列。通常可以使用Looper.prepare()方法创建Looper实例。
- 如果在Activity的onCreate()方法中创建Handler,应该避免在onPause()或onStop()方法中引用该Handler,否则可能会导致内存泄漏。
总之,在创建Handler之前,需要民却handler在哪个线程中使用,并且根据需要在相应的线程中进行创建和使用。
-
-
Looper.loop是一个死循环,拿不到需要处理的messgae会阻塞,那在UI线程中为什么不会导致ANR
- 在 UI 线程中,Looper.loop() 方法是在 ActivityThread 的 main() 方法中调用的,并且在这个方法内部已经处理了一些特殊的情况,比如处理了 ANR 弹窗的逻辑。因此,虽然在 UI 线程中也是通过一个死循环来不断地读取消息队列中的消息并处理,但 Android 系统会监测应用程序在一定时间内是否有响应,如果在规定的时间内没有响应,系统会认为应用程序已经发生了 ANR,此时会弹出 ANR 弹窗来提示用户应用程序已经停止响应。因此,只要应用程序及时地处理消息队列中的消息,就不会导致 ANR。