Handler底层机制

作者: Albert0211 | 来源:发表于2020-03-02 18:49 被阅读0次
    0.jpg

     安卓的底层是Linux系统,了解Android Handler底层之前,需要了解一下Linux 知识。

    • 文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。
    • epoll:IO多路复用机制,可以理解为event poll,不同于忙轮询和无差别轮询,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作)。
       每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。epoll都是使用waitqueue调用callback函数去wakeup你的异步等待线程的,如果设置了timeout的话就起一个timer,epoll的waitqueue callback函数把当前的有效fd加到ready list,然后唤醒异步等待进程,所以epoll函数返回的就是这个ready list。ready list中包含所有有效的fd,所以kernel不用去遍历所有的fd,用户空间程序也不用遍历所有的fd,而只是遍历返回有效fd链表。

    • pipe:管道,使用I/O流操作,Linux 系统中的一种进程间通信机制。简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件的文件描述符。这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容。当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容。写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。在管道机制的实现中,又使用 epoll 机制来监听读写事件。

    归入主题,handler底层是怎么运作的呢?

    MessageQueue的底层实现是利用管道和epoll机制来实现的。

    pipe.png

     MessageQueue 在构造方法中,会调用 native 方法 nativeInit 方法,在NativeMessageQueue 的构造方法中,会构造一个 JNI 层的 Looper。Looper.loop()有个for死循环,它调用了MessageQueue下的next方法。


    looper1.png looper2.png

     如上图,nextPollTimeoutMillis这个变量,这个变量代表MessageQueue下次被唤醒的时间。MessageQueue里Message在加入队列的时候,会按照执行的时间顺序排列;每次消息入队列时,MessageQueue都会尽量计算出一个精确的时间。假如这个时间是计算出来是1000ms,如果消息队列中没有消息需要马上处理时,会判断用户是否设置了Idle Handler。如果有的话,则会尝试处理mIdleHandlers中所记录的所有Idle Handler。此时会逐个调用这些Idle Handler的queueIdle()成员函数,再次调用nativePollOnce()方法,线程阻塞住,不占用资源。当时间到了,会往管道流中写入字节流,唤醒线程,处理Message。

    Looper队列的阻塞唤醒的功能是怎么实现的?

     MessageQueue 是按照消息触发时间的先后顺序排列的,队列头部的消息是最早触发的。当有消息加入,会从队列头部开始遍历,插入到合适的位置,以保证所有消息的时间顺序。如果当前线程处于空闲等待状态,需要调用 nativeWake 来唤醒。

    static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) {
        // ptr 获取 NativeMessageQueue
        NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);  
        return nativeMessageQueue->wake();  
    }
    

    唤醒请求转发到 Looper wake,往管道写入内容,从而唤醒线程。

    void Looper::wake() {  
        ......  
      
        ssize_t nWrite;  
        do {
            nWrite = write(mWakeWritePipeFd, "W", 1);  // 先管道中写入 "W
        } while (nWrite == -1 && errno == EINTR);  
      
        .......  
    }
    

    当消息队列中没有消息处理时,线程会进入空闲等待状态,具体是通过 Looper 调用 epoll_wait。
    附图,


    wake.png
    • 总结:
       线程在进入循环之前,会在 JNI 创建管道,当消息队列为空时,线程处于空闲等待状态。通过 epoll 机制监听 EPOLLIN 事件,当有新事件进入消息队列时,并且当前线程处于空闲状态,通过向管道写入数据,来唤醒线程。

    相关文章

      网友评论

        本文标题:Handler底层机制

        本文链接:https://www.haomeiwen.com/subject/yaqbkhtx.html