前言
- 我们知道,android应用程序是支持多线程的。android应用程序线程有主线程和子线程之分,其中,主线程是由Activity管理服务ActivityManagerService请求Zygote进程创建的,而子线程是由主线程或者其他子线程创建的。android应用程序在启动完成之后,会主动进入一个消息循环中,即android应用程序主线程是具有消息循环的。
- android应用程序的主线程又称为UI线程,它是负责响应界面事件的。因此,我们要避免在它里面执行耗时操作,因为执行耗时的操作会导致主线程长时间不能响应界面事件,从而影响用户体验。我们一般把一个耗时的操作看作是一个后台任务,并且放在一个子线程中执行。在这些后台任务中,有的是一次性的,有的是需要不定期执行的。
- 对于不定期执行的后台任务来说,它们有两种主要方式。第一种方式就是每当条件满足的时候,就创建一个子线程来执行一个不定期的后台任务;而当这个不定期的任务执行完成之后,新创建的子线程就随之退出。第二种方式就是创建一个具有消息循环的子线程,每当条件满足的时候,就将一个不定期的后台任务封装成一个消息发送到这个子线程的消息队列中去执行,每当条件不满足时,这个子线程就会因为它的消息队列为空而进入睡眠等待状态。虽然第一种方式创建的子线程不需要具有消息循环,但是不断地创建和销毁子线程是有代价的,因此,我们更倾向于采用第二种方式来执行那些不定期的后台任务。从这个角度来看,我们就希望android应用程序子线程像主线程一样具有消息循环。
- 无论是对于一次性的后台任务来说,还是对于定期性的后台任务来说,它们在执行的过程中可能都会涉及到界面操作。例如,我们通常把一个下载任务放在一个子线程中来执行,而在下载过程中,一般需要将下载进度显示在应用程序的界面上。在这种情况下,用来执行它们的子线程一般就会将一个与界面相关的操作封装成一个消息,并且发送到主线程的消息队列中,以便在主线程中处理这个消息时,可以执行一个相关的页面操作。为了简化子线程执行与界面相关的操作,我们通常将它们与主线程的消息循环关联起来。从这个角度来讲,我们也可以说android应用程序子线程需要有消息循环。
- 综上所述,我们就可以得到android应用程序线程的三个消息循环模型。第一种是应用程序主线程消息循环模型。第二种是与界面无关的应用程序子线程消息循环的。第三种是与界面相关的应用程序子线程消息循环模型。我们可以使用ActivityThread、HandlerThread和AsnycTask这三个类来分别实现上述三种消息循环模型。
android消息机制原理
在android框架或者应用程序开发中,随处可见Handler类和Looper类的使用。android框架Java层核心服务运行在SystemServer进程,SystemServer进程由Zygote进程启动,当进入SystemServer进程后就会进入它的main()方法,在main()方法中会调用run()方法。
|
|
在这个方法中,首先通过调用Looper的prepareMainLooper()方法创建一个消息队列,然后调用loop()方法让进程的主线程进入消息循环。而对于应用程序而言,它的主类,也就是入口,是框架层的ActivityThread.java类。当应用程序被启动之后,就会进入ActivityThread.java的main()方法。
|
|
在ActivityThread的main()方法和SystemServer的run()方法里调用了相同的代码,即它们分别调用了Looper类的prepareMainLooper()方法和loop()方法来创建一个消息队列和消息循环。
因此,可以总结出,系统SystemServer进程的主线程通过循环不断地从这个消息队列中获取消息,然后对获取的消息进行处理,这样就实现了通过消息来驱动应用程序和SystemServer进程的执行,这就是android的消息处理机制。由此可见,android的消息机制由4个重要的类组成,分别是消息Message、消息队列MessageQueue、消息循环Looper、消息的发送者和处理者Handler。
在消息处理机制中,消息都存放在一个消息队列中,应用程序和SystemServer进程的主线程就是围绕这个消息队列进入一个无限循环中,直到应用程序和SystemServer进程退出。当应用程序和SystemServer的Handler向消息队列发送一个消息之后,消息队列就有消息了,这时候应用程序和SystemServer进程的主线程就会把它取出来,并分发给相应的Handler进行处理。如果队列中没有消息,应用程序的主线程就会进入空闲等待状态,等待下一个消息的到来。因此,android系统消息机制的核心是以下三个方面:
- 创建消息队列,之后进入无限循环读取消息
- 发送消息
- 处理消息,当发送消息之后,无限循环读到这个消息,之后就分发给相应的处理者来处理
创建消息队列和进入消息循环过程
创建消息队列和消息循环以ActivityThread中创建消息队列、消息循环为例,因为这是为每个应用程序主线程默认的消息循环和消息队列。
下面开始分析这个流程。
|
|
ActivityThread的main()方法首先创建一个ActivityThread实例thread,然后通过Looper.prepareMainLooper()创建一个消息队列,最后通过Looper.loop()进入消息循环。接下来看Looper的prepareMainLooper()方法和loop()方法创建消息队列和消息循环的过程。
|
|
prepareMainLooper()方法通过调用prepare()方法在线程中创建一个Looper对象,并且把创建的这个Looper对象保存在sThreadLocal变量中。在调用prepare()方法创建Looper对象的时候,传入的参数是false,这个false最后会传入到MessageQueue中去。当创建Looper对象的时候会调用它的构造方法来创建一个消息队列MessageQueue,并且赋值给Looper类的变量mQueue。而对于创建消息队列MessageQueue,就会进入到它的构造方法。
|
|
参数quitAllowed通过looper的构造函数来传递,值为false,把这个值赋给mQuitAllowed变量,如果它的值为true,就表示消息队列退出。接着MessageQueue的构造方法调用了nativeInit()方法,这是一个native方法。
|
|
变量gMessageQueueMethods是jni里一个方法注册表变量,包含了Java方法对应到CPP文件里的方法映射表,映射表里的内容是一一对应的关系,在Java调用native方法的时候,通过它在CPP文件里找到对应的方法。在这里,nativeInit()对应android_os_MessageQueue_nativeInit()方法,而在这个方法中通过new关键字又新创建了一个NativeMessageQueue消息队列,于是进入NativeMessageQueue的构造函数。
|
|
在NativeMessageQueue()函数中创建了一个native的looper对象,它与Java层中的Looper不一样,但是是一一对应的。通过看native的looper源代码,发现android消息机制的核心就在native的Looper中。下面是native的Looper对象创建过程。
|
|
在创建native的Looper对象的过程中,会进入native的Looper的构造函数,native的Looper的构造函数首先调用了Linux的eventfd()函数来创建一个文件描述符mWakeEventFd。这个文件描述符被用户空间当作一个事件等待/响应机制,靠内核去响应用户事件。而参数EFD_NONBLOCK类似O_NONBLOCK标志,用来设置文件描述符,主要是为了节省对fcntl的额外调用。最后,Looper的构造函数调用rebuildEpollLocked()函数来进一步操作。
|
|
这个函数就是android消息机制的核心,用了Linux中的epoll机制。
- epoll机制:它是一种I/O多路复用技术,是select、poll的加强版,使用epoll会涉及epoll_create()、epoll_ctl()、epoll_wait()这三个重要的函数
- epoll_create():创建epoll文件描述符
- epoll_ctl():设置监控文件描述符
- epoll_wait():等待监控的文件描述符发生IO事件
由于select/poll能监控的文件描述符的数量限制,一般情况是1024,并且随着文件描述符的增加,效率线性下降。而在android系统中,Looper要监控触摸事件、按键事件和键盘事件等文件描述符,由此可见,他监控的文件描述符非常多。epoll是为了大批量文件描述符而作改进的poll,使用它可以很好地避免效率低下的问题。
继续看Looper类的rebuildEpollLocked()函数,首先通过epoll_create()函数来创建一个epoll专用的文件描述符,EPOLL_SIZE_HINT = 8,表示在mEpollFd这个文件描述符上能监控的最大文件描述符数是8。接着调用epoll_ctl()函数来告诉epoll要监控mEpollFd文件描述符的是什么事件,在这里就是监控mWakeEventFd文件描述符的EPOLLIN事件,当mWakeEventFd有事件触发的时候,就唤醒当前正在等待的线程。而一般情况下有两个线程,一个线程是否有事件发生,如果没有,这个线程就进入等待状态。另外一个线程写一个事件,来唤醒等待的线程。
mWakeEventFd就是在上一步Looper构造函数中通过eventfd()创建的。Eventfd早期用来完成两个线程之间事件触发,现在已经支持两个线程之间的事件触发。它类似管道pipe,其实在android早期的版本中,就是使用管道pipe而不是Eventfd。
经过上述过程,消息队列的创建就完成了,它的核心是在native里使用了Linux的eventfd和epoll机制。
回到前面Looper.java类的loop()方法,它使应用程序进入了消息循环。
|
|
接下来是MessageQueue的next()方法从消息队列中获取下一个要处理消息的实现。
|
|
next()方法也是通过for循环不断获取msg,循环中nativePollOnce()是一个native方法,它查看当前消息队列中是否有消息,如果有消息就获得,如果没有消息就等待。
接下来看看nativePollOnce()方法的实现。
|
|
android_os_MessageQueue_nativePollOnce()函数首先得到NativeMessageQueue指针对象,然后调用它的pollOnce()函数来进一步操作。
|
|
NativeMessageQueue的pollOnce()函数中,变量mLooper是C++层的Looper的对象,它在创建消息队列时被创建,这里调用它的pollOnce()函数来进一步处理。
|
|
Looper的pollOnce()函数通过调用pollInner()函数来进一步操作。
|
|
下面是awoken()函数。
|
|
awoken()函数通过read()函数独处这个事件,这个事件在应用程序发送消息时写进。
消息的发送过程
当消息循环和消息队列创建好了之后,就可以往消息队列发送消息。在了解这个过程之前,先学习整个Handler的构成。
|
|
|
|
|
|
android_os_MessageQueue_nativeWake()函数首先得到NativeMessageQueue指针对象,然后调用它的wake()函数进一步操作。
|
|
NativeMessageQueue类的wake()函数,又通过C++层mLooper对象的wake()函数进一步操作。
|
|
Looper.cpp里的wake()函数通过调用TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)))实现。这句代码调用了一个write()函数,于是mWakeEventFd就发生了一个event事件,这时前面监听的epoll机制就起作用了,会唤醒应用程序的主线程,于是应用程序的主线程就会从前面C++层的Looper类的pollInner()函数一层层返回,最后返回到Java层的MessageQueue类的next()方法中。
消息的处理过程
当发送消息之后,C++层的Looper唤醒了应用程序的主线程,之后会一层层地返回,最后返回到MessageQueue的next()方法。next()方法是在Looper.java的loop()方法循环中被调用的,于是next()方法就会在loop()方法中返回。接下来就看看loop()方法怎样来进行消息的处理。
|
|
loop()方法通过queue的next()方法获得了消息msg,如果它不为空,就会调用target对象的dispatchMessage()方法来处理这个消息。接下来看看dispatchMessage()方法的具体实现。
|
|
如果没有对mCallback进行设置,就调用handleMessage()方法来进一步处理,而handleMessage()方法又被子类实现,也就是mHandler实现了,于是进入它的handleMessage()方法。
|
|
至此,android的消息机制就分析完毕。