从这篇文章开始,准备分析一下Handler的源码。Handler对于Android的开发者来说是比较熟悉的,我们在开发的过程中通过Handler可以十分方便地在不同的线程间进行通讯,Handler内部会维护有一个消息队列,正常情况下我们发送给Handler的消息都是会按照一个时间的顺序来执行,所以对于需要指定时间的任务来说,我们发送任务的时候把时间参数传过去就可以了,具体后续的事情,Handler都会帮你处理好,不需要开发者自己来管理和维护。另外,通常Handler是我们最常用的一种线程间进行通信的手段,尤其是一些后台子线程任务处理完数据后,需要UI线程来更新界面的时候,我们通常会使用Handler来处理这种场景,后面的分析中,我们也会看到到底Handler是怎么在不同线程间进行通信的原理。
Handler的源码分析,相对于AMS等这些android核心模块来说,没有那么多涉及底层的概念,再我看来他更像是一个官方提供的线程间通信的库,我们平时工作中大多数情况也不会涉及那么多底层相关的概念,大多还是基于一种现实的场景,给出一种解决方案,所以从这点来说理解像Handler这种偏应用场景的库,对于我们平时工作的借鉴意义可能更大一些,我们可以看看google是怎么处理一些比较常用的问题的。好了,下面我们就是Handler这个类本身开始说起。
# Handler用法和构造函数
首先我们还是说下Handler的用法,相信大家都是比较熟悉的,这里就提一下。一般我们在主线程中,直接new一个Handler就可以了:
```java
Handler handler = new Handler();
// 发送消息的两种方式
handler.sendMessage();
handler.post();
```
从上面代码可以到,首先会new一个Handler,之后会调用sendMessage或者post方法来发生消息,sendMessage方法主要传一个Message对象,而post方法主要是传Runnable对象,这样就完成发送了,之后Handler会处理你发过去的消息。这里Handler的用法看上去很简单,但是Handler实际的内容要比看的复杂,这里我们就从Handler的构造方法一点点说开去。
我们先看下Handler的构造方法:

这里可以看到构造方法有好几个,其实最终都是一样的,只不过有些参数是自动获得,有些是调用者传入,所以会有好多个构造方法。这里的构造方法中最主要的是Looper这个方法,可以把构造方法分为两类来看,一类是传入Looper的,另一类是不传Looper。我们先看最后一个3个参数的构造方法,这里传入了Looper。
```java
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
```
可以看到这个构造方法很简单,就是赋值了Handler的四个变量。这里先大概解释下四个变量的意思。首先Looper,这个是Handler的核心类了,Looper类正如他的名字一样,他会不断地遍历他的消息队列,如果有消息就取出来处理,每个的话就休眠。
第二个MessageQueue类,这个类就是消息队列,所有需要处理的消息都会放入这个对象中。
第三个Callback类是每个消息处理最后会回调的方法,当前Handler的消息回调总共可以设置三个,这里传入的是其中一个,还可以覆写Handler的handleMessage方法,这个我们后面再说。
第四个参数表示这个Handler的消息队列中保存的是同步消息还是异步消息,每个Handler只能发送一种类型的消息,但是对应的消息队列中可以有同步和异步消息,所以这里async参数表示的是这个Handler实例是发送哪种类型的消息的,默认是同步消息。
上面我们介绍了有三个参数的Handler构造方法,那么对于有直接传入Looper参数的这一类方法最终都会调用到这个方法,其他几个我们就不多说了。我们再来看下不带Looper参数的Handler构造方法:
```java
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
// 如果是匿名类,嵌套类,局部类,声明不是static的,会有log打印这种类会导致内存泄露
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
```
不带Looper参数的构造方法,最终都会调用到上面这个两个参数的构造方法这里,可以看到相比于前面传入Looper的方法,其实就是少了Looper参数,但是这个方法会通过Looper.myLooper()方法来自动获取Looper,如果Looper是空,就会报错,可见Handler必须要持有Looper,否则这个Handler就是无效的。至于这个Looper到底是何方神圣,我们后面就会分析Looper,这里暂时不展开讲解了。
以上这个构造方法,除了初始化必要的对象外,在方法开头我们还看到会判断这个构造方法的对象是否是一个匿名类,嵌套类或者局部类,如果是这几种类型的话,并且没有声明static,就会有log输出。我们知道上面这几种类型的类,如果不声明static,那么就会持有一个外部类的引用,这样当前外部类要被回收的话,有可能会因为被这个Handler引用着,而这个Handler又可能被其他对象引用着不能回收,导致外部类也不能回收,这样就会造成内存泄露。这个问题相信使用过Handler的同学都会有印象吧,这个也踢一下,后面我们再说。
到这里的话,Handler的构造方法大致介绍完了,这里除了初始化外,最重要的就是里面的Looper对象了,我们后面会再分析Looper。接着我们在看下Handler的发送方法,前面我们看到了Handler的发送方法有2种,一种是sendMessage,一种是post,我们先看sendMessage。

我们看到sendMessage也有好几个方法,同Handler的构造方法一样,最终都会调用到sendMessageAtTime这一个方法,我们看下这个方法:
```java
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
```
可以看到这里传入了具体要发送的某个Message,首先看了消息队列MessageQueue是否为空,如果空了话就会报错,因为消息是要保存到MessageQueue中的,如果空也就没地方保存了。最后会调用enqueueMessage方法把消息插入到队列中,这个方法我们也暂时不展开,到后面讲具体发送消息的时候再展开。
这里几个sendMessage方法最终都会调用到sendMessageAtTime这里,所以也不用多说什么。这里涉及到了MessageQueue和Message两个类,这两个类我们在分析完这里的发送消息方法后在做分析。
sendMessage方法比较简单,暂时就说到这里。再来看一下post方法,这个方法也有几个不同参数的方法:

post方法的基本参数是一个runnable,也就是最终消息被处理后的回调方法,我们看其中的一个post方法:
```java
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
```
可以看到post方法最终会调用到sendMessage系列的方法,只不过调用getPostMessage方法把参数Runnable封装为一个Message,我们看下getPostMessage方法:
```java
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
```
这个方法首先获取一个Message,然后把runnable参数赋给Message的callback。可以看到如果通过post方法发送消息会直接把消息最终回调的方法传过去,而通过sendMessage方法,则取决于传入的Message时候有callback方法。事实上,后面分析最终的消息分发时候可以知道,最终消息的回调方法有3个,如果Message的callback中已经被赋值了,那么优先会回调Message的callback方法,其余两个方法都是设置给Handler的,这个我们等后面分析到后再仔细看。
通过上面分析了Handler的构造函数和发送消息方法,我们知道了和消息相关的两个类MessageQueue和Message,MessageQueue这个类我们在后面分析Looper的时候再一起分析,因为这两个类结合的非常紧密,我们先看看Message是怎么创建的。
# Message类
前面分析Handler发送消息的时候说到最终都会调用到sendMessageAtTime方法,这个方法的参数中Message是必须的,接下来我们来看一下Message这个类。
```java
public final class Message implements Parcelable {
// 消息的标识
public int what;
// int参数1
public int arg1;
// int参数2
public int arg2;
// 对象参数
public Object obj;
// 回复跨进程的message
public Messenger replyTo;
// 发送者的uid
public int sendingUid = -1;
// 在池中或者hander正在处理这个消息,会是这个值
static final int FLAG_IN_USE = 1 << 0;
// 异步消息
static final int FLAG_ASYNCHRONOUS = 1 << 1;
// 调用copyFrom方法时设置这个值
static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
// 表示是同步,异步还是复制消息
int flags;
// 消息时间
long when;
// 更复杂的数据
Bundle data;
// 对于的Handler
Handler target;
// 执行方法
Runnable callback;
// 下一个消息
Message next;
// 同步锁
private static final Object sPoolSync = new Object();
// 空闲消息池的队头
private static Message sPool;
// 消息池大小
private static int sPoolSize = 0;
// 消息池最大数量
private static final int MAX_POOL_SIZE = 50;
// 回收消息时,遇到不能回收的消息,是否要抛异常
private static boolean gCheckRecycle = true;
}
```
以上这些是Message对象的字段,各个字段的作用都在上面的注释中写明了,还是比较简单易懂的。下面我们这里主要看下获取一个Message的方法,其他方法结合后面的分析,遇到的时候再讲。
获取Message主要通过obtain方法,obtain方法也有好多个不同参数的方法:

