Android应用程序消息处理机制笔记

看老罗的Android源码情景分析学习的时候,边抄边理解再总结.希望能为面试提供点帮助吧.

1.Android应用程序是通过消息来驱动,Android应用程序每一个线程在启动时,都可以首先在内部创建一个消息队列.然后再进入到一个无限循环中.不断检查它的消息队列是否有新的消息需要处理,如果有新的消息需要处理,那么线程将会将它从消息队列中取出来,并且对它进行处理,否则,线程就会进入睡眠等待状态,直到有新的消息处理为止.这样就可以通过消息来驱动Android应用程序的执行了.

2.Android应用程序的消息处理机制是围绕消息队列来实现的.一个线程拥有一个消息队列之后.就可以进入到一个消息循环中.同时,其他线程或线程本身可以往这个消息队列发送消息.以便可以在这个消息被处理时执行一个特定的操作.

这样,我们可以将一个线程的生命周期划分为两个阶段:创建消息队列和进入消息循环两个阶段

其中消息循环阶段又分为发送消息阶段和处理消息阶段.它们是交替进行的.

Android系统主要通过MessageQueue,Looper和Handler三个类来实现Android应用程序的消息处理机制.

其中

MessageQueue类 用来描述消息队列

Looper类  用来创建消息队列,以及进入消息循环

Handler类 用来发送消息和处理消息

接下来,我们来分析线程消息队列的创建过程,然后分析线程的消息循环过程,最后分析线程消息的发送和处理过程.

一.创建线程消息队列

Android应用程序线程的消息队列是用一个MessageQueue对象来描述的,它可以调用Looper类的静态成员函数prepareMainLooper或者prepare来创建,其中,前者用来为应用程序的主线程创建消息队列;而后者用来为应用程序的其他子线程创建消息队列

在分析Android应用程序线程的消息队列的创建过程之前,我们首先来介绍一下Looper类和MessageQueue类的实现.

由于Android应用程序的消息处理机制不仅可以在Java代码中使用,也可以在C++代码中使用,因此Android系统在C++层中也有一个响应的Looper类和NativeMessageQueue类.其中Java层中的Looper类和MessageQueue类是通过C++层中的Looper类和NativeMessageQueue类来实现的.

Java层中的每一个Looper对象内部都有一个类型为MessageQueue的成员变量mQueue,它指向了一个MessageQueue对象,而在C++层,每一个NativeMessageQueue对象内部都有一个类型为Looper的成员变量mLooper,它指向了一个C++层中的Looper对象.

Java层中的每一个MessageQueue对象都有一个类型为int的成员变量mPtr,它保存了c++层中的一个NativeMessageQueue对象的地址值.这样我们就可以将Java层中的一个MessageQueue对象与C++层中的一个NativeMessageQueue对象关联起来.

Java层中的每一个MessageQueue对象还有一个类型为Message的成员变量mMessage,它用来描述一个消息队列,我们可以调用MessageQueue类的成员函数enqueueMessage来往里面添加一个消息.

C++层中的Looper对象有两个类型为int的成员变量mWakeReadPipeFd和mWakeWritePipeFd,它们分别用来描述一个管道的读端文件描述符和写端文件描述符.当一个线程的消息队列没有消息需要处理时,它就会在这个管道的读端文件描述符上进行睡眠等待.直到其他线程通过这个管道的写端文件描述符来唤醒它为止.

  当我们调用Java层的的Looper类的静态成员函数prepareMainLooper或者prepare来为线程创建一个消息队列的时,Java层的Looper类就会在这个线程中创建一个Looper对象和一个MessageQueue对象.在创建Java层的MessageQueue对象过程中,又会调用它的成员函数nativeInit在C++层中创建一个NativeMessageQueue对象和一个Looper对象.,在创建C++层的Looper对象时,又会创建一个管道,这个管道的读端文件描述符和写端文件描述符就保存在它的成员变量mWakeReadPipeFd和mWakeWritePipeFd中.

   Java层中的Looper类的成员函数loop,MessageQueue类的成员函数nativePollOnce和nativeWake,以及c++层中的NativeMessageQueue类的成员函数poolOnce和wake,是与线程的消息循环,消息发送和消息处理相关的.后面会继续分析.

可参考下图

wps49CA.tmp

