平时开发时常用Handler做UI更新操作,所以大家可能误解为Handler就是用来进行UI更新的。其实Handler的功能不止这些
每个app进程被创建后会通过反射调用走ActivityThread类的main方法,这个方法是应用程序主进程的入口,在这个方法中会开启Looper轮循,不断从MessageQueue 中取消息。然后让H这个Handler类来处理消息。从消息中可以看出、四大组件的创建、生命周期的处理、等其他操作系统都会发出相应的消息。所以Handler的功能不仅仅被用来更新UI。
上面所说的Looper、MessageQueue就是Handler机制相关类。其实Handler机制一共提供了四个相关类:Handler、Looper、MessageQueue、Message。这四个类分工协作共同完成安卓中的消息机制。
ActivityThread#main
public static void main(String[] args) {
...
Looper.prepareMainLooper();
}
Looper#prepareMainLooper
public static void prepareMainLooper() {
prepare(false); // 核心
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper(); // 保存下生成的Looper对象
}
}
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 @Nullable Looper myLooper() {
return sThreadLocal.get();
}
prepare方法中首先会从ThreadLocal中查询当前的主线程是否有Looper对象绑定,已经绑定时直接抛异常。即提示一个线程中只能创建1个Looper对象。
当 Looper对象为空时也即Looper未与当前线程绑定过,这时需要调用Looper的构造创建Looper对象,并绑定到当前线程。
Looper的构造中创建了MessageQueue对象并赋值给自己的成员变量mQueue。因为Looper的绑定只能成功绑定1次。所以MessageQueue也只能被创建1次。即每个线程最多存在1个MessageQueue。
sThreadLocal.set(new Looper(quitAllowed));
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
这个要从Handler的sendMessage中分析,方法最终会调用handler的enqueueMessage方法。入队时会把当前的handler也一并作为消息的属性添加到队列中。这样每条message便会与目标Handler绑定。
除此之外在handler对象被创建时还会通过Looper的myLooper方法获得Looper对象,通过looper对象又获得mQueue对象。这样消息就会被放到唯一的MessageQueue中。
以常见的无参构造为栗子:
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
...
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;
}
从消息发送到入队来看系统并未限制Handler的创建个数。
当创建Handler时:会拿到当前线程所属的Looper对象。
当发送消息时:会把当前的handler作为消息的属性一并发送到消息队列。
当处理消息时,Looper不断从消息队列中取消息,从消息中找到指定的handler 让handler进行处理。
发送消息逻辑在子线程、处理消息逻辑在主线程中,这样就实现了线程的切换。
Looper的创建是给线程有关的。Handler对象创建时要求必须先有Looper对象。Looper的prepare方法创建Looper对象时会绑定当前线程。
所以当在子线程中创建Handler时需要先创建Looper对象,这样Looper就绑定到子线程了。当子线程中创建Looper对象、开启loop轮询,这样在主线程中通过handler对象发送消息时,子线程中轮训处理消息也就可以实现了。
这道题考查的是Message的target属性。当handler 发送消息时(enqueueMessage)会把自身作为Message的一个属性值(msg.target)封装到Message对象汇总一并发送到消息队列中。 Looper轮循出消息会拿到消息的target属性这样就可以知道每条消息是属于哪个hadler对象的。然后让相应对象的handler进行消息处理。
消息最终被调用Handler的enqueueMessage入队。然后在ActivityThread#main#Looper轮询中拿到消息,获得msg.target对象调用Handler的dispatchMessage进行分发消息。
handler#sendMessage
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0); // 延迟设置为0
}
// 注意这个方法的作用:
//不是说延迟某个时间后吧消息插入消息队列中,代表的意思是到达延迟时间后开始分发消息。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
// 注意这里的时间为:开机时间+延迟时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 这里有个重要操作,吧当前Handler对象赋值个Message的target属性
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 最终调用消息队列的enqueueMessage方法。
// 注意这里的时间:开机时间+延迟时间
return queue.enqueueMessage(msg, uptimeMillis);
}
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 直接new handler然后简单发送个消息最终会调用调用用户重写的handleMessage方法。
handleMessage(msg);
}
}
Looper#loop
msg.target.dispatchMessage(msg)
如源码,默认情况下我们创建handler对象时直接new了一下。不创建mCallback 对象,sendMessage时也未给Message的callback属性赋值。所以sendMessage的消息最终会被handleMessage(msg)来处理。
public final boolean post(@NonNull Runnable r) {
// 把Runnable 封装成message信息。
return sendMessageDelayed(getPostMessage(r), 0);//延迟设置为0
}
// 具体的封装过程
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
// 特别注意下这里,把runnable对象赋值给message的callback属性
//(后续dispatchMessage消息处理会用到)
m.callback = r;
return m;
}
包装后最终也会调用enqueueMessage方法消息入队。后面就是取消息的流程了。具体源码如上(六-1)这时msg的callback属性是不为null的,这时会走:
private static void handleCallback(Message message) {
// 这里其实就是执行用户post runnable 的run方法。这里代码是运行在主线程中。
message.callback.run();
}
总结:
ps:
这里需要注意的是,这两种方式其实最后都是调用 sendMessageAtTime 方法然后再enqueueMessage入队。
sendMessageAtTime 方法被调用时是根据传入的延迟时间需要再加上SystemClock.uptimeMillis() 再往下传。
SystemClock.uptimeMillis() 表示的是系统开机到当前的时间总数,单位是毫秒,并且当系统进入休眠时时间就会停止,但是不受时钟缩放或者其他节能机制的影响。
这个时间总和是用来给 Message 排序的,至于为什么是从开机到当前时间呢?这个也很好理解,毕竟只有开机才会有消息的分发;那为什么不用系统时间呢(System.currentTimeMilis),因为这个可能因为用户调整了系统时间而改变,该值并不可靠。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) { //1、handler post runnable 方式优先级最高
handleCallback(msg);
} else {
if (mCallback != null) { // 2、new handler 时传入callback 对象优先级次之
if (mCallback.handleMessage(msg)) { // 有返回值的,当返回true 后续的代码就不会执行了。直接return。
return;
}
}
handleMessage(msg); // 3、内存泄漏方式优先级最低(自定义Handler对象重写handleMessage)
}
}
如上代码msg#callBack>Handler#callBack>Handler#handleMessage
Message 核心源码
1、其实Message内部维护了一个消息池,消息池的具体实现就是采用的链表。
2、取消相关的方法在obtain中实现具体的做法就是取出链表的头部。然后删除头部。
3、消息池添加的操作在recycleUnchecked中,当消息池中未达到最大数量时,采取头插法插入链表头部。
Message next; //next 指针,指向下一条消息。
private static final Object sPoolSync = new Object();
private static Message sPool; // 消息池中的头消息(head 节点)
private static int sPoolSize = 0; // 消息池中的消息数量,没有被使用的消息。
private static final int MAX_POOL_SIZE = 50
// 消息池中取消息,无消息时再new 对象。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) { // 当前节点插入消息池的头部。 注意这里消息池的最大容量为50
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
在ActivityThread#main方法中,Looper的loop方法中取出消息使用完毕后就会通过msg.recycleUnchecked 来调用此方法,清空消息信息。把消息对象放入消息池。
这里推荐使用 Message.obtain() 方法来实例化一个 Message,好处在于它会从消息池中取,而避免了重复创建的开销。虽然直接实例化一个 Message 其实并没有多大开销,但是我们知道 Android 是消息驱动的,这也就说明 Message 的使用量是很大的,所以当基数很大时,最好是复用存在的对象,避免创建的开销。这时消息池就显得非常有必要了。
这点是考察你数据结构掌握的是否扎实,虽然链表删除、插入的时间复杂度是O(1)但是这是在不考虑遍历的操作情况下。如果算上遍历操作那么尾部操作时,删除、插入总体操作时间复杂度就是O(n)了。而头部操作不受遍历影响不用遍历。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper#prepareMainLooper
public static void prepareMainLooper() {
prepare(false); // 调用了带参数的prepare方法,false代表不允许loop退出循环。
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper(); // 吧looper对象用静态变量保存一份
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
// 首先去ThreadLocal中取Looper对象,无对象才创建。
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));
}
Thread {
Looper.prepare() // 步骤1:prepare
Handler().post { // 步骤2:发消息
}
Looper.loop() // 步骤3:开启循环
}.start()
Looper#prepare
public static void prepare() {
prepare(true);
}
1、可见prepare()为public权限,任何人都可以使用,内部调用有参数的prepare(true)消息处理完后可以退出looper。
2、prepare(xxx)为私有访问权限,只能被系统使用。系统使用时传参false,代表消息处理完不允许退出looper,否则app主线程就退出了。主线程直接抛出异常。
Message#when
/**
* The targeted delivery time of this message. The time-base is
* {@link SystemClock#uptimeMillis}.
* @hide Only for use within the tests.
*/
public long when;
Handle#sendMessage
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0); // 延迟设置为0
}
// 注意这个方法的作用:
//不是说延迟某个时间后吧消息插入消息队列中,代表的意思是到达延迟时间后开始分发消息。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
// 注意这里的时间为:开机时间+延迟时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 这里有个重要操作,吧当前Handler对象赋值个Message的target属性
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 最终调用消息队列的enqueueMessage方法。
// 注意这里的时间:开机时间+延迟时间
return queue.enqueueMessage(msg, uptimeMillis);
}
boolean enqueueMessage(Message msg, long when) {
...
msg.markInUse();
// 这里对Message的when进行赋值
// 这里对Message的when进行赋值
// 这里对Message的when进行赋值
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) { // 插入头部
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else { // 需要插入后面
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
...
}
handler#SendMessage发送消息时消息的时间when = SystemClock.uptimeMillis() + delayMillis,这个时间在消息入队时会被使用到。这个时间决定消息在队列的相对顺序。
即使用sendMessageDelayed发送消息时delayMillis肯定不为0,SystemClock.uptimeMillis()是手机开机相对时间二者加起来肯定不为0
handler#SendMessage发送消息delayMillis为0但是SystemClock.uptimeMillis()是手机开机相对时间二者加起来肯定不为0
所以系统提供了Handler().sendMessageAtFrontOfQueue(xxx)来解决这个问题,可以发送when=0的消息。
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//when = 0
//when = 0
//when = 0
return enqueueMessage(queue, msg, 0);
}
收获:所谓的延迟并不是延迟插入消息,而是直接就插入到消息队列了。只是插入时进行了排序,处理时按照时间相对顺序处理。