说到Android的基础,就一定绕不开Handler,以及相关的Looper、Message、MessageQueue等。这篇文就说说它们之间的联系,来加深印象。
太基础的就不说了,那样要说的东西就太多太多了,多少会显得冗杂。
前言
众所周知,一个application启动后,会启动一个线程作为主线程,也就是ActivityThread,所有更新UI的操作都要放到这个线程里进行。除去这个主线程,其他线程均为子线程,在子线程里更新UI会报错:CalledFromWrongThreadException。这是因为在ViewRootImpl中做了检查,只要当前线程不是主线程,就会抛出异常。
那么,Android为什么这么做呢?
可以假想一下,如果不这么设计,允许所有线程更新UI,那么在并发量多的时候:一个TextView,同一时刻,有10个线程想要更新其显示的内容,这就会发生混乱。
如果想要解决这个问题,首先想到的应该就是加个同步锁synchronized,你们都可以更新UI,但是要排队,一个一个来。混乱的问题解决,但是性能很差,同步锁这个东西,不到万不得已的时候我都不会用。
最后,就有了现在的这种模式,单独出一个线程,也就是主线程,用于更新UI,其他线程若想更新UI,那么就向主线程发送一个消息,发送之后继续自己的任务,等到合适的时机,主线程就会根据发来的消息,做出相应的处理。
这就是Android的消息驱动模型。
团队组成
以前我只是知道个大概,为了写清楚这篇文,就去看了对应的源文件。搭配着AndroidStudio,源文件的可读性还是很不错的,如果有时间都可以去看看。
- Handler
用来发消息,即Message;同时,Handler也要提供Message的处理方式,也就是需要做哪些操作。 - Looper
如同名字一样,是一个循环器。它负责循环读取消息队列,MessageQueue中的消息,如果其中有新的未处理的消息,则就将其取出,并处理。 - Message
消息中包含了一个操作需要的一些信息,后面细说。 - MessageQueue
消息队列,所有的消息都将会发送到这里
这么说有些抽象,理解起来可能有些困难,打个比方:你去银行存款,到了银行需要先排号,然后等着喊号,排号期间,你可以做些别的事情,比如聊天、打游戏或者看视频,等到轮到你的时候,你就会去柜台,拿出自己装在兜里的钱,然后存到自己的账户上。
这个过程中,你就是一个Handler,排号就相当于发送了一个Message,所有排队中的号就相当于一个MessageQueue,而银行的叫号系统就如同一个Looper。
可以同时有多个Handler,一个Handler也可以发送多个Message。
Looper
- 线程里本没有Looper,需要手动创建,调用静态方法Looper.prepare()。
- 一个线程只允许创建一个Looper,Looper里面使用了一个类型为
ThreadLocal<Looper>
静态变量sThreadLocal
,保存了所有线程的Looper - 一个线程创建多个Looper时会报错:RuntimeException(“Only one Looper may be created per thread”)
- 主线程的Looper不需要手动创建,因为ActivityThread在main方法,也就是程序的入口,自动创建
- Looper里面持有了一个MessageQueue,在构造方法里面将其初始化
- 调用Looper中的loop方法之后,Looper便会循环从MessageQueue中取消息,没有消息时便会等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 只列出了源码中的部分重要代码
public class Looper {
static ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>()
static Looper sMainLooper;
final MessageQueue mQueue;
// 构造方法中出实例化MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
// 通过prepare创建Looper,并添加到sThreadLocal中
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static Looper myLooper() {
return sThreadLocal.get(); // 获取当前线程的Looper
}
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 取出消息
msg.target.dispatchMessage(msg); // 掉用Handler的方法来处理消息
}
}
}
Handler
- 在创建Handler时,会从Looper的静态变量
sThreadLocal
中获取当前线程的Looper,获取不到则报错RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
- 除会自动创建Looper的主线程,其他线程需要手动调用Loopr.prepare()进行创建,调用Looper.loop()开始循环取消息
- 现在推荐的创建Handler的方式,是通过Looper.mainLooper()获取主线程的Looper,然后将其作为构造参数传到Handler的构造方法里
- 所以,可以同时存在多个Handler发消息更新UI,因为使用的都是同一个Looper,即mainLooper
- 可以通过Handler发送Message,也可以发送Runnable,在加入到MessageQueue前,Runnable也会被封装成Message,最后Looper统一处理Message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46// 只列出了源码中的部分重要代码
public class Handler {
Looper mLooper;
MessageQueue mQueue;
Handler.Callback mCallback;
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper() // 获取当前线程的Looper
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLoopr.mQueue;
mCallback = callback;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
}
// 在Looper中调用,用来分发消息
public void dispatchMessage(Message message) {
if (msg.callback != null) {
handleCallback(msg); // 是Runnable的时候走到这里
} else { // 是Message的时候走的这里
if (mCallback != null) {
// 这个mCallback就是构造方法里传入的,
// 如果不重写handleMessage方法,也可以传入一个Callback来处理Message
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); // 如果没传入Callback,则调用handleMessage
}
}
// 用来处理Runnable
private static void handleCallback(Message message) {
message.callback.run();
}
// 用来处理Message
public void handleMessage( Message msg){
}
}
Message
- Message是个链表结构,其中有个Message类型的成员变量,叫
next
,靠这个变量来建立起链表结构 - Message中持有Handler的引用,叫做
target
,在Handler发送消息时,会把Handler赋值给Message的target
变量 - 在Looper从MessageQueue中取出Message时,便会调用其
target
的dispatchMessage方法来让Handler处理Message - Message一般不手动创建,而是通过
Message.obtain()
方法,如果Message池子里有可用的实例则会重用,没有则会返回一个new Message - 当使用Handler.post Runnable时,会将Runnable封装成Message,将Runnable赋值给其中的callback变量
1
2
3
4
5
6
7
8
9// 只列出了源码中的部分重要代码
public class Message {
Message next; // 链表,指向下一个Message
Handler target; // 发送和处理Message的Handler
Runnable callback; // 发送的Runnable
// obtain有很多重载方法
public static Message obtain() { /*省略*/ }
}
MessageQueue
- 这就是个消息队列,持有队列头消息的引用
- 对队列的操作都封装在这个类中,比如增加消息到链表
- 因为可通过Handler消息发送延迟消息,所以队列中的消息是根据执行时间排队的
ThreadLocal
总觉得说一说这个ThreadLocal才算完整。ThreadLocal是用来存储数据的,带有一个范型的参数,也就是需要存储的数据类型,它能够将数据与线程相互关联,保证在同一个线程取到的数据是同一个。通过get方法获取值,通过set方法设置值。
实现原理就是,每个线程里维护了一个名字为threadLocals
,类型是ThreadLocal.ThreadLocalMap
的变量,这种类型就是一个定制的Map
1 | public class ThreadLocal<T> { |
Handler使用
当使用Handler发送Runnable时,直接使用Handler.post()方法即可,这个方法有很多重载方法,例如:可发送延迟的消息,也可以发送在某个具体时间执行的消息,等等。
当使用Handler发送Message时,一般通过sendMessage方法发消息,然后重写它的handleMessage方法,来处理对应的消息,同样这个发送方法也有几个重载方法,查看对应的API文档即可:
1 | class HandlerTest : AppCompatActivity() { |
一般用法就是这样,但是这样编译器会提示一个可能会导致内存溢出的警告,因为在Handler的构造方法里做了检查:
1 | public Handler(boolean async) Callback callback, { |
什么是内存泄漏
简单说,就是当一个实例该被GC回收的时候,却无法被回收,这块无法被回收的内存,就是泄漏的内存。什么意思呢,举个例子,当从一个Activity返回的时候,Activity的实例从任务栈弹出,这个时候这个实例就该被GC回收掉,但是,如果这时还被其他实例引用的话,GC是不会对它回收的。
为何造成内存泄漏
我们来捋一捋,作为Activity的匿名内部类,Handler会持有外部类的引用,这是Java的机制;而Handler会被Message中的target变量引用,Message会被MessageQueue引用,MessageQueue会被Looper引用,而Looper的生命周期是和当前Thread的生命一样长,如果是主线程的话,那么这个Looper就会贯穿应用全程,因为Activity的实例间接被主线程引用,对于有引用的内存,虚拟机宁愿报错OOM,也不会让GC回收。
如何解决泄漏
原因既然清楚了,那么就好解决了,只要避免Handler引用外部类,这里就是Activity,那么就不会泄漏了。一般的方案,就是使用静态内部类,静态内部类对外部类是没有引用的,这也是Java的机制;或者将Handler独立出去,单独放到一个类文件中,使其不再是内部类,这样便不会在引用外部类。
总结
Android通过主线程的消息队列来更新UI,而Handler是这个消息驱动模型中的一员,负责发消息和处理消息,同时这个模型中还有其他几个成员:Message,消息;MessageQueue,消息队列;Looper,从队列去消息的循环器。