Android源码探究:Android Native层消息机制完全解析

前言

前文详细分析了Java层的消息循环机制的工作原理,在分析MessageQueue的过程中,我们遇到了nativePollOnce()nativeWake()方法的调用,下面我们就深入到Native层的消息机制来看看它背后的运作原理。

Native层的消息机制

一、NativeMessageQueue的相关逻辑
1、NativeMessageQueue的构建
首先,我们来看看Java层的MessageQueue的构造函数:

    MessageQueue(boolean quitAllowed) {
   
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

这里调用了一个native方法,实际上这里初始化了native层的消息队列,并返回了该消息队列的头部指针地址。由于这个是native方法,这里利用了JNI机制调用本地代码,JNI的相关知识笔者在前面的文章也有说到,这里就不再赘述。我们直接来看本地代码:(frameworks/base/core/jni/android_os_MessageQueue.cpp):

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();  //创建了一个本地的消息队列
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env); //增加强引用指针计数,这里与RefBase,类似于智能指针的概念
    return reinterpret_cast<jlong>(nativeMessageQueue); //强制类型转换,返回jlong类型,实际上是指针的值
}

这里引入了NativeMessageQueue,顾名思义,这应该是native层的消息队列,我们用UML类图来看看它的类结构:
NativeMessageQueue类结构
其中,RefBase是基类,它的作用比较特殊,在Android的native代码中,大部分的类都是继承自RefBase,作用有点类似于Java中的Object。实际上,它的作用与实现智能指针有关,便于native层的垃圾回收的实现(因为C++没有GC机制,我们需要手动实现对象的创建和回收操作)。这里不对RefBase做过多的深究,我们把关注点放回NativeMessageQueue

NativeMessageQueue是native层的消息队列,虽然它称作消息队列,但实际上它是一个空壳,它内部并没有维护消息的队列或者链表,它把涉及消息的相关操作都交给了Looper去处理。与Java层一样的是,一条线程只会有一个Looper,这是因为native层也有着一套线程独立变量的机制,当前线程的变量只与当前线程有关。我们来看看NativeMessageQueue的构造方法:

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
   
    mLooper = Looper::getForThread();  //获取当前线程的Looper对象
    if (mLooper == NULL) {
     //如果没有,那么初始化Looper对象
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);  //Looper与当前线程绑定
    }
}

通过NativeMessageQueue的构造方法可以看出,这里实例化了一个Looper,而这个Looper则是与线程相关的,因此我们可以推测Looper实际上承担了消息队列的实际功能,NativeMessageQueue对外表现为消息队列,也有相关的方法,但实际上它把核心逻辑都移交给了Looper去处理。

小结:Java层的MessageQueue被创建的时候,同时会创建一个Native层的NativeMessageQueue,并初始化一个native的Looper。经过JNI的调用返回,NativeMessageQueue对象的指针地址会返回给Java层的MessageQueue,保存在mPtr这个成员变量内。

2、NativeMessageQueue#pollOnce
经过上面的NativeMessageQueue的初始化后,我们就能正常使用它了。还记得我们在Java层的MessageQueue#next()方法内曾经看到过这个调用吗?nativePollOnce(ptr, nextPollTimeoutMillis),这里调用了native的方法,我们来看看它的nativec层的源码:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
   
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

方法很简单,这里传进来的ptr参数实际上就是MessageQueue.mPtr变量,经过强制类型转换后,把ptr转换成了NativeMessageQueue指针,然后调用这个对象的pollOnce(args)方法。这个过程可以理解为:在某一线程内,Java层持有Native层的NativeMessageQueue的指针地址值,经过JNI调用把该地址传递过来,native层根据这个地址找到了这个对象,强制类型转换成了NativeMessageQueue对象。简单地说,Java层的MessageQueue对应了Native层的NativeMessageQueue。

下面,我们来看nativeMessageQueue->pollOnce(env, obj, timeoutMillis):

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
   
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);  //把逻辑交给Looper去处理
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
   
        env->Throw(mExceptionObj);  //env是JNI环境,这里的异常会抛给Java层
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

逻辑很简单,这里调用了mLooper->pollOnce(timeoutMillis)方法,并把超时时间传递了进去,结合函数名字,我们可以合理推测:该函数内部在超时时间内进行native的消息处理,达到了超时时间就会JNI调用返回,以便处理Java层的消息。我们先记住这个推测,待会分析Looper的时候再来看这个推测是否正确。

3、NativeMessageQueue#wake
同样地,我们之前在讨论MessageQueue#enqueueMessage()的时候,会发现进行了nativeWake()调用,那么我们直接看源码:

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
   
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
   
    mLooper->wake();
}

显然,根据ptr找到对应的NativeMessageQueue,然后调用了mLooper->wake(),把逻辑交给了Looper去处理。那么到目前为止,一切问题的关键都指向了Looper,我们开始探究native的Looper吧。

二、native Looper的工作原理
Looper是整个Native层消息机制的核心所在,大部分功能都是Looper完成的。要准确理解native Looper,我们首先就要对它的类结构有个整体的认识,下面笔者给出Looper的UML类图方便读者的理解。

1、native Looper的整体认识
Looper的类结构被定义在/system/core/libutils/include/utils/Looper.h,源码地址为Looper.h。结合类结构,我们可以清晰地画出如下的UML类图:
Looper UML类图
结合Looper.h的源码和上面的UML类图,下面先列举几个与Looper有关的类或成员变量,以便后面的源码阅读。
①mWakeEventFd:用于事件通知的文件描述符,在这里表示唤醒Looper的文件描述符。实际上它指向一个eventfd对象,该对象可以实现事件的等待和通知机制。
②mEpollFd:epoll的句柄,指向一个epoll对象,有关epoll的使用都要经过该句柄。epoll简单来说,是Linux下的一种I/O事件通知机制。epoll监听了eventfd,当该文件描述符的缓冲区为空时,epoll发出可写信号,当文件描述符的缓冲区不空时,发出可读信号。利用这样的机制,能实现线程间的通信。
③MessageEnvelop:内部封装了MessageMessageHandleruptime。其中Message是native层发送的消息,而MessageHandler内部有一个回调函数,当消息接收方接收并处理消息后就会回调该函数。uptime是消息的触发时间。
④Request:封装了fdeventscallback等参数,实际上这是Looper监听的一种特殊消息,可以称之为事件,它是以文件描述符形式存在的。通过addFd(args)把fd添加到epoll的监听队列中,然后封装成Request对象,加入到Looper.mRequest内,然后等待事件的发生。
⑤Response:当epoll侦测到某一fd的缓冲池发生了改变后,Looper会找到该fd对应的Request,把它进一步封装成Response,然后加入mResponse等待Looper处理该消息。

2、Looper的创建与初始化
上面详细讲述了与Looper有关的一些知识,在此基础上我们继续探索Looper的原理,首先,我们从Looper的构造方法看起。(源码位置在:/system/core/libutils/Looper.cpp)

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值