2.android消息机制-native

1 几个简单类

  • MessageQueue — 里面有一个Looper,和java层的MessageQueue同名;
  • NativeMessageQueue — MessageQueue的继承类,native层的消息队列,只是一个代理类,其大部分方法操作都转交给Looper的方法;
  • Looper — native层的Looper,其功能相当于java层的Handler,它可以取出消息,发送消息,处理消息;
  • MessageHandler — native层的消息处理类,Looper把处理消息逻辑转交给此类;
  • WeakMessageHanlder — MessageHandler的继承类,也是消息处理类,但最终还是会把消息处理逻辑转交给MessageHandler。

2 MessageQueue

这篇文章主要分析nativeInit,nativePollOnce和nativeWake

  • nativeInit Java层Looper构造->MessageQueue构造->nativeInit
  • nativePollOnce Java层取消息 next方法时调用
  • nativeWake java层的MessageQueue的enqueueuMessage方法调用
//MessageQueue.java
public final class MessageQueue {
    private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    private native void nativePollOnce(long ptr, int timeoutMillis); 
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
    //...
}

2.1 nativeInit()

java层,在ActivityThread的main方法创建UI线程的消息循环,Looper.prepareMainLooper -> Looper.prepare -> new Looper -> new MessageQueue

//MessageQueue.java
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}
//frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {   
    //创建native消息队列NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    //...
    //增加引用计数
    nativeMessageQueue->incStrong(env);
    //使用C++强制类型转换符reinterpret_cast把NativeMessageQueue指针强转成long类型并返回到java层
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

//frameworks/base/core/jni/android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() 
    : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    //获取TLS中的Looper(Looper::getForThread相当于java层的Looper.mLooper中的ThreadLocal.get方法) 
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        //创建native层的Looper
        mLooper = new Looper(false);
        //保存Looper到TLS中(Looper::setForThread相当于java层的ThreadLocal.set方法)
        Looper::setForThread(mLooper);
    }
}

//system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    //1、构造唤醒事件的fd(文件描述符)
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    //...
    //2、重建epoll事件
    rebuildEpollLocked();
}

文件描述符是什么?它就是一个int值,又叫做句柄,在Linux中,打开或新建一个文件,它会返回一个文件描述符,读写文件需要使用文件描述符来指定待读写的文件,所以文件描述符就是指代被打开的文件,所有对这个文件的IO操作都要通过文件描述符

但其实文件描述符也不仅仅是指代文件,它还有更多的含义,可以看后文的epoll机制解释。

//system/core/libutils/Looper.cpp
void Looper::rebuildEpollLocked() {
      //如果有旧的epoll则先关闭掉
      if (mEpollFd >= 0) {
          mEpollFd.reset();
      }
  
      //创建epoll并注册wake管道
      mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
		
      struct epoll_event eventItem;
      memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
      //epoll事件为可读事件
      eventItem.events = EPOLLIN;
      //将创建的唤醒事件的fd设置到eventItem
      eventItem.data.fd = mWakeEventFd.get();
      //epoll_ctl将mWakeEventFd添加到创建的mEpollFd进行监听
      int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
      
  	  //这里遍历mRequests,将其他的事件,如Input,键盘等也添加到epoll进行
  	  //监听,mRequests中的fd是通过调用Looper.addFd添加的,
  	  //这些事件和Handler消息机制没有关系
      for (size_t i = 0; i < mRequests.size(); i++) {
          const Request& request = mRequests.valueAt(i);
          struct epoll_event eventItem;
          request.initEventItem(&eventItem);
  		  //epoll_ctl方法用作添加fd到epoll进行监听
          int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
          if (epollResult < 0) {
              	....//error
          }
      }
  }

Looper构造函数涉及Linux epoll机制,epoll机制是Linux最高效的I/O复用机制,可以监听大批量的fd,其效率远远强于select/poll,epoll除了监听通过eventfd创建的唤醒文件描述符外,还可以监控其他的文件描述符,native层的Looper提供了addFd接口来添加文件描述符到Looper对象中,当这些被监控的文件描述符上有事情发生时,就会唤醒相应的线程来处理,比如在Input系统中UI线程和InputDispatcher线程的Looper都会调用addFd分别将InputChannel的客户端socket和服务端socket添加到各自的epoll进行监听,以便进行通信从而实现事件分发和处理

2.2 nativePollOnce()

在native层通过epoll机制也建立了一套消息机制后,java层的消息循环也就创建好,在此之后就会在java层中启动消息循环,Looper.loop -> MessageQueue.next,在java层中每次循环去读消息时,都会调用MessageQueue的next函数,如下:

//MessageQueue.java
 Message next() {
 	//...
 	for(;;){
 		 nativePollOnce(ptr, nextPollTimeoutMillis);
 		 //...
 	}
 	//...
 }

//frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    //把ptr强转为NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

//frameworks/base/core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    //核心是调用了native层的Looper的pollOnce方法
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

