文章目录
一、前言
Handle
原理是一个老生常谈的事情,这里对其整个流程简单记录一下。这份理解是基于SDK31版本
二、示例代码
对于Handle
来说,主要有两个功能,一个是延迟处理消息,一个是线程间切换(参考链接:https://developer.android.google.cn/reference/android/os/Handler?hl=en)。这里简单定义个Handle
来进行说明:
Handle
有两种定义场景。一种是UI线程,一种是子线程。
UI线程
在主线程创建的话可以在子线程发送消息到主线程
class MainActivity : AppCompatActivity() {
private var handle: Handler = object : Handler(Looper.myLooper()!!){
override fun handleMessage(msg: Message) { //接受消息
super.handleMessage(msg)
Log.e("YM","-->what:${msg.what}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
Thread{
handle.sendEmptyMessage(10) //发送消息
}.start()
}
}
子线程
以下的例子是建立一个两个子线程直接相互通信的示例
class MainActivity : AppCompatActivity() {
private var handle: Handler ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
ThreadA().start()
ThreadB().start()
}
inner class ThreadA : Thread(){
override fun run() {
super.run()
Looper.prepare()
handle = object : Handler(Looper.myLooper()!!){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
Log.e("YM--->ThreadA","--->接受的参数:${msg.what}")
}
}
Log.e("YM--->ThreadA","--->循环开始")
Looper.loop()
Log.e("YM--->ThreadA","--->循环结束")
}
}
inner class ThreadB : Thread(){
override fun run() {
super.run()
Log.e("YM--->ThreadA","--->发送消息")
handle?.sendEmptyMessageDelayed(12,100L)
}
}
}
三、问题
看这篇文章的前提,是希望大家对Message
、MessageQueue
、Loop
、Handle
有个基本的概念。大致的意思如下(纯属个人理解,跟其他人理解可能不太一样)
- Message: 一个消息实体,
Handle
进行发送消息就是发送的这种格式的消息 - MessageQueue:消息队列,用来存储消息的(说个题外话,其实消息队列没存这里面,而是采用链表方式存在Message里面,叫消息队列是,其实解释来说是使用链表创建的队列,来保持先进先出的方式,这个稍后再说。)。
- Loop:用来轮训消息的,并分发给
Handle
- Handle: 这个就是用来发消息,对消息做些简单的加工,然后将拿到的消息回传给业务代码。相当于管理器的作用。
我们先看第一个问题,就是Handle
是如何将Message
传递过去的
1、消息是如何传递的?
当我们调用Handle::sendEmptyMessage()
函数时候,该消息会进入Handle中进行发送消息,该消息在Handle
中会调用MessageQueue::enqueueMessage(Message msg, long when)
将消息添加进去。在MessageQueue::enqueueMessage(Message msg, long when)
函数中会发现,最终是调用Message.next
字段进行赋值到下一个。所以MessageQueue
类只是对消息存储的一个管理类,最终存储是在Message
中的。通过查看Message
源码可以知道,该方式是采用链表的方式进行数据存储,并不是集合的方式进行存储的。这里会延伸出几个问题,首先因为有延迟消息,也有立刻执行的消息,那么链表的排序规则是什么?
2、Message消息的排列规则是什么?
上个问题解释了在Handle
机制中是使用链表这个数据结构进行消息存储的,那么消息排列的规则是什么呢?是如何处理延迟消息和立刻发送的消息的?在上一个问题中可以知道不管哪一类消息最终都会调用MessageQueue::enqueueMessage(Message msg, long when)
进行存储消息的,该函数中有一个变量when
。其值是用来在Loop::loop()
函数取值时候判断是否满足条件,满足条件即取出,不满足跳过。其时间计算方式为SystemClock.uptimeMillis() + delayMillis
。默认为0。到此为止就可以知道Handle
如何将消息进行存储的。(SystemClock.uptimeMillis()
为开机到现在的时间,单位为毫秒)
3、消息是怎么发送给Handle的?
上个问题解释了消息是如何存储的及其存储规则,那么消息该如何发送给Handle
?通过官方文档或者源码说明或者网上其余的参考资料可知,是通过Loop::loop()
函数进行循环取出消息链表中的消息的。该函数最终是调用Loop.loopOnce(final Looper me,final long ident, final int thresholdOverride)
进行遍历循环的----通常来说,我们是在重写的Handle::handleMessage(msg: Message)
函数中接受到消息的,在源码中,该函数为空函数,最终由Handle::dispatchMessage(@NonNull Message msg)
函数调用。而在Loop.loopOnce(final Looper me,final long ident, final int thresholdOverride)
函数中通过Message msg = me.mQueue.next();
获取目标的消息,最终调用了msg.target.dispatchMessage(msg);
。其中Message.target
参数为Handle
。这样就将消息传递给了业务代码。
4、Loop和MessageQueue和Handle的关系是什么?
上述的几个问题解释了消息的传递流程,但是有一些地方没有解释清楚,比如Loop
是如何将Handle
和线程以及MessageQueue
绑定在一起的。通过查看源码可知Loop
类中有一个变量static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
。该变量是由静态的ThreadLocal
存储了Loop
对象。所以各个地方都可以使用同一个变量,而不会出现数据不一致的情况。由于ThreadLocal
特性的问题,可以解决每个线程对数据的访问隔离,举个例子就是A线程对ThreadLocal
进行赋值,B线程是取不出来的。这样就可以在线程中通过判断有没有值来判断是不是绑定了,这样就将Loop
和线程绑定在一起了。在通过Loop::prepare()
函数进行创建Loop
时候最终调用了私有的构造函数,如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到这时候创建了MessageQueue
对象,因此这时候是将Loop
和MessageQueue
进行了绑定。由于创建Handle
的时候需要将Loop
传入进去(无参构造函数被废弃了,因为有时候会出现Loop
为空的情况,所以官方建议传入Loop
),这样就完成了Handle
和Loop
的绑定。
这样可以在Handle
里面取到Loop
及其MessageQueue
5、延迟消息是什么时候发送的?
上述问题已经解释了每个Message
里面都有一个when参数,当该参数的时间不到时候,循环空转,直至时间满足条件才会进行发送消息。另外注意到是插入消息只是按照先后顺序添加了,并没有按照时间顺序进行重新排列
6、如果创建一个新的Message也会有Handle吗?
上文说到了最终回调用Message.targe.dispatchMessage(msg)
将消息传递给Handle,那么如果new Message()
也会有Handle
吗?这个是的,因为最终会传递给Handle
。只要在里面发送消息之前赋值即可。参考代码如下:
public class Handler {
...
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; //赋值Handle
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
...
}
7、没有消息的时候会不会停掉Loop
这个问题问法有很多种,这里解释下过程。首先轮询是需要通过Loop.loop()
进行开启,这个Loop.loop()
是一个死循环for(;;)
,所以没有数据就就会空跑起来,一直到有数据为止。除非Handle
结束了或者退出才会终止循环。Message msg = me.mQueue.next();
该代码中的next()
函数也是有一个死循环for(;;)
。这样就不会出现取不到数据返回null的情况。
8、死循环不会导致ANR吗?
ANR的情况只会出现在生命周期函数里面出现大量耗时操作才会出现ANR,但是Loop.loop()
没有在生命周期里面。还有一个需要注意的地方就是UI线程也是一个线程,如果任务结束了,那么线程就退出了,如果UI线程退出了,那么程序就会结束了,换而言之,也是Loop.loop()
是程序一直在运行。而且生命周期触发其实也是通过Handle进行触发的。在程序启动时候最终会通过ActivityThread
启动一个UI线程,其入口为main
函数,Loop
就是就是在这里进行初始化以及开始轮询的。
9、Handle是如何进行线程间切换
上面的问题已经说了会通过Handle::dispatchMessage(@NonNull Message msg)
。这个函数是定义在Handle
定义的线程里面的,通过Message进行调用,而Message是通过Loop进行轮询,Loop又是通过ThreadLocal进行保存的,由于线程隔离,所以最终会回调到其创建时候的线程里面。同时还需要理解另外两部分,一部分是存message
,一方面是取message
。存是子线程存的,取是通过绑定线程的Loop
取的。就好像是放了一份共同的数据,一个线程存,一个线程取一样。这份共同的数据就是MessageQueue
。因为Loop不知道什么时候有数据,所以在需要不停的轮询,因为轮询操作是在UI线程,所以取到数据后面执行的代码也是在UI线程。所以其实没有存在线程切换的问题,只是外在感知为切换而已。
这里面可以尝试以下简化版的线程切换代码:
class ThreadLocalTest {
@Test
fun test(){
val runnableA = ThreadA()
Thread.sleep(50)
val runnableB = ThreadB()
Thread(runnableA).start()
Thread.sleep(50)
Thread(runnableB).start()
Thread.sleep(5000)
}
private var handle: MyHandle ?= null
inner class ThreadA : Runnable{
override fun run() {
handle = object : MyHandle(){
override fun dispatchMessage(msg: String){ //用于分发消息
println("YM------>收到的消息:$msg, threadId: ${Thread.currentThread().id}")
}
}
println("YM--->ThreadA-->创建时候的线程ID:${Thread.currentThread().id}")
handle?.loop()
}
}
inner class ThreadB() : Runnable{
override fun run() {
println("YM--->ThreadB-->创建时候的线程ID:${Thread.currentThread().id}")
handle?.sendMessage("=======")
}
}
}
open class MyHandle{
private val messageList = Vector<String>() //存储消息集合,这里使用线程安全的集合,为了防止多线程出现问题,在Handle是使用synchronized处理
open fun dispatchMessage(msg: String){ //用于分发消息
}
fun sendMessage(msg: String){
messageList.add(msg)
}
//开始循环
fun loop(){
while (true){
Thread.sleep(15)//这里是为了测试,所以不让循环次数太多
for (index in 0 until messageList.size){
val value = messageList[index]
dispatchMessage(value)
messageList.remove(value)
}
}
}
}
执行结果
YM--->ThreadA-->创建时候的线程ID:24
YM--->ThreadB-->创建时候的线程ID:25
YM------>收到的消息:=======, threadId: 24
10、Handle::sendMessage(Message m)和Handle::post(Runnable r)
这两个函数都可以在主线程刷新任务,但是有什么区别?首先我们知道Handle::sendMessage(Message m)
可以在重写的方法handleMessage(Message msg)
中收到消息,但是需要注意的Handle::post(Runnable r)
这个需要传入Runnable
。然后最终处理的逻辑也是在Runnable
中触发,并不会发送到handleMessage(Message msg1)
函数中,这是结论。那么看到结论你会怎么想呢?为什么会出现这个情况,会不会认为是直接启动了一个线程去做的?
首先来说即使是Handle::post(Runnable r)
函数也是会给MessageQueue
添加一个Message
的。所以也是会执行之前的轮询Message
的那一套流程。可以参考如下代码:
public class Handler{
...
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
...
}
可以看到Runnable
是赋值给Message
的一个参数了。那么什么时候执行这个Runnable
呢?之前知道消息轮询完后最终会调用Handle::dispatchMessage(@NonNull Message msg)
进行消息分发,这里看下代码
public class Handler{
...
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();
}
...
}
可以看到当callback
,也就是之前传入的Runabble
不为空时候,执行Runnable
而不是分发给Handler::handleMessage(Message msg)
函数。这里需要注意的是这里没有执行异步任务,Runnable
并没有在Thread
中执行。这里可以尝试以下简单的测试代码:
class MainActivity: AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e("YM--->","---->UI线程ID:${Thread.currentThread().id}")
val runnable = MyRunnable()
runnable.run()
Thread{
Thread.sleep(100)//延迟下保证顺序
val runnable1 = MyRunnable()
runnable1.run()
}.start()
}
inner class MyRunnable: Runnable{
override fun run() {
Log.e("YM","---->异步任务MyRunnable的线程Id:${Thread.currentThread().id}")
}
}
}
执行结果如下:
E/YM--->: ---->UI线程ID:2
E/YM: ---->异步任务MyRunnable的线程Id:2
E/YM: ---->异步任务MyRunnable的线程Id:451
可以看到实例化的Runnable
不会单独启动一个线程,只会运行在所处的线程。假如该环境是UI线程,那么Runnable
运行的环境是主线程,这里的话就需要注意一个地方,如果Handle::post(Runnable r)
在异步任务中执行的话那么,这个环境是主线程还是子线程?如下代码:
Thread{
Log.e("YM--->","---->异步任务Thread:线程ID:${Thread.currentThread().id}")
handle?.post {
Log.e("YM--->","---->异步任务post:线程ID:${Thread.currentThread().id}")
}
}.start()
执行结果如下:
E/YM--->: ---->UI线程ID:2
E/YM--->: ---->异步任务Thread:线程ID:436
E/YM--->: ---->异步任务post:线程ID:2
这又是为什么呢?答案就是。上文已经解释了消息传递过程,所以知道Loop::loop()
是在UI线程执行的,所以在UI线程中取出Meesage
,然后执行Runnable
当然还是在UI线程中了!
11、View::post()
有时候会通过View::post()
函数进行线程切换,其实内部也是使用Handle
进行切换的。这里可以看下官方是怎么使用Handle
。
首先View
中的Handle
函数是定义在内部静态类AttachInfo
中,该值是构造函数的必传参数。该参数在View
中的全局变量为mAttachInfo
,其赋值函数为View::dispatchAttachedToWindow(AttachInfo info, int visibility)
。该函数是通过ViewRootImpl
进行进行调用的。通过代码可知只有第一次加载进窗口的时候才会初始化。通过源码顶部的注释可知,该类是整个视图顶部的层次结构,所有View加载都会通过该类进行处理。所以当View
第一次加载进窗口的时候就已经有Handle
了。关于View先到此结束,继续Handle
的话题。View
内部的Handle
是在ViewRootImpl
中进行定义的。然后其为一个继承于Handle
的子类ViewRootHandle
。除此之外暂时没有发现有需要特别注意的地方。有一个需要注意的就是post()
添加的任务会放在绘制任务之后(该源码需要进行确认)