系统版本: Ubuntu 22.04 lts
AOSP分支: android-14.0.0_r28
Looper的工作方式
提到Handler,那么我们就绕不开Looper,在Android Java层,我们会经常看到如下的代码:
Looper.prepare()
Looper.loop()
这段代码,主要是为了阻塞当前线程,等待消息再来唤醒和处理的,他的作用基本类似于:
while(true){
//Do something
}
那么接下来,我们就来看下它到底是怎么实现的。
我们先看Looper.prepare的源码:
可以看到,这个静态方法只是创建了一个新的Looper,并且将它设置给sThreadLocal。
从上面的代码我们可以得知,sThreadLocal的类型是ThreadLocal<Looper>:
那么也就意味着,这段代码的意思就是创建了一个Looper并且赋值给当前线程的sThreadLocal,我们再看Looper的构造方法:
可以看到它主要的作用就是创建了一个新的消息队列,并且把当前线程保存下来。
我们再看Looper.loop的源码:
可以看到,首先它调用了myLooper方法:
获取到了当前线程的Looper实例,然后设置了一些值,最后用了一个死循环中调用了loopOnce方法,我们再进入此方法:
可以看到,最开始调用的是Looper初始化的时候就初始化好的MessageQueue的next方法,我们进入此方法:
可以看到里面又是一个死循环,并且其中调用了nativePollOnce,这是一个Native方法,我们进入Native层:
可以看到这里根据Java层传进来的指针恢复出来了一个NativeMessageQueue,并且调用了它的pollOnce方法。
我们首先来看MessageQueue的mPtr,可以看到它是在MessageQueue的构造方法里初始化的:
我们在Native层也可以看到,它指向的就是一个NativeMessageQueue:
进入刚才提到的NativeMessageQueue的pollOnce方法:
可以看到,它最终调用的是Native层Looper的pollOnce,我们再进入此方法:
再进入pollInner:
可以看到,最终是调用了epoll_wait,此时线程进入等待状态。
从这里我们就可以知道,我们Java层的Looper的loop最终会进入Native层的Looper,然后执行它的pollInner,然后在其中使用epoll机制来等待消息的唤醒。
Handler的工作方式
首先,我们进入Handler的构造方法:
可以看到,当我们初始化Handler的时候,Handler会保存当前线程的Looper和Looper中的MessageQueue,这个Looper,是我们在调用Looper的prepare方法的时候创建的。
我们再看sendMessage方法:
可以看到,这里最终调用的,是我们在初始化的时候就获取到的MessageQueue的enqueueMessage,我们进入此方法:
可以看到,这里会把消息加入队列,并且调用nativeWake方法,我们进入Native层,找到此方法:
可以看到这里会调用Native层对应Looper的wake:
最终这里也是利用epoll机制,进行了唤醒。
Looper处理消息
我们再回到Java层的Looper,找到它的loopOnce方法,当我们获取到Message的时候,可以看到在后面的处理中有这样的代码:
这里调用了Message的target的dispatchMessage方法,这个target的赋值,我们可以在Handler的enqueueMessage中找到:
也就是说,这个target指的就是这个Handler自身,我们进入它的dispatchMessage方法:
可以看到在这里,做了一个判断,如果Message有callback,那么就会调用对应Message的callback,如果初始化的时候传进来了Callback,那么执行此Callback的handleMessage,否则调用自身的handleMessage。
那么到这里,我们就知道了为什么Handler可以进行跨线程通信,因为每个Handler在初始化的时候都保存了当前线程的Looper对象,一般这个Looper在Android中都是MainLooper,也就是主线程的Looper,然后让它去处理自身的回调,这就保证了回调一定是执行在主线程的,因为Handler的初始化,一般就发生在主线程。