关于Handler

1、关于handler消息机制有哪些重要关键的类?

Handler,Message,MessageQueue,Looper
Handler是发送的处理消息的类,发送消息的方法都是final修饰的,handleMessage方法是空方法,我们必须要去重写它
Message是消息体,包括what,arg1,arg2,obj这4个发送消息必须显示赋予的,然后Message类自己有一个消息池,它的使命是减少重复new Message对象,浪费资源。
最后要提到的关键的属性就是when,target,callback这几个属性是干嘛的后面再讲

Looper,消息循环器,作用是从MessageQueue中取出消息体,然后去处理
MessageQueue,消息队列,存放消息体的数据结构,作用是存放消息体,阻塞Looper消息循环等

2、handler发送消息的方式

两种方式:postsend

1
2
3
4
5
6
7
8
9
handler.obtainMessage().sendToTarget();
handler.post();//直接执行
handler.postAtTime()//某个时间点执行
handler.postDelayed()//延迟多少事件执行
handler.postAtFrontOfQueue()//放到消息队列前面执行
handler.sendMessage();
handler.sendMessageAtTime()//某个时间点执行
handler.sendEmptyMessage()//向消息队列中发送一个只带what不带其他obj的消息
Message.obtain().sendToTarget();//Message.obtain()是建议使用的生成Message对象的方式,Message类维护了一个消息池,复用Message对象

上面没有包括所有的发送方式,但是所有的发送方式都会走到Handler的两个方法:

1.sendMessageAtTime(Message msg, long uptimeMillis)
2.sendMessageAtFrontOfQueue(Message msg)

然后这两个方法殊途同归都会走到enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)这个方法,区别是方法1的uptimeMillis参数是SystemClock.uptimeMillis() + delayMillisdelayMillis就是提供的延时时间,没有值或者小于0默认为0,方法2的uptimeMillis是0。
ps:

SystemClock.uptimeMillis() 从开机到现在的毫秒数(手机睡眠的时间不包括在内);
System.currentTimeMillis() 从1970年1月1日 UTC到现在的毫秒数;这个值是不准的,可以被修改。

1
2
3
4
5
6
7
8
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//将消息体的target绑定为当前的handler
if (mAsynchronous) {
//设置消息体是不是异步,生成消息体时如果没有设置那一定就是false,如果要设置调用消息体的setAsynchronous()设置
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

无论是那种方式,都直接或间接需要一个handler对象,Handler类的构造函数需要初始化4个属性mLoopermQueuemCallbackmAsynchronous,分别讲下这4个怎么赋值的,先讲下Handler的构造函数,明面上的有三个,撸到底就两个@hide的构造函数。

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
1、//调用这个构造函数的前提是传入了looper对象,或者looper和callback一起传入,async默认为false
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;//用Looper循环器的消息队列去初始化handler的消息队列
mCallback = callback;//没传就为null
mAsynchronous = async;
}
2、//调用这个构造函数的前提是传入了callback对象,或者什么都没传,async默认为false
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

从上面我们可以看到显式new出来的方式都是同步的,异步的怎么生成?

1
Handler.createAsync(@NonNull Looper looper, @NonNull Callback callback)

就是上面的方法了,它是一个静态的,方法体内默认async为true。接下来分析这几个属性:
1、Looper:上面两种方式,一种是自己传进去,另一种是自己获取,也就是Looper.myLooper()重点说下第二种,Looper类中维护了一个ThreadLocal,使用第二种方式获取也就是从当前本地线程副本中去获取Looper,在不同的线程中这个副本是不一致的,所以说在新开启的线程中,这个副本是get到的是null。所以如果handler初始化时如果没有传入looper的话,那就是第二种Handler的构造函数,它会默认调用当前线程绑定的Looper,可以看到如果获取的loopernull,会抛出运行时异常,所以你知道了吧,为什么在非主线程中使用新建使用handler的话,必须先调用Looper.prepare()方法
2、MessageQueue的初始化都是由Looper中的消息队列来完成的,Looper中的属性是在构造器中初始化的,而MessageQueue类的构造器是这样的

