深刻理解 Android 消息机制原理

欢迎你们前往腾讯云社区,获取更多腾讯海量技术实践干货哦~android

做者:汪毅雄bash

导语: 本文讲述的是Android的消息机制原理,从Java到Native代码进行了梳理,并结合其中使用到的Epoll模型予以介绍。服务器


Android的消息传递,是系统的核心功能,对于如何使用相信你们都已经至关熟悉了,这里简单提一句。咱们能够粗糙的认为消息机制中关键的几个类的功能以下:ide

Handler:消息处理者oop

Looper:消息调度者post

MessageQueue:存放消息的地方学习

使用过程:ui

Looper.prepare > #$%^^& > Looper.loop(死循环) --- loop到一个消息 > Handler处理this

好了,咱们直接看源码吧。spa

Java层

消息机制是伴随线程的,也就是说上面的几个类在能够在任何一个线程中都有实例的。

先看Looper吧。以主线程为例,Android进程在初始化,会调用prepareMainLooper

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            ...
            sMainLooper = myLooper();
        }
    }复制代码


private static void prepare(boolean quitAllowed) {
        ...
        sThreadLocal.set(new Looper(quitAllowed));
    }复制代码


private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }复制代码


以上几个方法就是Looper初始化,若是是主线程Looper会建立一个不可退出的MessageQueue,并把looper实例放入线程独立(ThreadLocal)变量中。

Looper#loop

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
            ...
            try {
                msg.target.dispatchMessage(msg);
            } 
            ...
            msg.recycleUnchecked();
        }
    }复制代码


Looper prepare后就能够loop了,loop很是简单,一直去queue中拿消息就行了,拿到了交给target也就是Handler处理。你们有可能会奇怪这种死循环,执行起来不会太sb粗暴了吗?其实这个解决方式在queue.next!!!后面再讲。

Handler#dispatchMessage

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }复制代码


handler收到后,若是发现message的callback不为空,则只处理callback。(提一句,咱们用的不少的handler.post(Runnable),其实这个Runnable就是这里的callback,也就是说post的Runnable实质上是一个优先级很高的Message),若是没有则尝试交给handler自己的callback处理(handler初始化的时候能够用callback方式构造),再没有才到咱们经常使用的handleMessage方法,这里就是咱们常常重写的方法。

再说说消息的发送,通常handler会调用sendMessage方法,可是最终这个方法仍是会跑到这里

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }复制代码


再交给MessageQueue

boolean enqueueMessage(Message msg, long when) {
        。。。
        synchronized (this) {
            。。。
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    。。。
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            if (needWake){
               nativeWake(mPtr);
            }
        }
        return true;
    }复制代码


MessageQueue会把消息插入队列,并依次改变队列中各个消息的指针。

咦,好像只用Java层貌似就能把整个消息机制说通了,native代码在哪儿?有何用呢?

可是,刚才提到了Looper初始化的时候也会新建一个MessageQueue

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }复制代码


好了,咱们第一个native方法出来了。这时候咱们能够猜获得,MessageQueue才是整个消息机制的核心!

Native层

接上面Java层的代码,MessageQueue构造的时候会调一个nativeInit。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    。。。
}复制代码


NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}复制代码


native层调用init方法后,会在native层构建一个native Looper!来看看native Looper的初始化

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    ...
    rebuildEpollLocked();
}复制代码


这里建立了一个eventfd,代码来自最新的8.0,这部分和5.0 pipe管道的mWakeReadPipeFd和mWakeWritePipeFd稍微有点不同,前者是等待/响应,后者是读取/写入。只是android选取方式的不一样而已,这块就不细说。

void Looper::rebuildEpollLocked() {
    。。。
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); 
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    。。。
}复制代码


再到rebuildEpollLocked这个方法中,能够看到经过epoll_create建立了一个epoll专用的文件描述符,EPOLL_SIZE_HINT表示mEpollFd上能监控的最大文件描述符数。最后调用epoll_ctl监控mWakeEventFd文件描述符的Epoll事件,即当mWakeEventFd中有内容可读时,就唤醒当前正在等待的线程.。

