Android 消息机制【笔记】

举报
Amrf 发表于 2020/12/02 09:40:49 2020/12/02
【摘要】 [Android消息机制(一):概述设计架构](https://www.jianshu.com/p/8656bebc27cb)学习Android的消息机制,有几个设计概念我们必须了解:消息:Message消息(Message)代表一个行为(what)或者一串动作(Runnable),每一个消息在加入消息队列时,都有明确的目标(Handler)。消息队列:MessageQueue以队列的形式对...

[Android消息机制(一):概述设计架构](https://www.jianshu.com/p/8656bebc27cb)
学习Android的消息机制,有几个设计概念我们必须了解:

消息:Message
消息(Message)代表一个行为(what)或者一串动作(Runnable),每一个消息在加入消息队列时,都有明确的目标(Handler)。
消息队列:MessageQueue
以队列的形式对外提供插入和删除的工作,其内部结构是以链表的形式存储消息的。
Looper
Looper是循环的意思,它负责从消息队列中循环的取出消息然后把消息交给目标(Handler)处理。
Handler
消息的真正处理者,具备获取消息、发送消息、处理消息、移除消息等功能。
线程
线程,CPU调度资源的基本单位。Android中的消息机制也是基于线程中的概念。
ThreadLocal
可以理解为ThreadLocalData,ThreadLocal的作用是提供线程内的局部变量(TLS),这种变量在线程的生命周期内起作用,每一个线程有他自己所属的值(线程隔离)。

[Android 消息机制详解](https://www.jianshu.com/p/3b8c2dbf1124)
消息机制的流程如下:
1.准备阶段:
* 在子线程调用 Looper.prepare() 方法或 在主线程调用 Lopper.prepareMainLooper() 方法创建当前线程的 Looper 对象(主线程中这一步由 Android 系统在应用启动时完成)
* 在创建 Looper 对象时会创建一个消息队列 MessageQueue
* Looper 通过 loop() 方法获取到当前线程的 Looper 并启动循环,从 MessageQueue 不断提取 Message,若 MessageQueue 没有消息,处于阻塞状态
2.发送消息
* 使用当前线程创建的 Handler 在其它线程通过 sendMessage() 发送 Message 到 MessageQueue
* MessageQueue 插入新 Message 并唤醒阻塞
3.获取消息
* 重新检查 MessageQueue 获取新插入的 Message
* Looper 获取到 Message 后,通过 Message 的 target 即 Handler 调用 dispatchMessage(Message msg) 方法分发提取到的 Message,然后回收 Message 并继续循环获取下一个 Message
* Handler 使用 handlerMessage(Message msg) 方法处理 Message
4.阻塞等待
* MessageQueue 没有 Message 时,重新进入阻塞状态

[Multi-Threaded Android: Handler, Thread, Looper, and Message Queue](https://medium.com/better-programming/a-detailed-story-about-handler-thread-looper-message-queue-ac2cd9be0d78)

[how to use postDelayed() correctly in android studio?](https://stackoverflow.com/questions/42379301/how-to-use-postdelayed-correctly-in-android-studio)

[Handler、Thread、HandlerThread三者的区别](https://blog.csdn.net/weixin_41101173/article/details/79687313)
①Handler:在android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。
②Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
③HandlerThread:一个继承自Thread的类HandlerThread,Android中没有对Java中的Thread进行任何封装,而是提供了一个继承自Thread的类HandlerThread类,这个类对Java的Thread做了很多便利的封装。
Andriod提供了 Handler  和  Looper  来满足线程间的通信。 Handler 先进先出原则。 Looper 类用来管理特定线程内对象之间的消息交换 (MessageExchange) 。 
1)Looper:  一个线程可以产生一个 Looper 对象,由它来管理此线程里的 MessageQueue( 消息队列 ) 和对消息进行循环。 
2)Handler:  你可以构造 Handler 对象来与 Looper 沟通,以便 push 新消息到 MessageQueue 里 ; 或者接收 Looper 从 Message Queue 取出 所送来的消息。 
3) Message Queue( 消息队列 ): 用来存放线程放入的消息。 
4) Message:是线程间通讯的消息载体。两个码头之间运输货物,Message充当集装箱的功能,里面可以存放任何你想传递的消息。
看到这里就明白了为什么:如果一个线程要处理消息,那么它必须拥有自己的Looper,并不是Handler在哪里创建,就可以在哪里处理消息。
注:对应关系Thread(1):Looper(1):MessageQueen(1):Handler(n).

[android之handler切换线程终极篇](https://blog.csdn.net/mysimplelove/article/details/79613361?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control)

[Handler消息机制详解,另对于MessageQueue阻塞线程的详解](https://blog.csdn.net/zip_tts/article/details/86097136?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control)
用送信的方式来形容如下:
Handler:收信人/发件人,执行具体的操作/发送消息
Message:信封,线程发送的消息实体
MessageQueue:邮筒,消息队列,存放消息实体
Looper:送信人,从消息队列中取消息并进行分发
至此,Handler,Message,Looper,MessageQueue四者之间的关系已经清晰了:
Message作为消息载体,用于承载消息。
MessageQueue作为消息队列,用于存放消息。
Looper作为消息传送的动力,用于获取分发消息。
Handler作为消息处理中心,用于处理消息和发送消息。

[Android HandlerThread详解](https://blog.csdn.net/VNanyesheshou/article/details/75073307)
HandlerThread适合处理本地IO读写操作(数据库,文件),因为本地IO操作大多数的耗时属于毫秒级别,对于单线程 + 异步队列的形式 不会产生较大的阻塞。而网络操作相对比较耗时,容易阻塞后面的请求,因此在这个HandlerThread中不适合加入网络操作。

[Android 多线程之HandlerThread 完全详解](https://blog.csdn.net/javazejian/article/details/52426353)
HandlerThread有那些特点:
HandlerThread本质上是一个线程类,它继承了Thread;
HandlerThread有自己的内部Looper对象,可以进行looper循环;
通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务。
创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。

[Decoding Handler and Looper in Android](https://proandroiddev.com/decoding-handler-and-looper-in-android-d4f3f2449513)
Handler和Looper是主要的底层Android OS之一,几乎没有人直接使用(至少在今天)。但是它们是核心,因此许多其他高级解决方案都基于它们。因此,应该知道Handler和Looper是什么,以及它们的运行方式。
Looper是事件循环的抽象(无限循环,它使事件排入队列),而Handler是将事件放入/移出事件的队列(由Looper排空)并在处理这些事件时对其进行处理的抽象。
Message
Message数据对象. It contains payload of the “message”. 由于可以将其发送到各种Android组件, 因此Message类实现了Parcelable接口(so that it can be put in extras to bundles and intents).
作为数据对象Message可以在其中包含一些数据。主要是:
int what —消息代码,以了解该消息的含义(例如,它代表什么动作)
int arg1,arg2—简单的整数基元,用于存储一些有效载荷值
Object obj —自定义对象,我们可能会发送
Runnable callback -我们可能会发送的自定义操作
注意:消息中并非所有这些项目都是必需的。另外,某些项目彼此排斥。例如,消息有两种主要类型:1)使用的what消息和使用的消息callback。
这意味着我们可以在Message内部发送一些操作(使用callback),也可以发送任意消息(使用what)
Message是公共类,具有公共构造函数以及上述所有字段都是公共的。因此,从技术上讲,可以创建消息实例并手动设置所有必需的数据。但是出于性能原因,Message最佳实践是使用obtain方法从Messages池中获取已创建的Message实例(目前未使用的),并使用所需数据对其进行初始化。
用法非常简单,例如,如果我们想使用what和获取自定义Message obj,那么我们将编写:
val msg = Message.obtain(handler, what, object)
MessageQueue
作为名称状态的MessageQueue只是消息队列。这正是Handler在其中放入事件以及Looper正在消耗事件的队列的实现。
Looper
Looper是为某个线程运行消息循环的类。在Java中,我们有线程,我们可以在其中做一些有用的工作。最初,线程没有事件循环功能,但是我们可以通过将Looper附加到它们来添加此功能。
Looper有两种主要方法:prepare和loop。第一种方法prepare-初始化线程并将Looper附加到该线程。第二种方法loop启动事件循环。
注意:
1.应该清楚的是,在将Looper连接到线程之前运行Looper会导致异常。确保prepare在线程内调用了该方法,然后才调用loop。
2.每个线程只能有一个Looper。这由ThreadLocal<Looper>Looper类内的对象控制。因此prepare,在同一线程内两次调用方法也会导致异常。
在Looper类中quit,我们感兴趣的最后一种方法是和quitSafely。这些方法用于停止事件循环。第一个是不安全的,因为它将终止对队列的处理,并且某些事件可能未被处理。第二个等待,直到所有消息都处理完毕,然后终止,因此称为安全。
Handler
Handler是一个类,它允许您从线程的MessageQueue发送和处理Messages和Runnables。
基本上,它的工作方式如下:

1.客户端初始化Handler和Looper(因此现在可以使用整个基础结构了)
2.客户端向处理程序发送消息
3.处理程序将消息发布到Looper的MessageQueue
4.Looper处理MessageQueue中的消息
5.当准备好处理消息时,Looper将其发送回Handler,后者可以处理消息。
因此,我们看到Handler做了两件事:1)发布要放入队列中的消息,2)处理由Looper处理的消息。

让我们分别看每件事。我们将从发布消息开始。
我们已经知道,基本上有两种主要的消息类型:what和Runnable。
为了发送已创建的消息(例如what),我们可以使用sendMessage,sendMessageDelayed和sendMessageAtTime方法。从名称中可以明显看出,sendMessage要尽快发送要处理的消息,sendMessageDelayed在超时后sendMessageAtTime发送要处理的消息,并在某个特定时刻发送要处理的消息。很明显。
还有一种方法可以直接发布Runnable而无需先为此创建显式消息。对于这个有post,postDelayed而且postAtTime方法。它们与sendMessageXXX方法的含义相同,不同之处仅在于我们发送的Runnable不是已创建的消息。

注意:Handler类中还有其他方法可以使您将消息发送到队列(例如sendEmptyMessage),尽管它们只是我们已经学到的东西的组合,并且是某种捷径。我强烈建议您查看源代码或文档,以了解有关公共API的更多信息。

为了在Looper处理消息时处理消息,我们可以执行以下两项操作之一:
Handler用我们自己的自定义实现和重写handleMessage方法扩展类
Handler使用提供自定义的构造函数创建实例Handler.Callback(具有handleMessage方法)
消息准备就绪后,Looper会将它们发送到这些回调。

最后但并非最不重要的一点是,Handler不仅可以将消息添加到事件队列中,还可以将其删除。对于这种情况,有一些removeMessages方法可以触发从MessageQueue中删除满足某些条件的未处理消息。

基本上就是关于Handler。如果您感到困惑,请放心,后面的示例中将提供示例,因此希望所有内容都将更加清晰。

HandlerThread
HandlerThread只是具有事件循环的线程。这意味着,如果您需要带有Looper的线程,则无需自己创建它,只需执行即可HandlerThread。

用法
在进行示例之前,让我们快速回顾一下类,它们的作用以及如何进行连接。如果我们需要某个线程内的事件循环,则其基本结构如下:

您需要有一个线程(它可能是主线程,HandlerThread或您的自定义线程)
在该线程内部,准备并启动Looper(线程现在将使用其自己的MessageQueue进行事件循环)
注:主线程已经初始化了Looper
从该线程使用Looper创建处理程序(将消息发送到事件循环,然后在处理它们时对其进行处理)
将消息发送到Handler并在Handler的回调中处理消息
太酷了,一切听起来都合乎逻辑,但是我们错过了一件事:为什么我们需要所有这些东西,我们试图解决什么问题?
根据经验,在以下情况下,Handler非常适合使用:

安排一些工作在将来的某个时间执行
使要在与您自己的线程不同的线程上执行的动作排队(我们将在后面看到,这扩展到向与您自己不同的线程中的线程发送一些动作)

[How threads work in Android?](https://medium.com/@souravgupta14/how-threads-work-in-android-3a0ba4ca79ae)
在后台线程上执行任务时,最困难的部分不是构建线程本身,而是与线程通信。那就是Handler对象进入的地方。

假设您要查询数据库并检索所有国家列表,则需要告诉后台线程查询数据库,然后将国家列表发送回可以显示它们的主线程。我们需要做的第一件事是将消息发送到后台线程,并让它知道我们想要做什么。我们在主线程上创建一个消息对象,该消息对象将使用Handler发送到后台线程,然后将消息添加到消息队列中,并在到达堆栈顶部时执行。数据库查询完成后,循环程序将清理消息队列,然后将查询数据发送回主线程。
再次创建另一个消息对象,使用捆绑将数据附加到消息对象,并且主线程处理程序有助于消息从后台线程到主线程的传递。接收到消息后,它将在主线程的消息队列中移动,并且最终将数据显示在视图中。
您可以使用各种技术在android中创建线程。
Threads
Handler Threads
Async Task
Thread pool

[Android- Handlers, Loopers, MessageQueue Basics](https://medium.com/@mr.anmolsehgal/android-handlers-loopers-messagequeue-basics-d56a750df2cc)
Android体系结构具有主线程AKA UI线程,该线程在每16ms帧后更新一次UI 。无法在此窗口中更新将反映为“滞后”,甚至更糟,如果它持续5秒钟失败,则向用户显示“应用程序无响应”,表明应用程序崩溃。
So the basic design follows:
1. Handler thread can receive messages from other threads.
2. Handler thread has a looper that reads queue for any new messages.
3. Handler Thread has a Message Queue where the messages are written by other threads via a handler.
4. Looper loops until it reads the message, and then it calls the callback function set by the worker thread, within the Handler Thread.
5. And thread can write to this HandlerThread if it has its looper.
He can do so by creating a handler(with its looper as param), and then sending messages via this handler.
6. Looper.getMainLooper() returns the looper for mainThread, which can be passed into the handler to write to the MainThread directly from any other thread.

[Using HandlerThread in Android](https://medium.com/@frank.tan/using-handlerthread-in-android-46c285936fdd)
--https://github.com/frank-tan/AndroidMultithreadingBlogs
Android提供了高级多线程选项,例如AsyncTask和IntentService,可以将长时间运行的阻塞任务卸载到单独的线程中。HandlerThread是低级别的线程替代方案,因此讨论不多。HandlerThread允许您使用单个消息队列运行工作线程。
1.您想要一个重量轻的替代方案来运行一些非常简单的任务,而AsyncTask和IntentService则显得过大,例如与摄像头或加速度计进行通信。
2.您想要在活动或片段的生命周期中执行一些简短的任务,例如文件或数据库操作。值得注意的是,Android框架中提供的AsyncQueryHandler类使用HandlerThread来使数据库CRUD操作异步化。
3.您需要在工作线程上按顺序处理一些简单的任务。
要使用HandlerThread,重要的是要了解Android如何管理线程之间传递的消息。
处理程序是 工作线程上的消息处理器。消息是从例如UI线程卸载的数据或工作(可运行)。在Handler上收到消息时,它将消息添加到消息队列。每个线程只能有一个消息队列。Looper是一个消息分派器,它一次从Message Queue中获取一条消息,然后将其分派给Handler进行处理。

知道消息传递的工作原理后,HandlerThread变得非常简单。在这里,我扩展了HandlerThread类,以允许自定义处理逻辑(休眠并将消息发布到UI线程)。当然,在现实生活中的项目中,您可能会进行文件操作或访问硬件等。
* 避免内存泄漏
1.使用HandlerThread或实际上任何多线程技术时都应注意的一件事是内存泄漏。

2.避免在活动中使用非静态内部类。非静态内部类将隐式引用托管活动,并阻止该活动被破坏。
如果您确实需要引用该活动,请使用WeakReference。
* 避免将长时间运行的任务与短时间的任务混合在一起
因为HandlerThread只有一个消息队列,并且所有消息都是按顺序处理的,所以长时间运行的阻塞任务将使所有短期运行的任务等待。更好的方法是将长时间运行的任务分隔为单独的HandlerThread或使用其他线程技术。

[HandlerThread](https://developer.android.com/reference/android/os/HandlerThread)
[AndroidManifest.xml详解](https://www.jianshu.com/p/3b5b89d4e154)
[Android五大UI布局的特有属性](https://blog.csdn.net/The_king_of_Asia/article/details/53982418)

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200