做Android开发的小伙伴一定都熟悉Android的消息循环机制(Looper MessageQueue Handler), 相信也一定知道Looper是一个死循环,循环中 MessageQueue 不停地去拿消息处理,如果拿不到就阻塞当前主线程执行,下面重温一下这块的代码
Looper.java
public static void loop() {
//... 省略无关代码
for (;;) {
// 这里我把官方注释放这里了,next方法执行过程中可能会发生阻塞
Message msg = queue.next(); // might block
}
//...省略无关代码
}
MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
Message next() {
for (;;) {
// 这是一个Native方法 核心的阻塞方法就在这里
nativePollOnce(ptr, nextPollTimeoutMillis);
}
在我了解IO多路复用、用户态、内核态 的知识之前,我一直有一个疑问:
假如代码执行nativePollOnce进入了阻塞状态,1秒钟之后来了一条消息(比如用户点击了App中的一个按钮)
主线程为什么可以从阻塞状态变成运行状态呢?
这里就牵扯到用户态 → 内核态的进程模式切换了
任何程序的执行本质上都是在执行一堆的指令,你以为指令之间都是平等的?那就大错特错了,其实指令之间也有高低贵贱之分。
用户态其实就是指令A的一生,只能做点简单的工作,要是遇到内核相关的操作就要请求指令B来帮忙,程序执行指令A的状态就是用户态,执行指令B的状态就是内核态,进程从用户态到内核态,再从内核态切换到用户态就是进程的模式切换。
这个对应到问题上来说就是,当代码执行到nativePollOnce的时候进入了内核态,接下来的指令都是内核态在操作,直到内核态收到点击按钮的消息然后把消息传递给了用户态,这样进程恢复到了用户态,线程被唤醒,继续执行。
用户态到内核态之后,主线程阻塞,
内核态到用户态之后,主线程被唤醒继续执行。
接下来的问题是 进程从用户态到内核态,再从内核态到用户态,代码顺序执行就完事了,为什么还会有阻塞呢? 不阻塞会怎么样呢?
咱们就以消息循环为例:
① 如果我们不加阻塞的情况下,假如没有收到消息的话,用户态会不停的循环检查是否有新消息,CPU资源疲于奔命做了很多无谓的浪费
while(true){
if(内核有新消息变化){
处理;
}
}
② 如果我们加了阻塞的情况下,假如没有收到消息的话,用户态会阻塞,直到内核态有消息返回的时候才会唤醒主线程,这个时候CPU可以节省下来去做其他的事,阻塞算是对资源的最大合理化利用。
while(true){
int res = nativePollOnce();
if(res > 0){
处理;
}
}
那么内核态是怎么做到收到点击按钮消息之后能把消息传递给用户态的呢?
【并发】IO多路复用select/poll/epoll介绍 这个视频是我看过最通俗易懂的解释,大家可以看下会受益匪浅~
看完这个视频,大家肯定还是很奇怪,这个IO多路复用更像是服务端高性能处理多客户端请求的场景,和我们的Android Looper有什么关系呢?
其实我们可以把这个收消息的Looper想象成一个服务端,用户操作、GPU渲染、磁盘操作、网络请求等等和App的交互其实就等于再和Looper建立连接,这么一想是不是就能大概明白Android 中的 多路复用啦?
那么Android中的nativePollOnce方法是使用了这个IO多路复用了吗?
android_os_MessageQueue.cpp
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);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
// ... 省略
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
// ...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 没错这里就是使用了epoll 多路复用
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
}