Android 消息处理

来源:互联网 发布:口腔耗材淘宝哪家好 编辑:程序博客网 时间:2024/05/22 13:06

官方微信:动力节点Java学院 关注官方微信免费领取java视频教程

官方微博:动力节点

其实对于初学者来说Handler的使用很容易会很容易上手,但是对于其中的机制的理解,却不会那么简单,甚至1,2年经验的android开发也可能对其内部实现原理不是很了解。

其中我经过的认知的过程

  1. 初识Handler,认为没必要,直接执行方法不就好了么,为什么还要post出去,然后再在handleMessage中作处理,多此一举。

  2. 异步可使用Handler,在主线程更新ui

  3. 主线程中有个Looper不断循环,从MessageQueue中获取到Message,而Handler只是往MessageQueue中塞入Message

  4. 非主线程中也可以创建自己的Looper来处理消息,不过要自己调用Looper.prepare(),Looper.loop(),初始化和开始轮询。

  5. MessageQueue调用了jni层的方法来实现消息处理机制

笔者对整个android的消息处理机制的认知过程如上,经过的时间也很长,不是一蹴而就的。

今天就通过对其底层源码分析,和大家分享其内部实现原理,主要是第5步的认知,涉及到了jni层。这里主要介绍主线程中的Looper。

注意:android源码2.3.1

我们从头开始

frameworks/base/core/java/android/app/ActivityThread