理解了这些背景知识,接下来我们就可以分析android应用程序线程的消息队列的创建过程,即分析Looper类的静态成员函数prepareMainLooper和prepare的实现.

Looper类的静态成员函数prepareMainLooper和prepare的实现.请自行参阅android.os.looper.

Looper类有一个类型为ThreadLocal的静态变量sThreadLocal [static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();],它是用来保存线程中Looper对象的.我们可以将Looper类的静态成员变量sThreadLocal理解为一个线程局部变量,或者一个HashMap,每一个创建了消息队列的Android应用程序线程在里面都有一个关联的Looper对象.

在Looper类的静态成员函数prepare中,第12行的if语句检查当前线程是否已经有一个Looper对象,如果有,那么第13行就会抛出一个异常;否则,第15行代码就会首先创建一个Looper对象,然后将这个Looper对象保存在Looper类的静态成员变量sThreadLocal中.

public static final void prepare() {

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

        }

sThreadLocal.set(new Looper());

    }

在Looper类的静态成员函数prepareMainLooper中,第19行代码首先调用Looper类的静态成员函数prepare在当前线程中创建一个Looper对象,接着在第20行代码调用Looper类的静态成员函数setMainLooper将这个Looper对象保存在Looper类的静态成员变量mMainLooper中.

public static void prepareMainLooper() {

prepare(false);

synchronized (Looper.class) {

if (sMainLooper != null) {

throw new IllegalStateException("The main Looper has already been prepared.");

            }

sMainLooper = myLooper();

        }

    }

Looper类的静态成员函数prepareMainLooper只能在Android应用的主线程当中调用.Android应用程序主线程是一个特殊的线程,因为只有它才可以执行与UI相关的操作.因此我们又将它称之为UI线程,将Android应用程序主线程的Looper对象另外保存在一个独立的静态成员变量中,是为了让其他线程可以通过Looper类的静态成员函数getMainLooper来访问它,从而可以往它的消息队列中发送一些与UI操作相关的消息.

一个Looper对象在创建的过程中,会在内部创建一个MessageQueue对象,并且保存在它的成员变量mQueue中.

private Looper() {

mQueue = new MessageQueue();

mRun = true;

mThread = Thread.currentThread();

    }

一个MessageQueue对象在创建的过程中,又会在C++层中创建一个NativeMessageQueue对象,这是通过调用MessageQueue类的成员函数nativeInit来实现+

MessageQueue() {

nativeInit();

    }

MessageQueue类的成员函数nativeInit是一个JNI方法,它是由C++层中的函数android_os_MessageQueue_nativeInit来实现的

static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {

    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();//创建一个NativeMessageQueue对象

    if (! nativeMessageQueue) {

        jniThrowRuntimeException(env, "Unable to allocate native queue");

        return;

    }

    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);

//调用 android_os_MessageQueue_setNativeMessageQueue函数将它与参数obj所描述的一个java层的MessageQueue对象关联起来.

}

其中android_os_MessageQueue_setNativeMessageQueue的方法如下

static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,

        NativeMessageQueue* nativeMessageQueue) {

    env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,

             reinterpret_cast<jint>(nativeMessageQueue));

}

其中 gMessageQueueClassInfo是一个全局变量,定义如下

wps49DA.tmp

它的成员变量clazz指向了Java层的的MessageQueue类.成员变量mPtr指向了MessageQueue类成员变量mPtr.

从全局变量gMessageQueueClassInfo的定义可以看出,函数android_os_MessageQueue_setNativeMessageQueue实际上是将一个C++层的NativeMessageQueue对象的值保存在一个Java层的MessageQueue对象的成员变量mPtr中,从而可以将它们关联起来.

一个NativeMessageQueue对象在创建的过程中,又会在内部创建一个C++层的Looper对象,如下所示.

wps49DB.tmp

该代码块首先调用c++层的Looper类的静态成员函数getForThread来检查是否已经为当前线程创建过一个C++层的Looper对象,如果没有创建过,那么接着首先创建一个C++层的Looper对象,并且将它保存在NativeMessageQueue类成员变量mLooper中,然后再调用C++层的Looper类的静态成员函数setForThread将它与当前线程关联起来.

一个C++层的Looper对象在创建过程中,又会在内部创建一个管道

wps49EC.tmp

首先调用函数pipe创建一个管道,接着分别将这个管道的读端文件描述符和写端文件描述符保存在C++层中的Looper类的成员变量mWakeReadPipFd和mWakeWritePipeFd中.

