什么是Handler?
1 | Handler是安卓消息机制的一个上层接口。 |
Handler的使用
1 | private Handler mHandler = new Handler() { |
Handler的使用主要有两种方式,如上。
方式1内部其实也是调用的sendMessage(msg)方法,但是它不会交给Handler的handleMessage(Message msg)来处理,而是调用Runnable的run()方法。这种主要在定时循环的需求上使用。
方式2是直接发了一个Message消息,最终会由Handler的handleMessage(Message msg)来处理。这种方式往往在子线程调用,用来更新UI。
这里可能大家会有个疑问,Looper呢?不是说Looper是用来循环读取消息的吗?在哪里呢?其实我们看ActivityThread的源码就知道了。程序启动的时候已经帮我们创建了Looper,并且开启了循环loop()。
1 | public static void main(String[] args) { |
所以,我们使用的时候不再创建Looper。但是我们要在子线程去轮询并处理消息呢?是需要我们自己创建Looper的。
1 | private void startThread(){ |
Handler 核心类与源码分析
Message
Messgae是在线程之间传送的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。Message的what子段表示携带了什么类型的信息,obj字段表示携带一个Object对象。
- Message的创建
1 | /** |
我们创建消息,应避免使用new Message()的方式,而是使用obtain()。这个方法会从一个全局的pool里返回一个实例,避免分配过多的内存。其实采用了单链表存储已经回收了的Message,会每次取头结点的Message来复用,取完后第二个节点变成了头结点,单链表长度-1。当然了,没有可利用的Message,还是会默认通过new Message()来创建。
- Message的回收
1 | void recycleUnchecked() { |
Message回收后,存储回收Message的单链表会将该Message添加为头结点。
1 | 需要注意区分的: |
Handler
Handler就是消息处理者,它主要用于发送消息和处理消息。发出的消息经过一系列处理,最终会传递到Handler对象的handleMessage()方法中再分门别类的处理。
我们先看一下和发消息相关的代码:
1 | public final boolean post(Runnable r) |
post(Runnable r)内部封装了一个Message(这个Message的callback被赋值了,在处理消息时来区分交给谁来处理),最终也是调用了sendMessageAtTime(Message msg, long uptimeMillis)方法。不管是方式1还是方式2,最终都会调用queue.enqueueMessage(msg, uptimeMillis)来将消息添加到消息队列中。
1 | /** |
dispatchMessage(Message msg)这个方法是在Looper的loop()方法中被调用的。
MessageQueue
MessageQueue是消息队列的意思,它主要用来存放发送的消息。通过Handler发送的消息会存入MessageQueue中等待处理,每个线程中只会有一个MessageQuery 对象。
它通过一个单链表的数据结构来维护消息队列的,在插入和删除上比较有优势。
我么先看一下插入的过程:
1 | boolean enqueueMessage(Message msg, long when) { |
mMessages是链表的头结点,when是消息执行的时间。
如果链表头结点为null(也就是链表空)或者该消息立即执行或者该消息执行的时间比头结点消息执行的时间还早,那么把该消息插入到头结点p的前面,该消息为头结点。
如果不是上面那几种情况,开启了死循环遍历链表。跳出死循环的条件是p == null || when < p.when,也就是说某个节点为null(前一个节点就是末尾了呗)或者该节点消息执行时间大于了该消息的时间。然后将该消息插入到合适的(按执行时间when)节点。
下面看一下取消息的过程,但是我们需要先知道一个概念:消息屏障,也可以说是同步屏障。简单来说,设置了同步屏障之后,Handler只会处理异步消息。因为在next()方法中,会判断忽略同步的消息。那么怎么设置同步屏障呢?postSyncBarrier()方法。
1 | public int postSyncBarrier() { |
由源码可见,同步屏障的具体实现是在消息队列中添加了一个target==null的Message。
下面我们来看next()源码:
1 | Message next() { |
有消息可以处理,则取出并移除该消息。如果没有,则阻塞。阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。也就是nativePollOnce(ptr, nextPollTimeoutMillis)这个方法。通过Native层的MessageQueue阻塞nextPollTimeoutMillis毫秒的时间。
- nextPollTimeoutMillis=-1 一直阻塞不会超时
- nextPollTimeoutMillis=0 不会阻塞,立即返回
- nextPollTimeoutMillis>0 阻塞nextPollTimeoutMillis毫秒,期间被唤醒则立即返回
我们经常为了防止内存泄漏而调用removeCallbacksAndMessages(Object token),这个方法内部会调用MessageQueue的下面方法:
1 | void removeCallbacksAndMessages(Handler h, Object object) { |
从队列中删除所有匹配的元素。先从队首删除,如果删除了则队首指向接下来的元素,重复这个过程,直到第一个不匹配的元素出现。接着从这个元素之后开始查找并删除。
Looper
Looper是每个线程中的MessageQueue的管家,每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的dispatchMessage(msg)方法中。每个线程也只会有一个Looper对象。
我们先看一下Looper的创建:
1 |
|
我们可以通过prepare(boolean quitAllowed)在子线程中创建一个Looper,quitAllowed参数表示是否允许退出。应用启动的时候已经通过prepareMainLooper()创建了Looper了,不需要也不允许我们再次在主线程中创建。并且提供了一个getMainLooper()来获取主线程的Looper。
Looper最重要的方法就是loop()方法:
1 | /** |
loop()里面获取了当前线程的Looper以及与之关联的MessageQueue。开启了一个死循环,不断地从消息队列MessageQueue拿出消息处理。那么问题就来了。
- Question1:这个死循环什么时候退出呢?
1 | queue.next()返回的为null,死循环退出。那么什么时候返回null呢? |
- Question2:主线程的Looper开启了loop()死循环,为什么不会卡死?
1 | 1.首先需要说明的一点是,正是这个死循环保证了应用程序的正常运行。 |
- Question3:从消息队列MessageQueue拿出消息来交给谁处理?
1 | 由msg.target.dispatchMessage(msg)可以看出,消息交给了msg.target来处理,也就是Handler的实例。 |
- Question4:Message会不会被回收?
1 | 消息被对应的Handler处理完以后,调用msg.recycleUnchecked();相关源码见上文。 |
内存泄漏
原因:非静态内部类持有外部类的引用。
正确的写法:Handler写成静态的内部类,并持有外部类弱引用。
1 | MyHandler myHandler=new MyHandler(this); |
结束语
欢迎留言交流。