1
2
3
4
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

需要传入一个是否允许停止的标志位,可以看到还使用了一个nativeInit(),这个是一个native方法。MessageQueue中有很多native方法,包括初始消息队列,销毁消息队列,唤醒消息队列,都是利用了native层方法,在native层也有一个消息队列。
3、Callback,Handler中封装的一个接口,里面只有一个handleMessage方法,它的执行顺序在Handler的handlerMessage空方法之前,具体可以看loop循环时分发消息调用handler的消息分发方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//handler处理消息分发的唯一的入口
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);// =>msg.callback.run();
} else {
if (mCallback != null) {
//handler重写的handleMessage执行顺序在callabck的handleMessage之后,也就是callback不为null,且callback的handleMessage返回true,这个消息就被消化掉了,不会再执行Handler重写的handleMessage
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

4、async异步属性,这个属性唯一在handler中使用的位置:

1
2
3
4
5
6
7
8
//这里是消息体入消息队列的唯一入口
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);//为入队的消息设置为异步消息
}
return queue.enqueueMessage(msg, uptimeMillis);
}

3、我们知道handler可以发送延时消息,那它是怎样实现延时处理的?

提到延时,可以想起来在每个消息体入消息队列之前,都会初始化when属性,这个属性是一个时间点,在loop方法中会去从消息队列中取消息。我们来看消息怎样取出来的

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
 public static void loop() {
//死循环一直跑
for (;;) {
//从messageQueue中取出消息,如果是UI主线程的Looper,这里就有可能阻塞,因为UI主线程的Looper是必须一直跑起来的,很多其他的消息行为,如启动Activity之类的都是在这个Looper中接收处理的,而如果是其他的另开的线程,使用了Looper.prepare和Looper.loop,是定义了自己的线程循环器,根据源码上来看,这个定义的looper是可以停止的,也就是说,当消息队列中没有消息之后,这个消息队列返回的msg就为空
然后loop方法就会return。
......
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
.....
try {
msg.target.dispatchMessage(msg);//去调用消息队列绑定的handler去执行消息分发
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
//消息体处理完之后进行销毁
msg.recycleUnchecked();
}
}

可以看到 Message msg = queue.next()这一行代码是有可能阻塞的,当时间还没到我们预设的时间,消息队列就没被唤醒,也就取不出消息体,消息就是这样实现延时的。我们再看下next方法,去从中找出它阻塞的证据

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;//字面意思是取出下一个消息体的阻塞时间,用于确定消息队列应处于出队状态还是等待状态,-1表示等待状态,0表示返回,>0表示阻塞多少毫秒
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

nativePollOnce(ptr, nextPollTimeoutMillis);//这里就是实现阻塞的地方了,C方法

synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//target就是处理的handler,如果取到的消息不为null,但是handler为null,就遗弃这个消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg =msg.next;
//这两句是将当前指向的msg向后移直到msg不为null且当前的消息不是异步消息
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//表示当前取出的消息还没到预定执行的时间点,设置一个超时时间去唤醒
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 取出一个消息体
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
//取出的msg为null,然后将这个参数置为-1,然后去阻塞
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
//已处理所有待处理的消息
if (mQuitting) {
dispose();//截断这个消息队列,在调用了quit()方法后会调用
return null;
}
nextPollTimeoutMillis = 0;
}
}

4、在子线程使用handler要注意什么?

新开子线程的Looper的ThreadLocal中没有绑定looper,所以必须先调用Looper.prepare()方法先初始化Looper和MessageQueue,或者在Handler构造器中传入已经初始化好的looper,比如Handler h = new MyHandler(Looper.getMainLooper());这种方式,而如果要绑定自己线程的循环器和消息队列:

1
2
3
4
5
Looper.prepare();
Handler h = new Handler();//初始化handler必须在prepare后面
h.sendEmptyMessage(1);
//sendmessage必须在loop()前面,原因是loop()是个死循环,它没有销毁之前,后面的语句都不会执行
Looper.loop();