这个管道在一个线程的消息循环过程中起到作用非常大,首先当一个线程没有新的消息需要处理时,它就会睡眠在这个管道的读端文件描述符上,直到有新的消息需要处理为止.其次,当其他线程向这个线程的消息队列发送了一个消息之后,其他线程就会通过这个管道的写端文件描述符往这个管道写一个数据,从而将这个线程唤醒,以便它可以对刚才发送到它的消息队列中的消息进行处理.

wps49FC.tmp

然后调用epoll_create来创建一个epoll实例,并且将它的文件描述符保存在C++层的Looper类的成员变量mEpollFd中

,接着将前面所创建的管道的读端文件描述符添加到这个epoll实例中,以便可以对它所描述的管道的写操作进行监听

Linux系统的epoll机制是为了同时监听多个文件描述符的IO读写事件而设计的.它是一个多路复用的IO接口,类似于Linux系统的select机制,但是它是select机制的增强版,如果一个epoll实例监听了大量的文件描述符io读写事件,但是只有少量的文件描述符是活跃的,即只有少量的文件描述符会发生IO读写事件,那么这个epoll实例可以显著的减少CPU的使用率,从而提高系统的并发能力.

这里可能觉得奇怪的地方就是,前面所创建的epoll实例只监听了一个文件描述符的IO写事件,即前面所创建的管道的IO写事件,这值得epoll机制来实现吗?其实,以后我们还可以调用C++层的Looper类的成员函数addFd向这个epoll实例中注册更多的文件描述符,以便可以监听他们的IO读写事件.这样就可以充分的利用一个线程的消息循环来做其他事情.例如键盘消息处理机制时,我们就会看到C++层的Looper类的成员函数addFd的场景.

至此,一个Android应用程序线程的消息队列就创建完成了.接下来,我们再分析这个Android应用程序线程是如何围绕这个消息队列来进行循环的.

二.线程消息循环过程

三.线程消息发送过程

四.线程消息处理过程

总结:面试中如何阐述这一机制(自己总结)

面试中自认为或许可以用来加分的口语式总结性概括:

思路:消息机制概述-->创建消息的基本过程

Android的应用程序通过这个消息处理机制来驱动,也就是说在每一个线程在启动的时候,都会在内部先创建一个消息队列,然后进入到无限循环,不断检查这个消息队列有没有消息,如果有,就把消息取出来做想相应的处理.没有就进入睡眠等待,直到有这个新的消息来为止.这样就可以通过消息来驱动应用程序的执行了.

Android系统中主要是通过MessageQueue,Looper和Handler这个三个类来实现Android应用程序的消息处理机制的.MessageQueue就是描述消息队列的,Looper是用来创建消息队列,以及进入这个消息循环.Handler是用来发送消息和处理消息的.

那么它这个消息处理机制呢,有四大过程,第一个呢是创建线程消息队列的过程,第二个呢是这个线程消息循环的过程,第三个呢是消息发送的过程,第四个呢就是消息处理的一个过程.

备用谈资:过程四选一,猜测毕竟面试时间有限,还有其他方面准备的要说,或许也有可能面试官不问或者没能引导到这块.

四选一开头语样式:你就说这个,消息创建的过程吧,它的过程是这样的.

消息创建的过程:

它的过程呢是这的.首先呢要知道他实际上是跟这个底层C++是有联动的.Java层的Looper和MessageQueue实际上是通过C++层中Looper类和NativeMessageQueue类来实现的.

当我们调用这个Java层的Looper类的静态成员函数prepareMainLooper或者prepare来为一个线程创建一个消息队列时,Java层的Looper类就会在这个线程创建一个Looper对象和一个MessageQueue对象.在创建Java层的MessageQueue对象的过程中呢又会调用成员函数nativeInit在C++层中创建一个NativeMessageQueue对象和一个Looper对象.在创建C++层的Looper对象时候,又会创建一个管道,这个管道读写端文件描述符保存在mWakeReadPipeFd和mWakeWritePipeFd中,当一个线程的消息队列没有消息需要处理的时候,它就会在这个管道读端文件描述符上进行睡眠等待,直到其他线程通过这个管道的写端描述符来唤醒为止.

posted @ 2015-07-05 15:22  miosec  阅读(650)  评论(0编辑  收藏  举报