Android

Android消息系统分析

2017-08-28  本文已影响17人  gczxbb

在整个消息系统中,Java层中负责消息内容的构建,消息队列的维护与创建,消息事件回调,Native层负责控制线程的休眠与唤醒,每个类都有自己的分工与任务,相互协作一起构建了Android消息系统

Java层与Native层消息队列MQ和Looper的创建及各自负责的内容

Java层Looper由线程亲自创建,每个线程创建的Looper实例通过ThreadLocal维护,使得每个线程都有属于自己的Looper的存储空间,并确保每个线程只有一个,各个线程的Looper不会相互影响。在创建Looper的构造方法中同时创建属于该线程的Java层消息队列MQ实例。

Java层消息队列MessageQueue,通过JNI方法nativeInit创建Native层消息队列NativeMessageQueue实例,并将 Native层对象的指针mPtr返回给Java层MQ, 在NativeMessageQueue的构造方法会创建Native的Looper对象。

NativeMessageQueue构造方法创建Looper
mLooper = Looper::getForThread();
if (mLooper == NULL) {
    mLooper = new Looper(false);
    Looper::setForThread(mLooper);
}
如果当前线程已经存在Looper(通过getForThread获取),直接使用,否则创建并绑定

getForThread与setForThread内部如何实现Native层Looper在线程中的唯一性

先看getForThread,执行了2个方法
int result = pthread_once(& gTLSOnce, initTLSKey)
return (Looper*)pthread_getspecific(gTLSKey);

参数定义
static pthread_once_t gTLSOnce = PTHREAD_ONCE_INIT;//控制变量
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.");
}

pthread_once方法的功能是使得initTLSKey在本进程中只执行一次,当执行pthread_once时
判断gTLSOnce状态
如果在执行中,则所有执行pthread_once的线程等待,直到正在执行initTLSKey的结束
然后pthread_once直接返回,不再执行initTLSKey
如果未执行状态,则执行initTLSKey
如果已完成状态,则直接返回

pthread_key_create创建线程私有数据,当有一个线程调用了pthread_key_create
那么其他所有线程都可以访问gTLSKey,
只不过每个线程获取到的value不同,保存每个线程的私有value,所以initTLSKey在进程中只需要执行一次
pthread_getspecific方法获取gTLSKey的值,对应的是调用线程的私有数据,这里保存的是Looper。

setForThread方法
pthread_setspecific(gTLSKey, looper.get());
保存looper到当前线程的私有,确保该线程执行getForThread时能得到私有的它

这样NativeMessageQueue中创建了Native层Looper,并把该Looper绑定了当前线程

Java层MQ负责消息队列维护与处理,消息的增删查该等相关算法,Looper负责建立Java层循环,连接MQ获取消息及回调处理,Native层MQ负责连接Java层服务通信,Looper负责wake与pull。

Native层Looper的利用epoll机制实现Pull消息与wake唤醒

Native层Looper是Android消息系统的核心,线程休眠与唤醒由它负责,在Looper构建时,会初始化内部一个唤醒事件文件描述符mWakeEventFd,并建立Epoll休眠唤醒机制。Epoll适用于Linux内核中监听多个fd句柄事件,需建立epoll句柄mEpollFd,告诉内核我想监听哪个fd的事件,并向epoll句柄注册这个事件。Fd可包括各类流,例如socket流,管道,文件描述符等,在Looper中监听了内部mWakeEventFd流以及其他流,这样Looper内部的epoll机制就构建好了。

Looper的pullOnce方法负责拉取消息,进入一个无限循环,NativeMessageQueue及Java层的消息队列通过JNI方法nativePollOnce会触发它,Native层其他利用Looper的唤醒休眠机制的地方也会触发它。通过epollwait使得当前线程等待,当注册的流中发生了事件,线程被唤醒,检查是否mWakeEventFd流发生事件,然后awoke读取写入mWakeEventFd的内容,读取的内容并不重要,重点在于这个过程使得线程被唤醒,会回到Java层的读取线程获取具体消息,并继续执行循环。

Looper的wake方法负责唤醒,只需要向mWakeEventFd流中写入内容,这样读取线程就能因epoll监听到内容而被唤醒,写入的内容不是重点。wake的时机在发送消息后,不管是Native层的消息还是Java层的消息,在Java层发送消息到MQ中,触发wake利用JNI方nativeWake调用Looper的wake,Native层的消息存储在Vector结构类型的mMessageEnvelopes中,将发送的消息插入。

综上可见,Native的Looper只提供了一种消息读取(接收)线程的休眠与唤醒的机制,唤醒是被发送线程触发的,触发的原因是有消息来了,还负责Native消息的发送及读取工作,但不负责发送与读取Java层消息内容的工作。Java层的消息接收线程利用Java层Looper建立loop循环,通过Java层消息队列的JNI方法而进入pullOnce,从而开启了消息的无限等待中。这时如果利用Java层Handler往消息队列中发送Message,即触发唤醒,这就是Native层的Looper的工作基本原理。

Java层发送消息插入到消息队列并处理消息

根据消息时间插入队列相应位置,判断是否需要唤醒,最后去唤nativeWake(mPtr),触发Native层消息队列NativeMessageQueue,触发Native层Looper的wake,唤醒接收线程,在Native层被唤醒后,又回到Java层的MQ的next方法来,读取到具体消息,继而回到Java层的Looper的loop循环,利用Handler进行消息读取与分发,它仅是一个工具,负责消息内容构建,发送,后期分发的回调,这样不管是哪个发送线程利用哪个Handler发送的消息,得到消息并进行回调的都是接收线程。

Android消息系统的大致工作就是这样实现的,本文只从表面来分析其中比较核心的部分,每个类的实现细节暂时不究,本文无任何代码示例,只谈如何工作以及核心原理。

上一篇下一篇

猜你喜欢

热点阅读