//NativeMessageQueue是一个代理类,所以它把逻辑转交给Looper,
//system/core/libutils/Looper.h
inline int pollOnce(int timeoutMillis) {
    //调用了带4个参数的pollOnce方法
    return pollOnce(timeoutMillis, NULL, NULL, NULL);
}

//system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
      int result = 0;
      for (;;) {
      		// 先处理没有Callback的Response事件
          while (mResponseIndex < mResponses.size()) {
	             ....
          }
          //
        //当result不等于0时,就会跳出循环,返回到java层
  			//待后面pollInner返回不为0的result后退出循环
          if (result != 0) {
              if (outFd != nullptr) *outFd = 0;
              if (outEvents != nullptr) *outEvents = 0;
              if (outData != nullptr) *outData = nullptr;
              return result;
          }
  		  //核心方法 处理内部轮询
          result = pollInner(timeoutMillis);
      }
  }

pollInner方法返回一个int值result,代表着本次轮询是否成功处理了消息,当result不等于0时,就会跳出循环,返回到java层继续处理java层消息,result有以下4种取值:

enum {
    //表示Looper的wake方法被调用,即管道的写端的write事件触发
    POLL_WAKE = -1,
    //表示某个被监听fd被触发。
    POLL_CALLBACK = -2,
    //表示等待超时
    POLL_TIMEOUT = -3,
    //表示等待期间发生错误
    POLL_ERROR = -4,
};
//system/core/libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) { 
    //timeoutMillis等于-1,并且mNextMessageUptime不等于LLONG_MAX
    //这说明java层没有消息但是native层有消息处理,这时在epoll_wait中,线程不能因为timeoutMillis等于-1而进入休眠,它还需要处理native层消息
    //所以这里会根据mNextMessageUptime把timeoutMillis更新为大于0的值
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            //更新timeoutMillis为大于0的值,这个大于0的值就是需要等待多久后,才会到达native层消息的执行时间,等待timeoutMillis后,epoll_wait就会返回处理native层消息
            timeoutMillis = messageTimeoutMillis;
        }
        //...
    }
    int result = POLL_WAKE;
    //...
    //事件集合(eventItems),EPOLL_MAX_EVENTS为最大事件数量,它的值为16
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    //1、等待事件发生或者超时(timeoutMillis),如果有事件发生,就从管道中读取事件放入事件集合(eventItems)返回,如果没有事件发生,进入休眠等待,如果timeoutMillis时间后还没有被唤醒,就会返回
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
    
    //获取锁
    mLock.lock();
    
    //...省略的逻辑是:如果eventCount <= 0 都会直接跳转到Done:;标记的代码段

    //2、遍历事件集合(eventItems),检测哪一个文件描述符发生了IO事件
    for (int i = 0; i < eventCount; i++) {
        //取出文件描述符
        int fd = eventItems[i].data.fd;
        //取出事件类型
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {//如果文件描述符为mWakeEventFd
            if (epollEvents & EPOLLIN) {//并且事件类型为EPOLLIN(可读事件)
                //这说明当前线程关联的管道的另外一端写入了新数据
                //调用awoken方法不断的读取管道数据,直到清空管道
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {//如果是其他文件描述符,就进行它们自己的处理逻辑
           //...
        }
    }
    
    //2、下面是处理Native的Message
    Done:;
    //mNextMessageUptime如果没有值,会被赋值成LLONG_MAX,但是如果mNextMessageUptime已经有值,它还是保持原来的值
    mNextMessageUptime = LLONG_MAX;
    //mMessageEnvelopes是一个Vector集合,它代表着native中的消息队列
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //取出MessageEnvelope,MessageEnvelop有收件人Hanlder和消息内容Message
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        //判断消息的执行时间
        if (messageEnvelope.uptime <= now) {//消息到达执行时间
            {
        	    //获取native层的Handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                //获取native层的消息
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                //释放锁
                mLock.unlock();
                //通过MessageHandler的handleMessage方法处理native层的消息
                handler->handleMessage(message);
            }
            mLock.lock();
            mSendingMessage = false;
            //result等于POLL_CALLBACK,表示某个监听事件被触发
            result = POLL_CALLBACK;
        } else {//消息还没到执行时间
            //把消息的执行时间赋值给mNextMessageUptime
            mNextMessageUptime = messageEnvelope.uptime;
            //跳出循环,进入下一次轮询
            break;
        }
    }
    //释放锁
    mLock.unlock();
    //...
    return result;
}

pollInner函数已经分析完毕,作一个总结:

  1. 视情况对timeoutMillis进行重置,timeoutMillis这个时间代表着epoll_wait被唤醒的时间,timeoutMillis有java层和native层的mNextMessageUptime共同决定,说明epoll_wait的唤醒由java层和native层共同决定
  2. 调用epoll_wait进入等待状态,三种情况能够唤醒epoll_wait,(1)epoll监听的fd被触发,(2)超时唤醒,(3)发生错误被唤醒
  3. 在epoll_wait被fd触发唤醒之后执行相应的操作,(1)如果是mWakeEventFd则清空管道,(2)如果是其他fd,则将其request添加到Response待稍后处理
  4. 开始处理native的msg和调用request的callback

