Android
中使用 Handler
来进行消息的处理,它是
- Handler意思是处理者,它是android特有的用来消息处理的一个类。使用它可以解决很多android中常见的问题.
- Handler体现的是一种消息的发送与处理异步进行的机制,消息发送时即刻返回,将消息加入队列中,另一端looper负责循环取出消息进行操作。
- 需要注意的是Handler可以发送Message也可以发送Runnable对象。但是消息处理必定是在Handler所在的线程中,也就是说UI线程中的Handler发送的消息也是在UI线程中执行,所以同样不能执行耗时操作。
使用场景:
- 发送延时消息,执行延时任务,使用线程睡眠的方式过于粗糙。。通常是用Handler发送延时消息或者使用Timer来完成延时操作。
- 在子线程“操作”UI,众所周知子线程是不能操作UI的,想要在子线程的任务执行完之后更改UI,就需要在子线程向主线程发送消息让主线程来修改UI。
- 结合异步任务实现任务回调,这个实际上是等同于2的,因为异步任务内部也是子线程,使用Handler+异步任务可以实现请求的回调,但是通常我们更加偏向于接口回调的方式,而不是传递Handler。
- 在子线程中使用Handler,对任务进行串行化处理,可以更加高效的管理任务的执行。
Handler原理图解:
Looper类
介绍
- Looper类负责在消息队列的另一端取出消息进行处理,Handler采用消息的先进先出原则。
成员变量
- Looper中的成员变量ThreadLocal,提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。查了很多资料,这里有很好的的解释,大家可以去膜拜一下。
- Looper类中,首先ThreadLocal变量是private static的,每个线程只能有一个Looper对象,在我看来,使用ThreadLocal是为了对每个线程的Looper进行管理。使用ThreadLocal存储Looper就可以很方便的隔离其他线程随时存取本线程的Looper对象,结合后面的代码,发现创建Looper时会使用当前线程为键,新创建的Looper为值存放到ThreadLocal中,prepare()又会进行判断是否已经创建了该线程的Looper,这么说可能有些抽象,建议看完后面的代码,再回来看这段也许会更加清晰一点。
1 | //ThreadLocal.get() will return null unless you've called prepare(). |
构造方法
- 构造方法,私有化的构造方法,并不允许外部调用,做的操作是创建一个MessageQueue和将mThread对象指向了本线程。这里有个重要的地方是,MessageQueue是Looper创建并首先持有的。
1 | private Looper(boolean quitAllowed) { |
- 在Looper之中有一个
private static Looper sMainLooper;
变量,这个变量代表主线程(UI)的Looper,这也就是我们不需要在UI线程显式调用Looper.prepare()方法的原因,看下面的代码及注释:
1 | /** |
成员方法
- prepare方法,创建Looper放入sThreadLocal,同时要求每个线程只能有一个Looper对象,多创建会报异常。
1 | public static void prepare() { |
- loop方法,使用该方法循环取出MessageQueue的消息。死循环取出消息,进行处理,处理完之后,同时消息会被回收掉,使用这个机制可以进行消息对象的复用,这里loop()结合MessageQueue的next()方法,形成了一个轮询的过程,详细的内容会在MessageQueue类的分析中来看,到时候会解释死循环轮询消息的机制。这里有其他对象的部分方法,暂且不去考虑,做下标记,看到Message的源码自然就会明白。
###留下的问题:
Message msg = queue.next();
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
稍后我们会去解决。
1 | public static void loop() { |
Message类
成员变量
- Message类十分类似于Bean类,毕竟它是用来承载消息的。
what,arg1,arg2,obj
都是预设好用来盛放简单消息内容的变量,原文的注释中使用了lower-cost
表示使用这些变量可以降低开销,就是不用自己创建维护这些变量,而且消息是可以被复用的,确实降低了开销。
- target变量,是一个Handler,后面的代码中会具体介绍他的作用,这个变量也算这个类的核心了。
- Runnable callback,代表一个任务,前面说过Handler可以发送简单消息也可以发送任务。
- Message next,代表下一个Message的引用,由此形成了一个链表的结构。
- 还有很多常量,变量不做一一介绍。下面有注释,不太详细,大家可以看完后面的再回来看也许会更加清晰。感兴趣的可以仔细去看源码注释。
1 | public int what;//消息标示 |
成员方法obtain()
- Message类中实现了obtain()的8个重载方法,提供了各种各样的参数,为的只是我们在外部用起来好用,所以大家使用Message时不妨多去用用其他方法,我到现在为止一般都是偏用参数为空的方法,其他的重载基本没看过,另外Handler也提供了大量的obtain方法,是的Message的重用和管理更加的方便了,所以千万不要去new Message。其他的方法也会回调空参方法然后进行一下外围的初始化,所以我们就来看看空参方法。
- 代码不多,解释一下,首先这个变量sPoolSync,看名字就知道他是用来同步操作的,目的是当一个操作在获取Message是进行同步操作,避免其他的操作再来创建Message,否则会怎么样?Message本身形成了一个链表的结构,不进行同步就会,出现多个头,或者一个Message后面接入多个Message,那么后接入的就会覆盖掉,是这样吗?还是出错误。
- 接下来是一个判断,我们看到sPool这个变量,它是一个Message对象,经过我的研究它是这个链表的头指针,同时sPool维护的是一个曾经创建过的空的可复用的Message队列,了解这一点至关重要,这是Message可复用的关键。看看他是怎么操作的
- 如果这个根sPool为空,则返回一个新的Message,Message的构造方法我看了,是个空的,所有的属性都在外部或者发送的那一刻设置。
- 如果不是空,那么表示可复用Message队列可用,则取出头部的Message
Message m = sPool
,同时指针向后移动一位sPool = m.next;
此时m是等于sPool的,以此表示sPool后移一位,然后将取出的Message.next置为null,因为这个消息是要拿来发送的,他此时可是指向的可复用Message队列的头,所以将它的next置为null,不然会怎么样?我们看Looper源码时,循环何时终止呢,就是在next==null时终止,如果不置为null,for循环是不会停止的,会把空的Message队列遍历一遍。最后可复用的链表长度减一sPoolSize--;
- 其他的obtain方法我们不再去研究,大同小异吧。
1
2
3
4
5
6
7
8
9
10
11
12
13public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
Message的回收方法
- 看一下代码,了解了上面说的机制,这个代码不难理解。解释一下,如何回收一个Message,要有一个概念就是Message调用该方法回收的是自己,首先将自己的next指向sPool
next = sPool;
也就是说。此时自己已经链接到了可复用的Message队列头部(每次都叫他。可复用的Message队列。真麻烦),然后sPool = this;
sPool指针又指向了 可复用的Message队列头部,队列长度++完成了消息的回收。在Looper中提到的msg.recycleUnchecked();
这个方法就是在这里实现的。
1 | void recycleUnchecked() { |
###补充
- Message实现了Parcelable接口表明它是可以传输的。
1 | public final class Message implements Parcelable {} |
MessageQueue类
介绍
- 内部通过链接Message形成了一个消息队列,有两个比较核心的方法。
boolean enqueueMessage(Message msg, long when){}
和Message next()
方法,Looper类中遗留的第二个问题Message msg = queue.next();
会在这里解释。
入队方法
前面巴拉巴拉一通判断,内部的target不能是null,Message对象不能被占用,线程不能退出。。。。
这里的mMessages变量起到了与Message中sPool相同的作用,它是队列的指针,指向消息队列的头,只是这里维护的队列是要被处理的消息队列。但是由于sendMsgDelay方法的存在,入队时不能单纯的链接消息,还需要判断时间戳。
if (p == null || when == 0 || when < p.when)
这个判断if(是第一个消息 ||要被处理的时间是0 ||要被处理的时间小于当前队列头消息的时间也就是已经到达处理这个消息的时间),此时将会将消息链接在队列头部msg.next = p;
,同时指针指向它mMessages = msg;
,并且此消息是不要被唤醒的needWake = mBlocked;
,这个操作与Message中的操作十分相似,不多做介绍。
- else语句块中表明此消息是一个延时消息,此时进行的操作是采用了一个for循环,完成的功能是找到这个消息被处理的时间when,大于这个时间的第一个消息,将它插入到该位置。如果没有找到会一直循环,最后找到preMsg指向的前一个消息和p = mMessage指向的后一消息,进行链接操作,
msg.next = p; prev.next = msg;
就是普通的链接操作。
1 | boolean enqueueMessage(Message msg, long when) { |
出队方法
- 之前在Looper.loop()方法中看到有这么一段代码,结合MessageQueue的出队方法,可以发现这是一个循环轮询消息队列的操作。在死循环中调用了
Message msg = queue.next(); // might block
方法,当返回msg==null,循环体结束,什么时候会结束,看next()方法中返回null的只有一种情况就是线程结束时,返回null,线程结束,他的Looper自然应该结束。
- 死循环实际是发生在MessageQueue中的,我在注释中写了说明。关于MessageQueue的很多本地方法的介绍,大家可以参考这里
1 | Message msg = queue.next(); // might block |
1 | Message next() { |
Handler类
成员变量
1 | //从这里看得出Handler很好的连接了Looper和MessageQueue |
构造方法
- 重载了多个构造方法,老规矩,我们只看没有最底层的构造方法和我们最常用的构造方法
1 | //常用空参构造方法 |
成员方法
- obtain()方法,这个方法不做解释,返回的是Message的obtain()方法,详细请看Message类的分析。
- 发送消息,我们浏览一下所有发送消息的方法。方法很多,大致的思想是,填充消息,发送消息,很多方法都是重载互相调用的,关注最后一个方法,它调用了MessageQueue的入队方法,同时将msg.target设置为this,将这个消息插入到了队列中,也就是被发送的Message持有发送它的Handler的引用。
1 | //以下是各种发送消息的方法,可以浏览一下。 |
事件分发与处理
- 还记得Looper中遗留的问题吗?
msg.target.dispatchMessage(msg);
结合发送消息的方法,可以得出,msg.target就是发送它的那个Handler,处理消息时调用dispatchMessage方法,也就是下面的方法,所以,Message携带它的发送者,谁发送的消息誰来处理它。 - 分析一下下面的逻辑。
msg.callback
是消息中包含的任务,如果是一个任务的消息,那么不需要外部处理,直接调用该Runnable任务的run方法,所以还是在当前线程执行,并没有开启新的线程,不要看到Runnable就想到线程,这也是我以前的一个误区 - 不是一个Runnable任务,
mCallback
是一个接口,在介绍成员变量是提到过,他可以通过构造方法在外部实现,当然不是必须实现的,如果实现了这个接口,那么调用这个接口的mCallback.handleMessage(msg)
方法处理消息。 - 如果没有实现这个接口,则调用
handleMessage(msg);
方法,这个方法是空的,需要你在子类中重载,如果你没有重载他不会执行任何操作。这算提供了处理消息的两种方式。
1 | public void dispatchMessage(Message msg) { |
##总结
- 线程中Handler消息机制的使用,Looper类中给出了很好的示例代码。注意的是在非UI线程需要我们显式的调用
Looper.prepare(); Looper.loop();
方法来完成消息的轮询。
1 | class LooperThread extends Thread { |