Android消息分发机制要点总结

几个月前用安卓写代码时总结的一些东西,现在发出来充充数,以后要多写写博客了。

概述

本文主要是针对Android消息分发过程中的几个要点:

  • 消息队列messageQueue构造
  • 主线程Looper的Loop过程
  • Activity怎么启动主Loop过程
  • messageQueue.next()函数的具体过程
  • UI分层结构,不同UI层次间的关系消息分发的层次

ActivityThread的启动过程

Android消息分发机制要点总结_第1张图片
一个新的ActivityThread的创立,标志着一个新的应用被打开,而在安卓系统中,所有的应用都是以Activity的形式表示,桌面是一个Activity,导航栏是一个Activity,新打开的应用也是一个Activity。
每个应用都有一个自己的进程空间,应用进程通过vegote进行fork创建并启动我们指定的MainActivity,然后应用进程的主线程开始执行自己的main函数,初始化looper、messageQueue,并绑定Handler,然后进入loop循环开始等待消息的传入并处理。
根据不同的消息,主线程可能会通过Handler的不同处理,通知不同的Activity进入不同的状态,或者终结一个Activity的生命周期,或者仅仅只是将点击事件分发给不同的view让其自行执行回调。
至少从主线程循环的角度看来,安卓应用是一种消息驱动的应用模型。

消息传递结构

MessageQueue 消息队列

其中Message mMessages记录的就是一条消息链表,而Looper的loop函数实质就是从MessageQueue中取出消息然后进行处理的过程。
另外在MessageQueue中还有几个native函数,这就说明MessageQueue会通过JNI技术调用到底层代码。mMessages域记录着消息队列中所有Java层的实质消息。当然,记录的只是Java层的消息,不包括C++层的。
MessageQueue的示意图如下:
Android消息分发机制要点总结_第2张图片

MessageQueue.next()——nativePollOnce()函数

MessageQueue.next()函数中中调用的nativePollOnce()调用到了C++层,这个函数起到了阻塞线程的作用,保证消息循环不会在无消息处理时一直在那里“傻转”,而通过native层的Looper实现可以很好的看到nativePollOnce函数是如何被阻塞,又是在何时被何种方式唤醒的。

C++层的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);
    LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s", strerror(errno));
    AutoMutex _l(mLock);
        // Close old epoll instance if we have one.
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        close(mEpollFd);
    }
    // Allocate the new epoll instance and register the wake pipe.
    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)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",strerror(errno));
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",request.fd, strerror(errno));
        }
    }
}

在C++层中,可以看到在构造Looper对象时,通过eventfd函数创建了一个文件描述符,而eventfd创建的文件描述符最主要的作用就是信号通知,相比起之前版本消息通知使用的pipe机制,eventfd的开销更低且使用更少的文件描述符即可实现。
之后Looper通过epoll机制对eventfd的读写操作进行监控,这样就使得上层的loop循环会在MessageQueue.next()时阻塞在nativePollOnce函数上,在收到其他线程发来的消息之后被唤醒进行消息的处理。
每当我们向消息队列发送事件时,最终会间接向eventfd文件描述符上进行写操作,于是epoll通过监听获取到了这个事件,epoll_wait()在等到事件后,随即进行相应的事件处理。这就是消息循环阻塞并处理的大体流程。
以下是Loop函数的从上到下的流程图:
Android消息分发机制要点总结_第3张图片
pollInner()调用epoll_wait()时传入的timeoutMillis参数,其实来自于前文所说的MessageQueue的next()函数里的nextPollTimeoutMillis,next()函数里在以下3种情况下,会给nextPollTimeoutMillis赋不同的值:

  1. 如果消息队列中的下一条消息还要等一段时间才到时的话,那么nextPollTimeoutMillis赋值为Math.min(msg.when - now, Integer.MAX_VALUE),即时间差;
  2. 如果消息队列已经是空队列了,那么nextPollTimeoutMillis赋值为-1;
  3. 不管前两种情况下是否已给nextPollTimeoutMillis赋过值了,只要队列中有Idle Handler需要处理,那么在处理完所有Idle Handler之后,会强制将nextPollTimeoutMillis赋值为0。这主要是考虑到在处理Idle Handler时,不知道会耗时多少,而在此期间消息队列的“到时情况”有可能已发生改变。

不管epoll_wait()的超时阀值被设置成什么,只要程序从epoll_wait()中返回,系统就会尝试处理等到的epoll事件。目前我们的主要关心点是事件机制,所以主要讨论当fd == mWakeEventFd时的情况,此时会调用一下awoken()函数。
之后,会挨个调用handler的handleMessage函数去处理每一个native层发过来的消息,并依次调用每个response的回调函数去处理事件。
而在这之后将会经由native层传入上方的java层,将消息交给java层的Looper和handler进行处理和分发。
C++层的Looper及这个层次的消息链表,再加上对应其他fd的Request和Response,可以形成下面这张示意图:Android消息分发机制要点总结_第4张图片

Window、Activity、DecorView以及ViewRoot之间的关系

Android消息分发机制要点总结_第5张图片

  • Activity
    Activity并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是Window。一个Activity包含了一个Window,Window才是真正代表一个窗口。Activity就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window、以及View进行交互。
  • Window
    Window是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow。PhoneWindow中有个内部类DecorView,通过创建DecorView来加载Activity中设置的布局R.layout.activity_main。Window 通过WindowManager将DecorView加载其中,并将DecorView交给ViewRoot,进行视图绘制以及其他交互。
  • DecorView
    DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏。

Android的UI分层和事件分发顺序

在收到其他进程发来的消息的时候,首先接到消息的是和其对应的拥有其Binder的线程,收到消息的线程将消息通过Handler发给主线程的消息队列,主线程再通过不同的接收Handler进行消息的处理。
比如点击事件的传递,其传播模型如上,消息的分发从上到下的逻辑也大致如此。
在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联,可以参考一下代码,在ActvityThread中,也就是ViewRootImpl是DecorView的父元素,但是ViewRootImpl并不是View。ViewRootImpl接收native层的消息,并向DecorView分发事件
这里的事件不仅仅包括MotionEvent,还有KeyEvent。我们知道View的时间分发顺序为Activity——>Window——>View,那么Activity的事件来源在哪里呢?这是个需要思考的问题,答案和ViewRootImpl有很大的关系。
首先,事件的根本来源来自于Native层的嵌入式硬件,然后会经过InputEventReceiver接受事件,然后交给ViewRootImpl,将事件传递给DecorView,DecorView再交给PhoneWindow,PhoneWindow再交给Activity。

Android消息分发机制要点总结_第6张图片

你可能感兴趣的:(安卓)