2.3 处理native message

只要epoll_wait方法返回后,都会进入Done标记位的代码段, 就开始处理处理native层的Message, 在此之前先讲解一下MessageEnvelope,正如其名字,信封,其结构体定义在Looper.h中,如下:

//system/core/libutils/Looper.h
class Looper : public RefBase { 
    struct MessageEnvelope {
        MessageEnvelope() : uptime(0) { }

        MessageEnvelope(nsecs_t u, const sp<MessageHandler> h, const Message& m) : uptime(u), handler(h), message(m) {}

        nsecs_t uptime;
        //收信人handler
        sp<MessageHandler> handler;
        //信息内容message
        Message message;
    };   
    //...
}

MessageEnvelope里面记录着收信人(handler,MessageHandler类型,是一个消息处理类),发信时间(uptime),信件内容(message,Message类型),Message结构体,消息处理类MessageHandler都定义在Looper.h文件中, 在java层中,消息队列是一个链表,在native层中,消息队列是一个C++的Vector向量,Vector存放的是MessageEnvelope元素,接下来就进入一个while循环,里面会判断消息是否达到执行时间,如果到达执行时间,就会取出信封中的MessageHandler和Message,把Message交给MessageHandler的handlerMessage方法处理;如果没有到达执行时间,就会更新mNextMessageUptime为消息的执行时间,这样在下一次轮询时,如果由于java层没有消息导致timeoutMillis等于-1,就会根据mNextMessageUptime更新timeoutMillis为需要等待执行的时间,超时后返回继续处理native层消息队列的头部信息。

3 nativeWake()

Java层通过Hanlder发送消息时,实际是把消息添加到消息队列,Handler.sendXX -> Handler.enqueueMessage -> MessageQueuue.enqueueMessage,最终会调用到MessageQueue的enqueueMessage方法,,如下:

//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    //...
    synchronized (this) {
        //...
        if (needWake) {
            nativeWake(mPtr);
        }
    }
}
//frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}
//NativeMessageQueue只是一个代理Looper的角色,该方法把操作转发给native层的Looper
//frameworks/base/core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::wake() {
    mLooper->wake();
}

//system/core/libutils/Looper.cpp
void Looper::wake() {
    uint64_t inc = 1;
    //使用write函数通过mWakeEventFd往管道写入字符inc
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    //...
}

Looper的wake方法其实是使用write函数通过mWakeEventFd往管道写入字符inc,其中TEMP_FAILURE_RETRY 是一个宏定义, 当执行write方法失败后,会不断重复执行,直到执行成功为止,在nativeinit中,我们已经通过epoll_create方法监听了mWakeEventFd的可读事件,当mWakeEventFd可读时,epoll文件描述符就会监听到,这时epoll_wait方法就会从管道中读取事件返回,返回后就执行消息处理逻辑,所以这里的往管道写入字符inc,其实起到一个通知的作用,告诉监听的线程有消息插入了消息队列了,快点醒过来(因为进入了休眠状态)处理一下。

为什么方法阻塞却又不消耗性能

epoll_wait方法会让当前线程释放CPU资源进入休眠状态,等到下一个消息到达(mWakeEventFd会往管道写入字符)或监听的其他事件发生时就会唤醒线程,然后处理消息,

因为java层的消息机制是依赖native层的消息机制来实现的,而native层的消息机制是通过Linux的管道和epoll机制实现的,epoll机制是一种高效的IO多路复用机制, 它使用一个文件描述符管理多个描述符,java层通过mPtr指针也就共享了native层的epoll机制的高效性,当loop方法中取不到消息时,便阻塞在MessageQueue的next方法,而next方法阻塞在nativePollOnce方法,nativePollOnce方法通过JNI调用进入到native层中去,最终nativePollOnce方法阻塞在epoll_wait方法中,epoll_wait方法会让当前线程释放CPU资源进入休眠状态,等到下一个消息到达(mWakeEventFd会往管道写入字符)或监听的其他事件发生时就会唤醒线程,然后处理消息,所以就算loop方法是死循环,当线程空闲时,它会进入休眠状态,不会消耗大量的CPU资源。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]的代码片段是关于gralloc设备的打开函数,其包含了对GPU设备和FB设备的区分。根据设备名来判断打开的是哪个设备,并调用相应的函数完成设备的打开操作。\[1\] 引用\[2\]的代码片段是gralloc_device_open函数的一部分,它展示了当设备名不是GPU设备时,会调用fb_device_open函数来打开设备。\[2\] 引用\[3\]的代码片段是关于Android消息队列的相关代码,其包含了将消息插入消息队列的操作。\[3\] 根据提供的引用内容,无法直接回答关于android.hardware.common.NativeHandle的问题。请提供更多相关信息或明确问题的具体内容,以便我能够给出更准确的答案。 #### 引用[.reference_title] - *1* *2* [Android图形显示之硬件抽象Gralloc](https://blog.csdn.net/iteye_13202/article/details/82542325)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Android组件系列:再谈Handler机制Native篇)](https://blog.csdn.net/m0_64420071/article/details/127030611)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值