前言
Android和Windows一样都采用了消息机制,从开发角度说,Handler
是Android消息机制的上层接口,开发过程中和Handler
交互即可。Android规范限制我们不能在子线程中更新UI,只能在主线程中更新UI,Handler可以轻松的将一个任务切换到Handler所在的线程中去执行。所以,开发过程中Handler通常被用来更新UI,利用Handler将更新UI的操作切换到主线程中执行。
ViewRootImpl
对我们的UI操作进行了验证,具体是在checkThread()
方法中完成的,如下:
|
|
ViewRootImpl
是所有View
的根,Window
和View
通过ViewRootImpl
建立联系,checkThread()
方法校验当前线程是否是ViewRootImpl
被创建时所在的线程。具体请参考Android中子线程真的不能更新UI吗?
说明:本文中的源码都是基于Android-25版本。
Handler
Android的消息机制主要是指Handler的运行机制,Handler的运行需要MessageQueue
和Looper
支撑,MessageQueue是消息队列,内部存储了一组消息,而Looper则是消息循环,Looper会无限循环查看是否有新消息,如果有的话处理消息,没有的话就一直等待。
Looper中有一个特殊概念ThreadLocal
,ThreadLocal
并不是线程,作用是在每个线程中存储数据,可以让不同线程保存在同一个ThreadLocal
中的对象数据互不干扰。ThreadLocal
的工作原理请参考:Android的消息机制之ThreadLocal的工作原理
工作过程
Handler在创建过程时会采用当前线程的Looper来构造消息循环系统,那么Handler内部是如何获取到Looper呢?通过ThreadLocal
可以很轻松的获取每个线程的Looper,但是线程默认是没有Looper的,要使用Handler就必须为线程创建Looper。而主线程(UI)线程即ActivityThread
被创建时就初始化了Looper,所以我们在主线程中可以默认使用Looper。
Handler的默认构造方法最终会通过以下构造方法实现:
从中可以看到,如果当前线程没有Looper,则会抛出异常,而mLooper
是由Looper.myLooper()
方法返回的,代码如下:
|
|
Looper.myLooper()
就是返回了保存在ThreadLocal
中的Looper对象。
Handler创建完毕后,Handler和Looper以及MessageQueue便协同工作,构成一个消息系统。通过Handler的post
系列方法将一个Runnable
投递到Looper,或者send
系列方法发送一个消息到Looper中,通过调用MessageQueue的enqueueMessage()
方法将消息放入消息队列中,此时Looper发现有新消息到来,处理此消息。最终消息中的Runnable
或者Handler的handleMessage()
方法就会被调用,这样就又将任务切换到创建Handler的线程中去了。过程如图所示:
工作原理
Handler的工作主要包括发送和接收消息。
发送消息
上面提到,发送消息是通过一系列post或者send方法实现,post系列的方法最终都是通过send系列方法实现的。Handler中有关发送消息的send
系列方法源码如下:
|
|
可以看到除了sendMessageAtFrontOfQueue()
方法,其余最终都是调用sendMessageAtTime()
方法,在sendMessageAtTime()
方法中调用enqueueMessage()
方法,再调用MessageQueue
的enqueueMessage()
方法,发送消息就是向消息队列中插入一条消息,MessageQueue的next()
方法将这条消息返回给Looper。
处理消息
Looper接收到消息后处理,并最终交由Handler的dispatchMessage()
处理,源码如下:
首先检查Message
的callback
,不为null则调用handleCallback()
,源码如下:
Message
的callback
是一个Runnable
对象,就是Handler中post方法的Runnable
参数。post方法源码如下:
post
方法利用sendMessageDelayed()
方法发送消息,其中又调用了getPostMessage()
方法,其源码如下:
|
|
该方法传入一个Runnable
参数,得到一个Message
对象,将Runnable
参数赋值给Message对象的callback
字段。
然后检查mCallback
,不为null则调用mCallback
的handleMessage()
,Callback
是一个接口,定义如下:
|
|
使用Callback
创建Handler不需要派生一个Handler子类,通常开发过程中都是派生Handler子类并重写其handleMessage()
方法来处理具体消息,Callback
提供了另一种使用Handler方法。
最后不符合以上两点时,就调用Handler的handleMessage()
来处理消息。
Looper
Looper在Android消息机制中扮演消息循环的角色,它会不断从MessageQueue中查看是否有新消息,如果有新消息就立即处理,否则一直阻塞在那里。
创建Looper
首先看一下构造方法,在构造方法中它会创建一个MessageQueue消息队列,然后将当前线程的对象保存起来。
|
|
Handler的工作需要Looper,没有Looper的线程就会报错,那么如何为线程创建Looper呢?通过Looper.prepare()
即可为当前线程创建一个Looper,同时会将Looper保存在ThreadLocal
中,Handler的构造函数便是从ThreadLocal
中获取Looper
对象,从代码如下:
|
|
接着通过Looper.loop()
来开启消息循环。为一个子线程创建Looper和Handler的代码如下所示:
|
|
Looper除了prepare()
方法外,还提供了prepareMainLooper()
方法,这个方法主要是给主线程即ActivityThread
创建Looper使用的,本质也是通过prepare()
方法来实现的,源码如下:
|
|
在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()
方法。ActivityThread
中的main()
方法调用了Looper.prepareMainLooper()
方法,而这个方法又会再去调用Looper.prepare()
方法。因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()
方法了。
由于主线程Looper比较特殊,所以Looper提供了一个getMainLooper()
方法,通过它可以在任何地方获取主线程的Looper。
Looper退出
Looper也提供了退出方法,quit()
和quitSafely()
,区别在于quit()
是直接退出Looper,而quitSafely()
只是设定一个退出标记,然后把消息队列中的已有消息全部处理完毕后再退出。quit()
方法最终调用的是MessageQueue
中的removeAllMessagesLocked()
方法,quitSafely()
最终调用的是MessageQueue
中的removeAllFutureMessagesLocked()
方法,源码如下:
|
|
Looper退出后,通过Handler发送的消息会失败,Handler的send方法会返回false,在子线程中如果为其手动创建了Looper,消息处理完毕后应该调用quit()
方法来终止消息循环,否则这个子线程会一直处于等待状态,退出Looper以后,这个子线程就会立刻终止,因此建议不需要的时候终止Looper。
消息循环
Looper通过loop()
方法开启消息循环,源码实现如下:
|
|
loop()
方法是一个死循环,唯一跳出循环的方法是MessageQueue
的next()
方法返回了null。当Looper的退出方法被调用时,通知消息队列消息退出,当消息队列被标记为退出状态时,它的next()
方法就会返回null。也就是说Looper必须退出,否则loop()
方法会无限循环下去。loop()
方法会调用MessageQueue
中的next()
方法来获取新消息,而next()
是一个阻塞操作,没有新消息时会一直阻塞在那里,同理导致loop()
阻塞,如果MessageQueue
的next()
方法返回了新消息,Looper便会处理这条消息,通过以下语句执行:
|
|
这里的msg.target
是发送这条消息的Handler对象,这样通过Handler的dispatchMessage()
方法来处理消息,dispatchMessage()
是在创建Handler时所使用的Looper中执行的,这样就将代码逻辑切换到指定的线程中了。
Message & MessageQueue
Message
在线程之间传递消息,Message
有what
字段,是消息类型字段,arg1
和arg2
携带一些整型数据,obj
字段携带一个object对象。MessageQueue
是消息队列,存放所有通过Handler发送的消息。消息一直存放在消息队列中,等待被处理。每个线程只会有一个MessageQueue
对象。MessageQueue
主要包含两个操作,插入和读取,读取伴随着删除操作。euqueueMessage()
的作用是往消息队列中插入一条消息,next()
的作用是从消息队列中读取一条消息并移除。尽管MessageQueue
叫做消息队列,但是其内部是通过单链表的数据结构来维护消息列表。euqueueMessage()
的源码如下:
|
|
从源码中可以分析出,当前消息队列的头结点为空或待插入的消息需要被立即执行时,就让当前消息成为消息队列的新的头结点,并且如果消息队列处于阻塞状态,则将消息队列唤醒;否则则按消息等待被执行的时间顺序,将待插入消息插入消息队列中,最后如果需要唤醒消息队列,则通过native
方法nativeWake()
来唤醒消息队列。
下面再看看next()
方法的源码:
|
|
可以发现next()
方法是一个无限循环方法,如果消息队列中没有消息,其会一直阻塞在这里,有新消息到来时,next()
方法会返回这条消息并将其从单链表中移除。
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
问题描述
Android程序的入口点可以认为是ActivityThread
类的main()
方法,源码如下:
|
|
可以看到Looper开启了消息循环,loop()
方法是一个死循环,但是并没有看见有相关代码为这个死循环准备了一个新线程去运转,但是主线程却并不会因为Looper.loop()
中的这个死循环卡死,这是为什么呢?
原因
从上述代码我们可以发现,首先调用prepareMainLooper()
方法为主线程创建一个消息队列;其次,生成一个ActivityThread
对象,在其初始化代码中会创建一个H(Handler)
对象,即ActivityThread.H
,它内部定义了一组消息类型,主要包含了四大组件的启动和停止过程,如下所示:
|
|
thread.attach(false)
生成了一个AppplicationThread(Binder)
对象,ActivityThread
通过ApplicationThread
和AMS
进行进程间通信,AMS
以进程间通信方式完成ActivityThread
的请求后会回调ApplicationThread
中的Binder
方法,Binder
负责接远程ActivityManagerService(AMS)
的IPC调用,用于接收系统服务AMS发来的消息,收到消息后,通过Handler将消息发送到消息队列,UI主线程会异步的从消息队列中取出消息并执行操作;最后,UI主线程调用Looper.loop()
进入消息循环。
Android的Handler消息机制涉及到Linux的pipe/epoll
机制,MessageQueue没有消息时,阻塞在那里,主线程会释放CPU进入休眠状态,通过Linux系统的epoll
机制中的epoll_wait
函数进行等待,当有新消息来临时,往pipe(管道)
写入端写入消息来唤醒主线程,其实就是一个生产消费模型。
以上部分摘自柯元旦
其他问题
- MessagePool
- MessageQueue Native层?pipe/epoll机制 Android消息机制2-Handler(Native层)
- 还有一个疑问,那就是Android怎么响应点击事件呢?或者说对通常的GUI模型,如windows都是怎么实现的呢?生产消费模型?
Handler的其它用法
除了通过编写子线程并结合Handler发送消息改变UI外,Handler还有一些其他用法。
View中的post()方法
代码如下所示:
|
|
发现View的post()
方法就是调用了Handler中的post()
方法,前文已经说过Handler的post()
方法了,不再多解释。
Activity中的runOnUiThread()方法
代码如下所示:
|
|
先判断当前线程是否是UI线程,如果不是则调用Handler的post()
方法,否则就直接调用Runnable
对象的run()
方法。
参考信息
- 任玉刚.《Android开发艺术探索》
- Android中为什么主线程不会因为Looper.loop()里的死循环卡死?