这里不了解的人可能听着晕,上面这么一大段一句话归纳就是:Android native层用了Epoll模型。什么是Epoll模型呢?我先简单介绍一下。

Epoll(必看!!!)

为何要引入呢?

在Looper.loop的时候提到了,android不会简单粗暴地真的执行啥都没干的死循环。刚才说了,问题出在queue.next。Epoll干的事就是: 若是你的queue中没有消息可执行了,好了你能够歇着了,等有消息的我再告诉你。这个queue.next就是“阻塞”(休眠)在这里。

Epoll简单介绍

一、传统的阻塞型I/O(一边写,一边读),一个线程只能处理一个一个IO流。

二、若是一个线程想要处理多个流,能够采用了非阻塞、轮询I/O方式,可是传统的非阻塞处理多个流的时候,会遍历全部流,可是若是全部流都没数据,就会白白浪费CPU。
。。。
因而出现了select和epoll两种常见的代理方式。

三、select就是那种无差异轮询的代理方式。epoll能够理解为Event poll,也就是说代理者会代理流的时候也伴随着事件,所以有了对应事件,就能够避免无差异轮询了。

四、其一般的操做有:epoll_create(建立一个epoll)、epoll_ctl(往epoll中增长/删除某一个流的某一个事件)、epoll_wait(在必定时间内等待事件的发生)

eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);复制代码


好了,咱们结合Looper初始化的代码来读一下epoll在这里干了什么吧。

我直接翻译了:往mEpollFd代理中、注册、一个叫mWakeEventFd流、的数据流入事件(EPOLLIN)

这样你们应该懂了吧。。。

接上MessageQueue在初始化后,在native建立了一个Looper。
咱们继续消息的发送和提取在native层的表现。其实native层主要负责的是消息的调度,好比说什么时候阻塞、什么时候唤醒线程,避免CPU浪费。

native发送

发送在native比较简单,handler发送消息后,会到MessageQueue的enqueueMessage,此时在线程阻塞的状况下,会调用nativeWake来唤起线程。

void NativeMessageQueue::wake() {
    mLooper->wake();
}复制代码


void Looper::wake() {
    。。。
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
      。。。。
    }
}复制代码


这里TEMP_FAILURE_RETRY是一个宏定义,顾名思义,就是不断地尝试往mWakeEventFd流里面写一个无用数据直到成功,以此来唤醒queue.next。这部分就很少说了。

native消息提取

也就是queue.next

Message next() {
        。。。
        for (;;) {
           。。。
            nativePollOnce(ptr, nextPollTimeoutMillis);
           。。。
        }
    }复制代码


能够看到,又是一个死循环(阻塞)。继续往下看

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    。。。
    mLooper->pollOnce(timeoutMillis);
    。。。
}复制代码


mLooper->pollOnce

mLooper->pollInner

int Looper::pollInner(int timeoutMillis) {
    。。。
    int result = POLL_WAKE;
    mPolling = true;
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    mPolling = false;
    mLock.lock();

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } 
        。。。
    }
    。。。
    mLock.unlock();
    。。。
    return result;
}复制代码


在这里,咱们注意到epoll_wait方法,这里会获得一段时间内(结合消息计算得来的)收到的事件个数,这里对于queue来讲就是空闲(阻塞)状态。过了这个时间后,看看事件数,若是为0,则意味着超时。不然,遍历全部的事件,看看有没有mWakeEventFd,且是EPOLLIN事件的,有的话就真正唤醒线程、解除空闲状态。

消息机制在native层的主要表现就是这些。

最后,画了一个粗糙、且不太准确图仅供参考学习

相关阅读

此文已由做者受权腾讯云技术社区发布,转载请注明原文出处

相关文章
相关标签/搜索