...private Looper() {

我们这里以ActivityThread为入口来分析消息处理机制,每个app启动的时候,都会创建自己的ActivityThread,并且在main方法中也会创建主线程中的Looper。

这里如果对app整个启动流程不是特别清楚的话,建议先看下

http://www.jianshu.com/p/9da5bb46835c

frameworks/base/core/java/android/os/Looper

...private Looper() {

首先我们要理解 ThreadLocal 这个类的作用

http://www.cnblogs.com/alphablox/archive/2013/01/20/2869061.html

简单来说,就是为了实现每个线程中有对应一个Looper。

上面代码主要做了

  1. 创建Looper,MessageQueue

  2. 把Looper与当前主线程关联

mQueue其实就是MessageQueue,在Looper的构造方法中创建。

下面分析下MessageQueue。

frameworks/base/core/java/android/os/MessageQueue

...private native void nativeInit();

创建MessageQueue,其实主要调用了native方法。

其实在我们分析android源码的时候,要找到的对应的native方法,其实是一件很麻烦的事情,因为我们只知道方法名,并不知道对应的.c文件或者.cpp文件是哪个,这样就造成了我们阅读的障碍,这里有个比较笨的方法就是使用grep '**' -R . 来查询文件内容。但是毕竟android源码比较庞大,这样做其实还是很麻烦的,我这里暂时也没有更好的办法,主要还是参考了别人的文章,找到了对应的文件。

frameworks/base/core/jni/android_os_MessageQueue

...

简单地介绍下上面代码

  1. 注册对应的jni方法,如nativeInit.

  2. java层调用nativeInit后创建,NativeMessageQueue

  3. NativeMessageQueue中创建了Looper,如java层也是跟线程绑定,但是这个jni层的Looper,跟java层的 Looper没有关系。

  4. 利用JNIEnv方法给java 层中的MessageQueue对象的mPtr变量设置为NativeMessageQueue的地址。

下面是对jni层Looper的解析

frameworks/base/libs/utils/Looper

Looper::Looper(bool allowNonCallbacks) :

其实Looper的代码很多,这里我就贴出了关键的几行,但是涉及到的知识点却很多。

  1. pipe,创建读写管道

    http://www.cnblogs.com/kunhu/p/3608109.html

  2. epoll_create,管理io

    http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

其实我对这2者也并不是很熟悉。但是我知道以下几点,我认为就足够了。

  1. pipe创建读写2个通道

  2. epoll监听io

  3. epoll_wait会io阻塞,等到pipe管道中写入内容后,阻塞结束。

以上其实都是ActivityThread中调用Looper.prepareMainLooper之后的流程。主要是进行一些初始化,创建一些基础的对象。

下面我们来分析Looper.loop(),消息轮询。

frameworks/base/core/java/android/os/Looper

... public static final void loop() {

这段代码就是开启while死循环,不断从MessageQueue中获取Message,一看最重要的肯定是queue.next()获取Message这个方法。

继续跟入。

frameworks/base/core/java/android/os/MessageQueue

...

next方法去获取Message,最终还是调用nativePollOnce方法,传入的2个参数,这里介绍下。

  1. mPtr,是MessageQueue的一个变量,在前面其实已经介绍过了,其实是指向jni层的NativeMessageQueue的地址。

  2. nextPollTimeoutMillis其实是epoll_wait时,如果pipe中没有内容写入的等待时间,举个例子,如果nextPollTimeoutMillis为1000,那么这1秒中如果pipe中一直没有东西写入,那么,epoll_wait那么会阻塞1秒,然后继续运行,如果为0的话,epoll_wait就不会阻塞,如果为-1的话,如果pipe中没有东西写入,就会一直阻塞。

下面我们来看下nativePollOnce的具体实现。

frameworks/base/core/jni/android_os_MessageQueue

...void NativeMessageQueue::pollOnce(int timeoutMillis) {

其实在前面那一步就已经介绍了,ptr是NativeMessageQueue指针,在方法中强转为NativeMessageQueue对象后,调用他的pollOnce,

然后再调用NativeMessageQueue中的Looper的pollOnce方法

frameworks/base/libs/utils/Looper

...int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0; for (;;) { while (mResponseIndex < mResponses.size()) {
  1. epoll_wait阻塞,等待 timeoutMillis 毫秒或者pipe中有输入

  2. 如果有消息过来了,其实会先往pipe中输入,解除epoll_wait阻塞

  3. for循环eventCount,判断是否是当前线程的管道的fd,如果是,就调用awoken方法,read一下把东西读出来

其实到这里了,已经把大部分重要的代码贴出,下面我们来过一遍,走下整个流程,来梳理一下。

  1. Looper初始化,这里就不过多介绍了。

  2. 调用loop,不断从MessageQueue取数据。

    第一次调用时, nextPollTimeoutMillis=0 ,epoll_wait没有造成阻塞,马上运行到下面,由于刚开始,还没有消息过来eventCount=0 ,所以

    java层中。

    frameworks/base/core/java/android/os/MessageQueue

    ...final Message next() {

    调用 nativePollOnce 其实主要作用就是在jni层中epoll_wait等待pipe管道有新的输入,即有新的 Message。

刚刚开始,没有阻塞,所以 nativePollOnce 马上返回。

Message 其实是一个链表结构。

mMessages变量始终指向第一个Message.

由于刚开始还没有Message,所有mMessages = null

进入else,设置nextPollTimeoutMillis = -1;

由于还处于for之中,所以继续调用native方法 nativePollOnce ,

前面我已经介绍了,当nextPollTimeoutMillis=-1时,其实在epoll_wait会一直阻塞。

这时,我又不得不提到这篇优秀的文章

http://www.jianshu.com/p/9da5bb46835c

这里我并不清楚第一个Message消息是哪里传过来的,但是在App启动时Activity的生命周期中大部分都是利用handleMessage来处理的,姑且当作是第一个Message吧。

我们都知道是通过Handler来发送Message,由于Handler中发送 Message方法很多,但是最后都是调用 sendMessageAtTime

frameworks/base/core/java/android/os/Handler

...public boolean sendMessageAtTime(Message msg, long uptimeMillis)

这里的mQueue其实就是我们在Looper中创建的MessageQueue.

frameworks/base/core/java/android/os/MessageQueue

...final boolean enqueueMessage(Message msg, long when) {

enqueueMessage其实这个方法很重要,它对Message先进行了排序,要早运行的排在了前面,比较晚运行的排在队列后面。

前面介绍了mMessages其实是指向Message链表的第一个Message.

这时候来了个新的Message,由于是刚刚开始mMessage = null;

所以进入了if()中的语句。

这里有个变量需要特别解释, needWake ,顾名思义,是否需要唤醒。

就整篇文章而言,其实阻塞的就一个地方,就是epoll_wait,所以needWake指的需不需要唤醒,也就是指的这里。

mBlocked代表,当前的epoll_wait是否正在阻塞,如果是阻塞的,

mBlocked = true,所以needWake = mBlocked = true,也就是需要唤醒。

如果非阻塞那么,needWake = false.

最终是

if (needWake) { nativeWake(mPtr);

前面已经介绍了,由于nextPollTimeoutMillis=-1,epoll_wait正在阻塞,所以needWake = true;所以这个时候需要唤醒。

这里我就 不介绍跳转过程了,最终调用jni层的Looper中的wake方法

frameworks/base/libs/utils/Looper

...void Looper::wake() {...

前面其实我已经介绍过了epoll_wait有2种方式不再阻塞

  1. 等待传入的timeout参数时间到

  2. pipe管道中有新的输入

由于这个时候的timeout=-1代表一直阻塞,所以只能通过往pipe管道中写入东西,才能不阻塞epoll_wait.如上write一个内容。其实写的内容是什么并不重要,如上,其实就是写了一个W 字符。

这个时候,epoll_wait不再阻塞,继续往下运行。

frameworks/base/libs/utils/Looper

...int Looper::pollInner(int timeoutMillis) {

这个时候进入awoken方法,把写入内容读出。没有造成阻塞。

也就是java层中的MessageQueue中的next方法中的 nativePollOnce 没有造成阻塞,继续运行。

frameworks/base/core/java/android/os/MessageQueue

... final Message next() {

这时 nativePollOnce 阻塞结束,继续运行,if (now >= when) 是为了判断这个消息是否延迟,对应我们Handler中延迟发送的一些方法,如postDelay()等。这里直接认为没有延时。

在Message链表中把第一个Message移除返回。

最后调用Handler的handleMessage,这不是重点我就直接略过了。

等到Message都处理完了之后,获取到Message 为null。nextPollTimeoutMillis又被置为-1,然后又在epoll_wait中进行阻塞。

这里对延时发送Message的没有详细讲解,主要还是2点。

  1. 在添加Message的时候进行时间排序了,把Message插入到对应的位置。

  2. 主要还是利用nextPollTimeoutMillis,在epoll_wait的时候阻塞,等待。

这里我就不再详细赘述了。

问题

在分析源码的时候,我遇到了一个问题,整个消息机制,其实就是一个死循环,一直在执行,当没有消息的时候epoll_wait阻塞。

为什么epoll_wait在主线程阻塞不会造成卡顿,或者ANR?

Android 消息处理

我这里就不再过多赘述了。

0 0