这些方法的参数都是给Message初始化的,所以不同参数的方法原理都是一样的,但是每个方法都会调用无参数的obtain方法来获取一个Message对象,我们看下这个无参数obtain方法:
```java
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
// 不在池中,也不在handler处理中,flags置为0
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
```
这个方法首先会看看消息池中有没有可用的Message,如果有的话就取出,没有的话就new一个。这个方法中我们看到空闲消息池sPool非空的时候,会从池中取Message,同时池中的数量减一,既然有从池中取的动作,那必定有放入池中的动作。我们可以设想下第一次获取Message的时候肯定池中是空,所以会new一个Message,之后肯定是用完了,会把这个Message放入消息池中,我们可以在源码中找到回收消息的方法:
```java
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
```
这个方法首先判断这个Message是否还在使用,如果在使用就退出,否则继续调用recycleUnchecked方法:
```java
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool; // 下一个mesaage指向当前链表头
sPool = this; // 回收的message设置为链表头部
sPoolSize++; // 数量+1
}
}
}
```
这个方法可以看到,回收一个Message的时候,首先是把这个Message初始化为默认值,之后如果池中的数量小于MAX_POOL_SIZE的时候,会把这个Message插入空闲池头部,同时池中的数量加一,这里MAX_POOL_SIZE的值是50。
我们结合获取Message和回收Message来理解空闲消息池这样做的目的是,想让已经创建过的Message尽量保留着给下一次使用。这样做一方面是创建一个对象是需要时间的,如果频繁的创建对象,创建对象本身消耗的时间就不太值得,既然已经创建过了,即使不用了,也先保留着呗,Handler是android中使用十分频繁的一个机制,说不定马上就有请求获取一个新的Message了,所以放在空闲消息池中可以减少创建对象的消耗。另一方面,java中GC的时机是虚拟机来决定,如果内存使用过多,导致创建一个对象的空间不足,那么需要先把那些无效的对象回收再创建对象,这样其实消耗的时间又多了垃圾回收的时间。所以说这里采用了设计模式中的享元模式,即可以减少对象创建的消耗,又可以减少GC的消耗,也是采用享元模式的一个目的。
当然可能在obtain方法中获取Message的时候,有同学会说,如果空闲消息池sPoolSize一直是空,那不是会一直new一个Message对象吗。理论上这样想是对的,但是我们空闲池最多可以保留50个Message,起码说明同时存在50个Message对于系统应该不会造成什么影响,而实际过程中,大部分情况应该不会出现sPoolSize一直是空,而需要new出50个以上Message的情况,android已经这么多年的,有些问题实际的应用中也应该经过检验了,所以这个大家也不用太担心。
好了,Message这个类主要就说了下获取和回收的方法,其余的方法我们放在后面分析中遇到了再说,Message相等还是比较简单,没有太多复杂的理解。我们接下去看下Looper这个类。
# Looper的初始化
在文章开头,我们讲解Handler构造方法的时候,说到Handler没有传Looper参数的构造方法是,会调用Looper.myLooper()方法来获取Looper,我们先看下这个方法:
```java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
```
这里可以看到会调用ThreadLocal来获取Looper,ThreadLocal虽然是Java中的一个保存线程本地变量的类,但是理解ThreadLocal的机制原理对于理解Handler还是比较重要的,所以关于ThreadLocal原理的文章可以参考这篇[ThreadLocal源码解析](https://liqi.site/archives/%E8%BF%99%E7%AF%87%E6%96%87%E7%AB%A0%E6%88%91%E4%BB%AC%E6%9D%A5%E8%AE%B2%E8%A7%A3%E4%B8%80%E4%B8%8Bthreadlocal%E5%85%B3%E4%BA%8Ethreadlocal%E4%B8%8D%E7%9F%A5%E9%81%93%E5%A4%A7%E5%AE%B6%E7%94%A8%E7%9A%84%E5%A4%9A%E4%B8%8D%E5%A4%9A%E4%BB%8E%E5%90%8D%E5%AD%97%E4%B8%8A%E7%9C%8B%E5%8F%AB%E5%81%9A%E6%9C%AC%E5%9C%B0%E7%BA%BF%E7%A8%8B%E5%85%B6%E5%AE%9E%E4%BB%96%E5%B9%B6%E4%B8%8D%E6%98%AF%E4%B8%80%E4%B8%AA%E7%BA%BF%E7%A8%8B%E4%BD%86%E6%98%AF%E7%A1%AE%E5%AE%9E%E5%92%8C%E7%BA%BF%E7%A8%8B%E6%9C%89%E5%85%B3%E4%BB%96%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E5%B1%9E%E4%BA%8E%E4%B8%80%E4%B8%AA%E7%BA%BF),ThreadLocal虽然是相对独立的知识,但是对于理解Handler是非常有用的,由于我们这篇文章主要是讲解Handler的,所以对于ThreadLocal不会做太深入的讲解,但是对于涉及到的内容会做一些相关的讲解,具体的如果不熟悉ThreadLocal的同学可以参考上面这篇文章。好了,我们回到上面的方法。
这个方法调用了sThreadLocal的get方法返回一个Looper。sThreadLocal是一个ThreadLocal,ThreadLocal是一个泛型类,这里的泛型就是这个ThreadLocal代表的对象,这里如果你把ThreadLocal看成是一个本地的变量,那么他和一般的变量的区别在于ThreadLocal代表的变量会保存在他所在的线程中,其他线程是访问不了他的,所以他其实在每个变量都都有一个副本。而这里sThreadLocal被声明为static,所以如果不了解ThreadLocal肯定认为他是一个全局的变量,是属于类的,而前面又说他在每个线程都有一个副本,是不是有些矛盾呢?其实ThreadLocal看上去好像是一个变量,但是其实把他看成是一个方法或者是一个封装了具体变量的类更合适,这样理解的话,对于虽然这里被声明为static,但是一个static的方法或者静态类内部获取不同线程的变量这个就比较好理解了吧,所以这里返回的是一个保存在这个线程里的Looper实例,至于具体ThreadLocal原理,还是请参考前面给出的文章,这里就不多赘述了。
这里new一个ThreadLocal的时候,创建的是一个ThreadLocal类,此时Looper的实例还没有创建,所以上面myLooper方法,每个线程第一次调用的时候这个返回的肯定是null,所以开发中相信很多同学都有遇到过,new一个Handler的时候会报错,提示"Can't create handler inside thread that has not called Looper.prepare()"这个错误,要你先调用Looper.prepare()这个方法,这个方法就是创建Looper的。但是这个错误只会出现在子线程中,主线程默认是已经调用了这个方法了。如果熟悉Android进程创建流程的同学或者看过我们之前进程启动过程的同学肯定记得,当每个进程创建的时候,会调用ActivityThread的main方法,在main方法里面我们看到下面代码:
```java
public static void main(String[] args) {
..........
Looper.prepareMainLooper();
.........
Looper.loop();
............
}
```
这里看到会先调用prepareMainLooper方法,最后调用loop方法,其中prepareMainLooper就是创建Looper的实例的,最后loop方法是会遍历消息队列,如果有消息会取出来执行。这个Looper的调用套路其实对于我们自己在子线程开发中也是一样的,只不过我们自己最开始调用的是Looper.prepare(),而prepareMainLooper方法里面也是调用prepare来创建Looper的,我们来看一下:
```java
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
```
这里prepareMainLooper方法第一句代码就调用了prepare方法,prepare方法有个bool类型的参数,表示这个Looper里面的消息队列是否可以关闭,这里主线程是不可关闭的,因为主要主线程存在,一直会等待着接受新的消息,如果没消息就会处理睡眠状态,否则会被唤醒,这个我们在后面的分析中会有看到。
这里调用prepare方法创建Looper,如果这个线程已经有Looper了,就会报错,否则会调用myLooper方法把Looper赋值给sMainLooper变量。可见每个线程只能持有一个Looper,其实Looper确实只需要一个,他是负责处理消息队列中消息的大管家,一个就够了,后面我们会看到他具体是怎么工作的。这里我们继续看prepare方法,看是怎么创建Looper的:
```java
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {// 如果已经有Looper了,退出
throw new RuntimeException("Only one Looper may be created per thread");
}
// 创建Looper,并设置到这个线程的ThreadLocal里面
sThreadLocal.set(new Looper(quitAllowed));
}
```
这里首先会从ThreadLocal里面取出值,看是不是空,如果非空的话说明本进程已经有Looper了,报错,否则我们看到会new一个Looper设置给ThreadLocal,这里ThreadLocal的set方法就不深入跟进了,前面给出的ThreadLocal分析文章中已经有了详细的分析,可以到那篇文章去看下。这里我们看到new了一个Looper实例给ThreadLocal,我们看下Looper的构造方法:
```java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); // 初始化MessageQueue
mThread = Thread.currentThread();
}
```
Looper的构造方法里面,我们看到会new一个MessageQueue,然后会把当前线程保存的Looper的mThread变量中。Looper的构造方法其实就是保存了一个MessageQueue和当前线程。Looper到这里就初始化好了,目前对于一个线程来说是持有了一个Looper,而一个Looper有持有了一个MessageQueue,暂时Looper在jave层这里我们先告一段落,我们先看看MessageQueue的初始化,之后还会再回到Looper这里来。
```java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
```
上面是MessageQueue的构造函数,这里的入参代码这个MessageQueue是否可以被关闭,前面我们说过了主线程来说是不可以被关闭了,其他线程默认是可以的。接着会调用一个native方法来初始化mPtr变量,这个变量保存的是native层的MessageQueue的地址,由这里我们可以看到MessageQueue会有两层,分别在java层和c++层都有一份,之所以这样是因为Handler的机制中是Android中最重要的线程间的通信机制,在同一进程中,几乎大部分线程间的通信都会依靠Handler,而虽然我们平时开发更多的关注是java层,但是Handler的机制不止用于java层,native之间的通信也是通过他的,所以必须具备c++层的部分。另外,从后面我们的分析中就可以知道,Handler中使用了epoll的底层机制,这个也是通过调用native层来执行的。所以说,Handler涉及的面其实是非常广的,应用层和系统层都是使用到,所以他被设计为两层。
# JNI对应方法的寻找
下面我们就看下native方法,在看native方法前我们先说一下这些JNI方法是怎么找到他们的位置的,如果看过之前我们分析的启动时候zygote进程的文章会知道,zygote进程在启动的时候会调用到AndroidRuntime::start方法,这个方法里面会调用AndroidRuntime::startReg方法,这个方法是注册这些JNI方法的,我们看下这个方法:
```c++
// frameworks/base/core/jni/AndroidRuntime.cpp
int AndroidRuntime::startReg(JNIEnv* env)
{
................
// 注册jni各种方法
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
...............
}
```
这个方法里面调用了register_jni_procs来注册初始化必须的JNI方法,这里传入的参数gRegJNI是一个注册JNI方法的数组,我们看下register_jni_procs方法和这个数组:
```c++
// frameworks/base/core/jni/AndroidRuntime.cpp
#define REG_JNI(name) { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
static const RegJNIRec gRegJNI[] = {
............
REG_JNI(register_android_os_MessageQueue),
.............
}
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
for (size_t i = 0; i < count; i++) {
if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
return -1;
}
}
return 0;
}
```
首先这里的gRegJNI数组元素是一个结构体RegJNIRec,这个结构体是一个指向一个方法的指针。然后我们看到数组中每个元素是用宏来初始化的,这个宏定义就是一个方法的名字,这里由于初始化宏的数量比较多,我们就列出了和我们要分析的MessageQueue相关的方法register_android_os_MessageQueue,这个方法可以在/ frameworks/base/core/jni/android_os_MessageQueue.cpp这个文件中找到,这里顺便提一下由于他们都是在namespace android这个命令空间中的,所以不同文件中的数据可以访问到。register_android_os_MessageQueue这个方法我们稍等下看,先把上面这段代码看完。上面这段代码最后调用register_jni_procs方法,这个方法会遍历数组中的每个元素,我们知道这个数组的每个元素是指向一个方法的,所以这里遍历就是执行数组中的每个方法。我们现在到register_android_os_MessageQueue这个方法中去看下:
```c++
// frameworks/base/core/jni/android_os_MessageQueue.cpp
static struct {
jfieldID mPtr; // native object attached to the DVM MessageQueue
jmethodID dispatchEvents;
} gMessageQueueClassInfo;
static const JNINativeMethod gMessageQueueMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
{ "nativeSetFileDescriptorEvents", "(JII)V",
(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};
// AndroidRuntime.cpp中会注册这个方法
int register_android_os_MessageQueue(JNIEnv* env) {
// 注册native方法
int res = RegisterMethodsOrDie(env, "android/os/MessageQueue", gMessageQueueMethods,
NELEM(gMessageQueueMethods));
// 或者java的MessageQueue类
jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
// 把java的mptr赋值给这里的mPtr,mPtr即C++的MessageQueue地址
gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
// 把java的dispatchEvents方法赋值给这里的dispatchEvents
gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
"dispatchEvents", "(II)I");
return res;
}
```
register_android_os_MessageQueue这个方法首先会调用RegisterMethodsOrDie注册gMessageQueueMethods这个数组中的JNI方法,其中gMessageQueueMethods数组的第一项就是nativeInit这个前面我们看到初始化MessageQueue的方法,等下我们会跟进去看下。剩余的几个方法我们后面都会分析到,接着gMessageQueueClassInfo是个结构体,里面有2个字段,分别对应java层的MessageQueue类的字段名和方法名,这里会把java层的MessageQueue类的mPtr字段和dispatchEvents方法保存在结构体gMessageQueueClassInfo,mPtr从之前我们的分析可以知道就是native层MessageQueue的地址值,dispatchEvents的作用我们稍后在说,这里涉及到更深一层的内容。
# 初始化native层MessageQueue
JNI方法怎么找到的过程说好了,我们回过头继续说native层初始化MessageQueue的方法nativeInit:
```c++
// Message queue的构造方法中调用jni的nativeInit方法,最后会调到这里
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
// 创建NativeMessageQueue
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue); // 返回messageQueue的地址
}
```
这个方法比较简单,new了一个NativeMessageQueue对象,如果非空的话会返回这个对象的地址,这里会返回到jave层的MessageQueue那里,并保存在mPtr变量中,这个前面分析的时候也说过了。接着我们继续看NativeMessageQueue的构造方法:
```c++
// NativeMessageQueue构造函数
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
// 看下当前线程有没有Looper,用的是pthread_getspecific
mLooper = Looper::getForThread();
// 开始第一个肯定返回null,所以下面会是null
if (mLooper == NULL) {
mLooper = new Looper(false);
// 设置到这个线程的私有空间中,可以理解成java的ThreadLocal
Looper::setForThread(mLooper);
}
}
```
这个方法初始化时候分别设置了3个私有变量为NULL,这里mPollEnv是JNIEnv,mPollObj是jobject,mExceptionObj是jthrowable,都是java层调用者对应的对象,通过这些java层的对象,c++和java就可以进行通信。
前面我们分析jave层的Looper的时候说过,一个线程只能持有一个Looper,这里native层也是一样的,所以下面在MessageQueue的构造方法中开始要创建Looper。从这里我们也可以看出,在java层中是Looper的构造方法中创建一个MessageQueue,而在native层中是MessageQueue中创建Looper,正好反过来。
# native层的线程隔离
这里创建前会首先调用Looper::getForThread(),他的作用和java层那边一样,看看当前线程中是否已经有Looper了,如果没有则new一个Looper,并把looper保存在当前线程中,否则就什么也不做。我们先来看下Looper::getForThread方法,这里会涉及到linux底层的一些方法。
```c++
#define PTHREAD_ONCE_INIT {_PTHREAD_ONCE_SIG_init, {0}}
struct pthread_once_t {
long __sig;
char __opaque[__PTHREAD_ONCE_SIZE__];
};
static pthread_once_t gTLSOnce = PTHREAD_ONCE_INIT;
sp<Looper> Looper::getForThread() {
// 这个方法执行一次,最终会调用到pthread_once.c中的__pthread_once方法,initTLSKey方法只会执行一次
// 该方法会寻找一个key,即下面的gTLSKey
// 这个可以可以会联系到一个数组下标,这个数组下标就是保存在这个线程中的一个变量
int result = pthread_once(& gTLSOnce, initTLSKey);
// result非0,报错
LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");
// 从pthread_setspecific.c中调用__pthread_getspecific方法获取gTLSKey这个
// 数组下标中保存的Looper
return (Looper*)pthread_getspecific(gTLSKey);
}
```
通过前面的分析,我们知道在Java层中是通过ThreadLocal来保证每个线程中只存在一个Looper的,在c++层中也有这个机制,我们来看下c++层是怎么保证每个线程只有一个Looper的。
这里我们看到首先会调用pthread_once方法来保证一个方法只被执行一次,这里执行的方法是initTLSKey,也就是pthread_once第二个参数,第一个参数gTLSOnce是一个结构体,里面有2个字段,一个是数字,一个是字符串,我们不用管他具体代表什么意思,这里由于只是一个标识,他有个初始值PTHREAD_ONCE_INIT,是一个宏定义,给出了一个初始值。执行pthread_once方法的时候会判断,如果是初始值的话,说明第二个参数的方法还没有被执行过,会执行第二个参数的方法,如果不是初始值值,说明已经执行过了,就什么也不做。我们看到,这里第一个参数被声明为static,所以是个全局的变量,所以它的值一旦被修改,起码在这个进程中就不能再执行initTLSKey方法,所以这里initTLSKey只会被执行一次,initTLSKey这个方法我们稍后再看,我们首先看下pthread_once这个方法。由于这里一系列的方法都是glibc库的中的,所以下面的代码是来自于glibc库中的,这里挑选了一个稍早期的版本2.9的版本,因为glibc中的方法都是对于操作系统底层代码的支持,所以最新版本和早期版本的代码逻辑基本是差不多了,区别在于最新版本对于代码的封装更大严谨,比如这里对比了下最新的2.38版本和2.9版本实现的逻辑是一样的,只不过很多代码经过抽象后理解起来相对麻烦点,所以这里选择了一个早期的版本,对代码的理解更加的直观。好,我们先看下pthread_once这个方法的实现:
```c
// 指向一个初始化方法init_routine,根据once_control来确保只会执行一次
int
__pthread_once (once_control, init_routine)
pthread_once_t *once_control;
void (*init_routine) (void);
{
/* XXX Depending on whether the LOCK_IN_ONCE_T is defined use a
global lock variable or one which is part of the pthread_once_t
object. */
// 如果等于PTHREAD_ONCE_INIT,可以执行,否则返回0
if (*once_control == PTHREAD_ONCE_INIT)
{
// 加锁
lll_lock (once_lock, LLL_PRIVATE);
/* XXX This implementation is not complete. It doesn't take
cancelation and fork into account. */
if (*once_control == PTHREAD_ONCE_INIT)
{
// 执行初始化方法
init_routine ();
// 修改这个判断是否已经执行的值
*once_control = !PTHREAD_ONCE_INIT;
}
// 释放锁
lll_unlock (once_lock, LLL_PRIVATE);
}
return 0;
}
```
这个方法开始会判断第一个入参是不是等于PTHREAD_ONCE_INIT,前面我们说了第一个入参初始化的值是PTHREAD_ONCE_INIT,所以只要第二个参数还没被执行过,入参一的值都是PTHREAD_ONCE_INIT。由于是多线程,所以一旦进入了这里if分支就可能会有线程安全问题了,所以这里先要加锁,这里调用的是lll_lock方法,这个方法在glibc库中最终会通过汇编代码来执行,这里就不继续跟进去了,和java层的synchronized的作用类似。之后会再次判断入参一是否等于PTHREAD_ONCE_INIT,这个是不是和我们常见的设计模式中单例的双重检查一样啊,当多线程加锁后需要再次检查同步条件是否一样,如果还是等于PTHREAD_ONCE_INIT的话,说明这个线程是第一个执行到这里的,所以调用参数二的init_routine方法,执行完毕后修改参数一的初始值为!PTHREAD_ONCE_INIT,这样入参二的方法已经被执行过一遍了,并且入参一的标识的值也修改了,其他线程在执行到这里都不会被执行了。
我们回到Looper::getForThread()方法,看下pthread_once执行的方法做了些什么事情:
```c++
static pthread_key_t gTLSKey = 0;
void Looper::initTLSKey() {
int result = pthread_key_create(& gTLSKey, threadDestructor);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not allocate TLS key.");
}
void Looper::threadDestructor(void *st) {
Looper* const self = static_cast<Looper*>(st);
if (self != NULL) {
self->decStrong((void*)threadDestructor);
}
}
```
在initTLSKey方法中我们看到主要是调用了pthread_key_create方法,这个方法的第一个参数gTLSKey类型是pthread_key_t,其实这个是自定义类型,他原始类型就是一个long类型,这个是用来表示创建对象的一个序号,下面我们分析pthread_key_create方法时候会看到。第二个参数threadDestructor是一个方法,这个方法表示当销毁创建的这个对象的时候,调用的析构函数,可以看到这个方法的定义是减少一个对自己的强引用。这里我们主要是看下pthread_key_create方法到底做了些什么事情:
```c
struct pthread_key_struct
{
uintptr_t seq;
/* Destructor for the data. */
void (*destr) (void *);
};
struct pthread_key_struct __pthread_keys[PTHREAD_KEYS_MAX]
__pthread_key_create (key, destr)
pthread_key_t *key;
void (*destr) (void *);
{
/* Find a slot in __pthread_kyes which is unused. */
// 最多512个,正确获取返回0
for (size_t cnt = 0; cnt < PTHREAD_KEYS_MAX; ++cnt)
{
// 获取__pthread_keys数组中的元素
uintptr_t seq = __pthread_keys[cnt].seq;
// 如果可用的话
if (KEY_UNUSED (seq) && KEY_USABLE (seq)
/* We found an unused slot. Try to allocate it. */
&& ! atomic_compare_and_exchange_bool_acq (&__pthread_keys[cnt].seq,
seq + 1, seq)) // 返回0说明__pthread_keys[cnt]已经修改正确了
{
// 保存销毁方法
/* Remember the destructor. */
__pthread_keys[cnt].destr = destr;
// 保存key值
/* Return the key to the caller. */
*key = cnt;
/* The call succeeded. */
return 0;
}
}
return EAGAIN;
}
```
这里有个pthread_key_struct结构体,里面有两个字段,seq是保存创建的新的对象的序号的,destr是保存销毁对象时候的方法的。然后还有个pthread_key_struct数组,数组大小是512。__pthread_key_create方法会遍历这个数组,取出数组元素pthread_key_struct结构体的seq字段,之后会判断这个字段是否可用的,这里使用KEY_UNUSED和KEY_USABLE来判断seq是否可用,我们看下这两个宏:
```c
#define KEY_UNUSED(p) (((p) & 1) == 0)
#define KEY_USABLE(p) (((uintptr_t) (p)) < ((uintptr_t) ((p) + 2)))
```
可以看到这里会根据传入的p,p也就是seq本地变量的序号是奇数还是偶数来判断是否有效,偶数是有效的,奇数是无效,所以可以看出native这里是根据这个seq的奇偶性来判断这个本地变量是否还有效的,之所以根据奇偶性是因为这里seq的数值是一个只会增加不会减少的值,这里创建的方法会增加seq的值,同样在删除的方法中也是会增加这个值的,所以说既然是一直增加的数值,只能根据奇偶性了。
既然是一直增加的值,那么就会有数值溢出的问题,所以这里除了根据奇偶性判断有效外,还要根据是否溢出判断有效性,由于每次有效之间是隔了两个数的,所以这里会比较是否比后面加2的数小来判断是否溢出了。
之后如果一切正常后就会调用atomic_compare_and_exchange_bool_acq宏把seq加一,这个宏是个原子操作,涉及到操作系统底层的接口,我们这里就不深入进去看了,只要知道这里会把seq加1。
如果以上都一切正常的话,就会把之前传入的析构函数赋值给这里找到的数组元素,同时会把数组这个数组下标保存到传入的参数一中,也就是我们之前在Looper类中看到的gTLSKey变量,这个变量就代表了一个线程本地变量在系统中数组的位置。
接着我们回到Looper.cpp的Looper::getForThread方法,创建了一个本地变量后其实就是获取了这个变量在前面说的保存本地变量数组中的下标,接着会调用pthread_getspecific方法来获取这个下标对应的具体对象了,然后再返回给调用者,这里最后会返回到Looper::prepare方法,这个方法会判断如果返回的是一个空对象,就会创建一个Looper的实例,并且把这个Looper实例通过调用Looper::setForThread方法保存到该线程中。这里的逻辑还是比较清楚的,这里除了pthread_getspecific获取具体的某个本地线程对象外,还会调用setForThread方法来设置本地线程,我们先看这个设置的方法:
```c++
void Looper::setForThread(const sp<Looper>& looper) {
sp<Looper> old = getForThread(); // also has side-effect of initializing TLS
if (looper != NULL) {
looper->incStrong((void*)threadDestructor);
}
// 最终调用pthread_setspecific.c方法,会在pthread_key_data数组中的
// gTLSKey下标的位置插入,
pthread_setspecific(gTLSKey, looper.get());
if (old != NULL) {
old->decStrong((void*)threadDestructor);
}
}
```
这个方法首先又调用了getForThread获取本地线程是否有Looper对象,如果有的话最后会调用他的析构函数,然后把传入的新的Looper对象保存到线程里面,调用的是pthread_setspecific方法。
上面的这些代码,最后涉及的就是pthread_getspecific和pthread_setspecific这两个读取本地线程和设置本地线程对象的方法,我们就看下这两个方法,先看下设置的方法:
```c
register struct pthread *__thread_self __asm__("r13");
# define THREAD_SELF \
((struct pthread *) ((char *) __thread_self - TLS_PRE_TCB_SIZE))
int
__pthread_setspecific (key, value)
pthread_key_t key;
const void *value;
{
struct pthread *self;
unsigned int idx1st;
unsigned int idx2nd;
struct pthread_key_data *level2;
unsigned int seq;
self = THREAD_SELF;
/* Special case access to the first 2nd-level block. This is the
usual case. */
// 如果key小于32个,第一级数组有32个元素
if (__builtin_expect (key < PTHREAD_KEY_2NDLEVEL_SIZE, 1))
{
/* Verify the key is sane. */
// 这个key是否已经在pthread_key_create中有效,无效的话return
if (KEY_UNUSED ((seq = __pthread_keys[key].seq)))
/* Not valid. */
return EINVAL;
// 获取specific_1stblock数组的下标key的元素
level2 = &self->specific_1stblock[key];
/* Remember that we stored at least one set of data. */
if (value != NULL)
THREAD_SETMEM (self, specific_used, true); // 表示这个线程有本地数据
}
............
level2->seq = seq; // 保存这个线程本地变量的序号
level2->data = (void *) value; // 保存具体的数据
```
这个方法我们分两段来看。这里首先会获取一个调用线程的线程描述符,这里定义了宏THREAD_SELF来获取,调用线程描述符的获取和操作系统底层原理有关,这里可以看到会从cpu的r13寄存器中取出来,在进程一些操作,这些我们也不深入研究了,这里self变量就代表着当前的线程。
在前面创建本地变量的方法中,保存的是本地变量的序号和析构方法,数组大小是512。具体的需要保存的对象并没有,具体对象是保存在每个线程自己的堆栈中的,所以这里开始要保存具体的对象了,这个方法传入的key参数是对应创建本地变量的那个数组下标,value就是需要保存的对象。
我们知道创建本地对象数组大小是512,所以key的值不会大于512,但是这里保存对象的处理逻辑会根据key的值小于或者大于32做不同的处理。我们先看这段小于32时候的处理。这里根据key取出前面在本地变量数组中的元素的seq序号值,如果这个序号是无效的,即是奇数(这里根据奇偶性判断有效值,前面有过分析了),则退出。否则取出线程自己的数组specific_1stblock中下标为key的元素level2,level2是一个pthread_key_data结构体,我我们看下这个结构体:
```c
struct pthread_key_data
{
uintptr_t seq; // 表示这个本地变量的序号,即key
/* Data pointer. */
void *data; // 表示变量如果是第二维数组保存一个真正要存储的本地对象,如果是第二维会指向一个pthread_key_data结构体数组,类似与第一维数组
} specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];
```
这个结构体是一个数组,大小是32,这个数组的32项对应的就是前面全部的本地变量数组中512项的前32项,其中每一项有2个字段,第一个字段等于保存在全部本地数组中的每项的seq值,第二项在这里前32项中就是真正的保存对象了,大于32项的指向一个32项的数组,数组的结构和这里的前32项的这个数组一样,所以就是一个二级数组了,这个我们下面会讲到。
这里可以看到之前保存的全部本地变量的数组相当于是一个全部的数组,那个数组中保存着每个本地变量的一个基本序号,这个序号就代表了一个同一个线程隔离的变量,如果用java来理解就类似于同一个ThreadLocal,既然是同一个线程隔变量,那么他们在全局的线程隔离变量数组中是同一个下标,即key是一样的,而从上面的代码中我们看到,保存到具体的某个线程中,这个key和线程中数组的下标也是要一样的,这样就可以通过同一个key去不同线程中取出对应同一个线程隔离变量了。
回到上面代码,取出level2后,后面就是简单的把seq值和保存对象value保存到这个pthread_key_data结构体中,最后会调用宏THREAD_SETMEM把本线程的specific_used字段置为true,表示本线程起码已经保存了一个线程隔离的变量。THREAD_SETMEM宏代码如下,很简单就是把传入的线程的specific_used字段置为true。
```c
#define THREAD_SETMEM(descr, member, value) \
descr->member = (value)
```
key的值小于32的情况就如上面所分析的,主要就是保存在线程的specific_1stblock数组中,下面我们在来看下大于32的情况:
```
int
__pthread_setspecific (key, value)
pthread_key_t key;
const void *value;
{
struct pthread *self;
unsigned int idx1st;
unsigned int idx2nd;
struct pthread_key_data *level2;
unsigned int seq;
self = THREAD_SELF;
.............
else
{
// 大于512了,key是pthread_key_create方法调用__pthread_key_create方法创建的
// 最多512个
if (key >= PTHREAD_KEYS_MAX
|| KEY_UNUSED ((seq = __pthread_keys[key].seq))) // seq是奇数,无效
/* Not valid. */
return EINVAL;
// 到这里说明本地变量超过32个了,要准备放在第二维数组里面
// 第二维数组每个都是
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
// 超过32,不到512,就是存在specific数组中
/* This is the second level array. Allocate it if necessary. */
// 获取本线程第二维数组(specific)的第idx1st个元素
level2 = THREAD_GETMEM_NC (self, specific, idx1st);
if (level2 == NULL) // 如果这个元素是null,需要创建一个类似第一维数组的数组
{
if (value == NULL)
/* We don't have to do anything. The value would in any case
be NULL. We can save the memory allocation. */
return 0;
// 创建新pthread_key_data结构体数组,32个元素
level2
= (struct pthread_key_data *) calloc (PTHREAD_KEY_2NDLEVEL_SIZE,
sizeof (*level2));
if (level2 == NULL)
return ENOMEM;
// 把本线程的specific数组的第idx1st项指向一个32个元素的pthread_key_data数组
THREAD_SETMEM_NC (self, specific, idx1st, level2);
}
// 取出第二级数组的第idx2nd项
/* Pointer to the right array element. */
level2 = &level2[idx2nd];
/* Remember that we stored at least one set of data. */
THREAD_SETMEM (self, specific_used, true);
}
/* Store the data and the sequence number so that we can recognize
stale data. */
// 保存数组
level2->seq = seq; // 保存这个线程本地变量的序号
level2->data = (void *) value; // 保存具体的数据
return 0;
}
```
以上是key大于32的情况,这里首先会验证key和seq的有效性,如果无效的话就退出。接着key大于32的时候的时候也是保存在一个32个元素大小的数组里的,但是这个数组不像小于32时候那样是现成的一个数组,而是需要动态开辟的,因为线程隔离变量一般在实际的开发中,使用的数量不会太多,所以初始化32个对于大多数情况下都够用了,所以如果初始化就512个的话就有点浪费了,所以这里大于32的情况会根据key来动态开辟数组。
由于通过前面的分析我们可以知道,线程隔离变量一个有512个,前面key小于32的时候已经用了32个了,所以剩下的会放在一个数组中,这里是放在specific这个数组中,下面是这个数组的声明
```c
// ((512 + 32 - 1) / 32)
#define PTHREAD_KEY_1STLEVEL_SIZE \
((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1) \
/ PTHREAD_KEY_2NDLEVEL_SIZE)
struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];
```
这个数组的结构和前面key小于32的数组是一样的,这个数组大小是用宏定义的,这里宏定义的最终值就是((512 + 32 - 1) / 32)。由于前面分析了后面的数组是动态开辟的,所以没32个会开辟一个数组,所以上面specific保存的依然不是最终的线程隔离变量对象,而是一个指向数组的指针,所以这个数字代表了一共有几个32个元素的数组,这个通过计算后最终声明了specific。我们回到前面__pthread_setspecific方法。
这里会先通过key除以PTHREAD_KEY_2NDLEVEL_SIZE以及模上PTHREAD_KEY_2NDLEVEL_SIZE(PTHREAD_KEY_2NDLEVEL_SIZE的值为32),对计算敏感的同学肯定一眼就看出来了,这是分别计算出在数组specific中第几个元素,以及每个数组元素中的第几项。然后通过宏THREAD_GETMEM_NC获取上面声明的specific数组的第idx1st项,idx1st代表的就是数组的第几个32个大小的数组元素,这里的宏THREAD_GETMEM_NC也很简单,就是获取specific数组的项,我们看一眼代码就可以了:
```c
#define THREAD_GETMEM_NC(descr, member, idx) \
descr->member[idx]
```
代码很简单就不多说了,继续看后面的代码。如果获取的数组项是空,那么就开辟一个32个大小的pthread_key_data结构体数组,然后调用THREAD_SETMEM_NC宏把这个新开辟的数组地址level2赋值给前面specific数组的第idx1st项,这样就是可以通过specific找到新开辟的数组level2了。这里宏也简单的看一眼就行:
```c
#define THREAD_SETMEM_NC(descr, member, idx, value) \
descr->member[idx] = (value)
```
也很简单不多说了。接着从level2数组中取出前面通过key计算出的这个数组的第idx2nd项,最后保存seq值和value值到这个数组中。这样key大于32的线程隔离对象也保存好了。
这样native层设置线程隔离变量就完成了,我们可以看到在native层首先会声明一个线程隔离变量,声明是通过pthread_once来创建一个key,这个key是一个全局数组的下标,这样当具体的一个线程执行到这个线程隔离变量的时候,会通过这个key到线程自己的堆栈中寻找保存变量的数组项,所以说key在全面声明数组中的下标和线程保存对象数组的下标是一一对应的。
看我了__pthread_setspecific方法,我们在来看下__pthread_getspecific方法:
```c
void *
__pthread_getspecific (key)
pthread_key_t key;
{
struct pthread_key_data *data;
/* Special case access to the first 2nd-level block. This is the
usual case. */
if (__builtin_expect (key < PTHREAD_KEY_2NDLEVEL_SIZE, 1))
// 如果是属于第一级的数组,直接从第一级数组中获取第key个pthread_key_data
data = &THREAD_SELF->specific_1stblock[key];
else
{
/* Verify the key is sane. */
if (key >= PTHREAD_KEYS_MAX) // 第二级数组,但是大于512了返回null
/* Not valid. */
return NULL;
// 第二级数组的下标
unsigned int idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
// 第二级数组中的32个对象数组中的下标
unsigned int idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
/* If the sequence number doesn't match or the key cannot be defined
for this thread since the second level array is not allocated
return NULL, too. */
// 获取第二级数组的第idx1st个
struct pthread_key_data *level2 = THREAD_GETMEM_NC (THREAD_SELF,
specific, idx1st);
if (level2 == NULL) // 空的话返回null
/* Not allocated, therefore no data. */
return NULL;
/* There is data. */
data = &level2[idx2nd]; // 不空获取第二级数组中指向的32个数组的第idx2nd个的data值,即一个pthread_key_data
}
void *result = data->data; // 从pthread_key_data结构体中获取data值
if (result != NULL) // 非空的话
{
uintptr_t seq = data->seq;
// 如果这个pthread_key_data的序号和保存在__pthread_keys数组中的key号不一样说明不对
if (__builtin_expect (seq != __pthread_keys[key].seq, 0))
result = data->data = NULL;
}
return result;
}
```
理解了set方法,在来看get方法就比较简单了。这里get方法同样也分为key小于32和大于32的情况,具体处理和上面set是相反的,具体这里就不详细介绍了,可以对照着前面set方法来看。这里需要注意的是,最后取出数据后需要比较下,这个的seq的值和声明这个线程隔离变量数组中的seq值是否一样,如果一样说明是同一个,否则就返回null。
这里之所以要比较seq值是因为,一个native层线程隔离变量的移除不会主动去析构每个线程中保存的变量,这个是需要开发人员自己去保证的,所以有可能会出现的情况是这个声明的线程隔离变量已经移除了,并且重新被声明了一个新的变量,此时的key和某个线程中存在的老的线程隔离变量是一样的,所以当某个线程中的变量要析构这个变量的时候调用的析构方法就不是他开始的那个了,所以就会报错。这也就是为什么开始我们分析创建线程隔离变量的时候看到的seq值只会增加,不会减小,因为seq的值是不会被复用的,一旦复用的了,就不能判断当前这个线程隔离变量的析构方法是不是真正自己的了,所以seq的值只会增加,不会减少。
好了关于native层的线程隔离变量主要就这些方法了,可以看到理解上比起java层的ThreadLocal稍显得复杂些,当前natice层其实对于线程隔离的处理办法不止这一种,也有类似ThreadLocal的处理,这里主要根据Handler的源码来分析,大家对这些了解下就可以,有兴趣的再深入研究,好了,我们回到Looper代码哪里。
# linux的epoll和Eventfd
前面分析到native层的Looper的prepare方法,说到了也需要向java层那样,给每个线程创建一个线程隔离的Looper,上面我们分析了native层的线程隔离原理,下面就开始要创建native层的Looper了,在分析native层创建Looper前面,我们先介绍下linux的poll机制,因为Handler的核心原理就是epoll机制,我们稍微知道下是怎么回事,后面看Handler代码的时候能有更好的理解。
epoll机制简单说就是监听某个文件描述符,如果这个文件描述符有发生变化就会唤醒监听的线程,否则该线程就会睡眠,睡眠不会占用cpu,所以对性能没太大影响。如果对之前我们分析的android启动流程或者binder源码分析有看过的同学,肯定看到过select或者poll机制,其实epoll和他们也是类似的,之前我们分析select或者poll的时候也是监听某个文件描述符,比如我们分析android启动流程的时候就会创建一个socket文件,然后监听他的文件描述符,如果有创建新进程的请求就会往这个socket的fd发生请求,从而poll可以监听到。
之前遇到过的select或者poll机制也是一种多路复用的技术,但是他们的缺点在于,如果监听了数个fd,一旦其中的某个fd有新的消息了,select或者poll是不知道具体是哪个fd的,需要遍历所有监听的fd才能知道那个fd需要处理,所以他们的时间复杂度是O(n)。而epoll改进的地方是监听的所有fd中,当某个fd有新消息的时候会主动通知epoll,如果是多个fd有消息会返回给epoll一个数组,里面包含了所有的fd,这样的话时间复杂度相当于O(1)了,效率自然提高了。
epoll主要有三个方法,epoll_create,epoll_ctl和epoll_wait。epoll_create有一个参数,表示可以监听多少个文件描述符。epoll_ctl方法主要负责对某个创建的epoll进行fd的增,删,改等操作,这个之后我们在分析Handler代码中可以看到使用。epoll_wait是监听所有的指定的fd,如果有fd被请求了,就会触发epoll_wait中传入的数组,数组中就是被触发发生变化的fd。这些方法这里稍微提一个,后面我们到代码具体看。
上面说了epoll监听文件描述符,epoll监听的文件描述符需要实现特地的接口来用于回调,比如之前我们分析android启动流程的时候看到的socket的文件描述符也可以用于epoll这里。但是我们这里主要是用户Handler的消息发送,对于这种普通的事件产生的场景,linux提供了Eventfd这种文件描述符来处理这类事件,Handler中也是使用了这种文件描述符。
对于Eventfd的理解,其实就可以看做一个计数器,只要这个fd的值大于0,那么说明有请求了,就会触发epoll的唤醒,如果fd中的值是0,就说明没有新消息,就被阻塞了。Handler中一旦有消息了就会往一个eventfd写入数据,具体数据是多少不重要,只要写入一个大于0的数字,epoll就换知道,然后就会继续处理后续的消息。Eventfd的声明有两个参数,第一个参数是初始值,一般是0,第二个参数表示如果这个fd没有数据的时候是否需要阻塞,比如一个epoll监听了2个fd,其中一个fd创建的时候参数设置了阻塞,那么当没有消息的时候epoll就不会进入睡眠状态,线程将卡在那边,这个不是我们希望的,所以一般创建eventfd的时候都会设置不阻塞。
上面说了Eventfd其实就是一个计数器,所以如果往Eventfd写入的是一个8字节的数字,每次写都会在原来值的基础上累加,并且不管写几次,只要读取一次,这个值就被清0了,这个就是Eventfd的特性。
好了epoll和相关eventfd就简单介绍到这里,我们下面会分析natvie层的Looper的创建,里面会看到epool和eventfd的使用,通过实际代码我们就能更好的理解他们运行机制。
# native层Looper的创建
```c++
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
// 创建 eventfd EFD_NONBLOCK 非阻塞 ,EFD_CLOEXEC 当fork出的进程使用是会自动关闭
// 这个是默认唤醒请求时候的fd
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
strerror(errno));
AutoMutex _l(mLock);
// 初始化epoll
rebuildEpollLocked();
}
```
上面的方法就是native层的Looper的构造方法。可以看到方法开头就是创建了一个eventfd,参数一是初始值0,参数二是设置设置了非阻塞以及如果被fork一个新进程后这个fd会自动关闭。这个fd就是代表了Handler是否有新的消息可以处理,一旦有新的消息,这个fd会被写入数据,epoll就会被唤醒,Handler的后续流程就会继续进行,我们继续看后面的代码就会比较清楚了。
之后会加锁进行同步,反正后面初始化epoll的时候多次被执行。接着调用rebuildEpollLocked方法开始初始化epoll:
```c++
void Looper::rebuildEpollLocked() {
// Close old epoll instance if we have one.
if (mEpollFd >= 0) { // 已经有了,那么先关闭
#if DEBUG_CALLBACKS
ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
close(mEpollFd);
}
// Allocate the new epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT); // 创建epoll
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
// EPOLL_CTL_ADD表示添加要监听的文件描述符到epoll
// 参数一 代表epoll的文件描述符
// 参数二 添加监听的文件描述符操作
// 参数三 监听的文件描述符
// 参数四 监听的文件描述符对应的结构体
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
strerror(errno));
// 遍历mRequests容器,如果里面有数据取出来加入epoll监听中
// 这个一般在添加定义fd时候,如果已经存在了fd,会修改这个fd,但是修改时候有报错不存在这个fd
// 会重新唤醒looper后,进而调用pollonce-pollin方法后重新创建epoll,便会走到这里,就从mRequests
// 取出后再加入
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem; // 创建一个epoll_event
request.initEventItem(&eventItem); // 把mRequests取出的元素初始化到eventItem中
// 加入到epoll监听
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
request.fd, strerror(errno));
}
}
}
struct epoll_event {
uint32_t events; // 类型EPOLLIN等
epoll_data_t data;
}
typedef union epoll_data {
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
```
这个方法首先会判断mEpollFd是否大于0,mEpollFd代表了一个epoll实例的文件描述符,注意和上面eventfd的文件描述符区别开来,不要搞混了。如果mEpollFd大于等于0,说明可能有了一个epoll了,那么需要先关闭之前的这个epoll(Looper在每个线程只会有一个,正常情况下这个只会被执行一次,除非是出错需要重新创建,默认值是0的话没关系,不会报错)。之后调用epoll_create方法创建epoll,epoll_create方法我们前面已经介绍过了,是创建一个epoll,他的参数表示他可以监听多少个文件描述符,这里EPOLL_SIZE_HINT的值是8,表示可以监听8个文件描述符。接着就要往epoll里面添加监听的fd了,这里会调用epoll_ctl方法来添加,这里需要四个参数,第一个mEpollFd就是这个epoll的文件描述符,创建成功后mEpollFd就正式被赋值了(可能值是0,和默认的一样,但是实际意义不一样)。第二个参数EPOLL_CTL_ADD表示需要的操作是往epoll里添加一个fd。第三个参数就是前面创建的eventfd的文件描述符。第四个参数是一个epoll_event,这个是给回调时候用的结构体,前面我们介绍epoll机制的时候说过,相比于select和poll机制,epoll不需要自己去变量所有监听的fd哪个被触发了,系统会自动返回一个数组,这个数组的类型就是这个epoll_event。
epoll_event是个结构体,有两个字段,第一个字段events表示这个fd要监听哪种事件的,这里有好几种类型的时间比如,读事件EPOLLIN,写事件EPOLLOUT,错误事件 EPOLLERR等等,这是主要是监听Handler发送的消息所以是监听读取事件EPOLLIN。第二个字段是保存和监听某个fd有关的一些数据,这里是个联合体,所以一般会在四个字段中选一个来用,这里赋值了这个文件描述fd的值,在后面回调时候判断文件描述符的时候用,下面我们会看到。
这样就把监听Handler消息到达的fd添加好了。我们我们在使用Handler的时候就用这个默认定义fd就可以处理消息了,但是Android还提供了可以让你添加一个自定义fd的方法,通过添加自定义fd可以监听一些其他事件,比如默认的fd只监听读取事件,如果你还想监听写事件,可以自己添加fd。最终这个自定义的fd相关的数据会添加到mRequests这个KeyedVector中,KeyedVector可以理解为一个Map,key是一个fd,value是一个Request,这个Request保存了这个fd的监听类型,触发后的回调方法等等,我们后面会具体分析,这里先不深入了,这里我们需要理解的是除了把默认的监听Handler发送消息的fd添加到epoll中,还会从mRequests中取出自定义的fd添加到epoll中。
我们回到rebuildEpollLocked方法,可以看到会遍历mRequests中的数据,取出Request对象,然后调用Request的initEventItem方法来初始化一个epoll_event对象,最后把这个自定义fd添加进epoll中。我们稍稍看一眼initEventItem方法:
```c++
void Looper::Request::initEventItem(struct epoll_event* eventItem) const {
int epollEvents = 0;
if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
if (events & EVENT_OUTPUT) epollEvents |= EPOLLOUT;
memset(eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem->events = epollEvents;
eventItem->data.fd = fd;
}
```
可以看到这个自定义添加了EPOLLIN和EPOLLOUT(如果有的话)的监听类型,之后和默认的一样,把监听类型和文件描述符添加到epoll_event对象中就初始化好了。
到这里其实一个Handler就算初始化完毕了,调用者已经得到了一个Handler的实例对象了,接着就可以使用Handler来发送和接受消息了,此时如果是刚开始的话,当前线程会进入到阻塞的状态,等待着被唤醒,这点我们接下去在分析发送和接受消息那里可以看到。目前分析到初始化完毕,这篇文章就暂时告一段落了,我们下篇文章从发送消息开始继续分析,下篇文章见。
Handler源码解析(一)