前语
Handler
是Android开发中运用常用的机制,自然也是面试中的高频考点,咱们都清楚,在问到Handler
时,都会供出他的好同伴Message
、MessageQuene
、Looper
。
在Handler
中会延生出一些问题,这些问题包括:
-
postDelay
是怎么做到推迟的? -
Handler
是怎么做到线程切换的? - 主线程
Looper#loop()
死循环会卡顿吗? -
Handler
内存走漏怎么处理? - Handler、Looper、MessageQueue、Thread的对应关系?
接下来咱们带着问题来看看Handler的一些源码吧。
Base on Android12
根本运用
先来温习一下Handler的根底用法。
// 直接在主线程中创立Handler
@SuppressLint("HandlerLeak")
final Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
LogUtils.INSTANCE.error(TAG, "来音讯了");
}
};
// 在一个子线程中运用
new Thread(() -> handler.sendEmptyMessage(10086)).start();
示例中的Handler在主线程初始化,假如是子线程中的Handler,记住调用Looper#prepare
和Looper.loop()
, 后边说为什么。
Handler的创立相关
首要来看一下Handler
结构的办法。
// 【无参结构办法,高版别现已废弃,不建议运用】
@Deprecated
public Handler() {
this(null, false);
}
// 【带Looper参数的结构办法】
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
咱们知道Handler
需求和Looper
、MessageQueue
相关。直观的看,Handler
目标中需求持有对应的目标引证
public class Handler {
……
final Looper mLooper;
final MessageQueue mQueue;
}
所以带Looper
参数的的结构办法目的就很明显了——你的Handler
需求和哪个Looper
相关。
主要看一下无参数的结构办法是怎么和Looper
相关上的。
public Handler(@Nullable Callback callback, boolean async) {
……
//【要害点1】调用myLooper办法获取Looper目标
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; // 从Looper中取MessageQueue
mCallback = callback;
mAsynchronous = async;
}
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
// 【要害点2】从ThreadLocal中获取Looper
return sThreadLocal.get();
}
可见当构建Handler
没有指定Loope
r时,会从ThreadLocal中去获取当时线程的Looper目标,假如为空,会抛反常,相信不少小同伴初学Android时都遇到过。那么当时线程的Looper
是何时存储到ThreadLocal
中的呢?当然便是调用Looper#prepare
的时分。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 【要害点3】存储当时线程的Looper目标,且只能初始化一次。
sThreadLocal.set(new Looper(quitAllowed));
}
这儿回答了为什么在子线程中创立Looper
时,需求先调用prepare
。那么为什么在主线程中创立的Handler
不需求呢?
废话不多说,由于主线程在ActivityThread#main
办法中现已调用了
public static void main(String[] args) {
……
// 【要害点4】
Looper.prepareMainLooper();
……
// 【要害点5】
Looper.loop();
}
所以在主线程中创立Handler
不再需求初始化Looper
等。
音讯入队
咱们用得比较多的办法便是post
、sendMessage
、sendMessageDelay
办法,先挨个看看
post办法
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
- 能够看到,post接受一个Runnable类型的参数,并将其封装为一个Message目标,并且将Runnable参数赋值给msg的callback字段,这儿要记住,后边有用——Runnable什么时分履行的呢?
- 最终调用的便是
sendMessageDelayed
sendMessage办法
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
- 最终调用的也是
sendMessageDelay
,第二个参数是0。
sendMessageDelay办法
可见无论是post仍是sendMessage办法,最终都走到了sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 这儿留意第二个参数 @param updateMillis 是一个详细的时刻点。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
……
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 【要害点6】这儿要留意target指向了当时Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 【要害点7】调用到了queue#enqueueMessage办法
return queue.enqueueMessage(msg, uptimeMillis);
}
最终会走到handler中的`enqueueMessage
办法,然后走到quene#enqueneMessage(msg, uptimeMillis)
,从这个命名来看,便是将message放入了音讯行列,那么咱们来看看,是怎么放入行列的吧。
boolean enqueueMessage(Message msg, long when) {
……
// 【要害点8】对queue目标上锁
synchronized (this) {
……
msg.markInUse();
msg.when = when; // msg的when时刻赋值
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 翻译:新的头结点,假如queue堵塞,则wakeup唤醒
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
// 翻译:将音讯刺进到音讯行列中,通常我不需求进行唤醒操作,除非........
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { // for循环,break结束时when < p.when,说明依照when进行排序刺进,或许尾节点
prev = p;
p = p.next;
if (p == null || when < p.when) {
break; // 【要害点9】 找到刺进方位,条件尾部或许when从小到大的方位
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 履行刺进
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); // native办法,唤醒
}
}
return true;
}
这儿面将msg放到音讯行列中,能够看到这个行列是一个简单的单链表结构,依照msg的when进行的排序,并且进行了synchronized加锁,确保增加数据的线程安全。之所以选用链表的数据结构,原因是链表方便刺进。
初看源码的时分,应该忽略掉wakeup这些处理,重视msg是怎么参加行列即可。
到这儿,咱们了解了message是怎么参加音讯行列MesssageQueue。可是音讯什么时分履行,即重新Handler#handleMessage
办法,以及post(Runnable)
中的Runnable什么时分才履行。
音讯出队履行
咱们看完了音讯入队,接下来看音讯出队的方位。自然在MessageQueue
中查找,很简单就定位到next
办法,调用它的的办法是Loope#oopOnce
,再往上找便是Looper#loop
(留意我是Android12)。这个办法很熟悉吧。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
……
// 【要害点10】死循环,只要loopOnce办法回来false时退出
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block 【留意注释:可能堵塞】
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
……
try {
……
// 【要害点11】还记住这个target是什么吗?
msg.target.dispatchMessage(msg);
……
} catch (Exception exception) {
……
} finally {
……
}
……
return true;
}
loopOnce
办法中主要便是调用me.mQueue.next()
获取一个音讯msg,留意这儿可能堵塞。【要害点11】这儿调用了msg.target.dispatchMessage(msg)
能够回头看看【要害点6】赋值的地方。所以这儿就将音讯分发给了对应的Handler去向理了。待会儿再看Handler#dispatchMessage
,咱们接着看next办法是怎么取音讯的。
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 nextPollTimeoutMillis = 0;
// 【要害点12】持续死循环
for (;;) {
……
nativePollOnce(ptr, nextPollTimeoutMillis);
// 加锁
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis(); // 当时时刻
Message prevMsg = null;
Message msg = mMessages; // msg 指向链表头结点
if (msg != null && msg.target == null) { // 这个if能够先忽略
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// 【要害点13】假如当时时刻小于头结点的when,更新nextPollTimeoutMillis,并在对应时刻安排妥当后poll通知
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message. 开端获取音讯
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next; // 更新头结点
}
msg.next = null; // 断链处理,等候回来
……
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
……
}
……
// 下面是IdleHandler的处理,还不是很了解
}
}
next
办法中也是一个死循环,不断的尝试获取当时音讯行列现已到时刻的音讯,假如没有满意的音讯,就会一向循环,这便是为什么会next
会堵塞的原因。
看完了next
办法,获取到了msg,回到刚才的msg.target.dispatchMessage(msg)
,接着看Handler是怎么处理音讯的。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
// 【要害点14】假如音讯有CallBack则直接,优先调用callback
handleCallback(msg);
} else {
// 【要害点15】假如Handler存在mCallback,优先处理Handler的Callback
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 【要害点16】此办法会被重写,用户用于处理详细的音讯
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
dispatchMessage
办法中优先处理Message
的callback
,再回头看看Handler#post
办法应该知道callback
是个啥了吧。
【要害点15】假如Handler设置了mCallback
, 则优先判断mCallback#handleMessage
的回来值,这个机制能够让咱们做一些勾子,监听Handler上的一些音讯。
【要害点16】应该不用多说了,这个办法一般在创立Handler
时被重写,用于接收音讯。
到这儿,咱们就简单的把Handler的音讯机制看完了,咱们结合开篇的几个问题,做一个总结吧。
总结
-
一个线程只能有一个
Looper
,怎么确保?- ThreadLocal
-
Handler#post(Runnable)
会将Runnable
封装到一个Message
中。 -
MessageQueue
选用单链表的实现方法,并且在存取音讯时都会进行加锁。 -
Looper#loop
选用死循环的方法,会堵塞线程。那么为什么主线程不会被堵塞?- 由于Android是事件驱动的,很多的体系事件(点击事件、屏幕刷新等)都是经过Handler处理的,因此主线程的音讯行列,会一向有音讯的。
-
Handler是怎么实现线程切换的?
-
Looper
和MessageQueue
和线程绑定的,也便是说这个音讯行列中的一切音讯,最终分给对应的Handler都是在创立Looper的线程。所以无论Handler在什么线程发送音讯,最终都回到创立Looper的线程中履行。(有点饶,能够自己好好捋一下,能够看一下第一节的运用)
-
-
Thread和Looper、MessageQueue是1对1的关系,Looper、MessageQueue关于Handler和是一对多的关系。这儿要留意,一个详细的Handler实例,必定只相关一个Looper和queue的哟。
上面便是整个Handler的音讯机制,还有一些扩展内容,一同看看。
扩展
音讯屏障
Android 体系为了确保某些高优先级的 Message(异步音讯) 能够被赶快履行,选用了一种音讯屏障(Barrier)机制。其大致流程是:先发送一个屏障音讯到 MessageQueue 中,当 MessageQueue 遍历到该屏障音讯时,就会判断当时行列中是否存在异步音讯,有的话则先跳过同步音讯(开发者自动发送的都属于同步音讯),优先履行异步音讯。这种机制就会使得在异步音讯被履行完之前,同步音讯都不会得到处理。咱们能够经过调用 Message 目标的 setAsynchronous(boolean async)
办法来自动发送异步音讯。而假如想要自动发送屏障音讯的话,就需求经过反射来调用 MessageQueue 的 postSyncBarrier()
办法了,该办法被体系隐藏起来了
屏障音讯的作用便是能够确保高优先级的异步音讯能够优先被处理,ViewRootImpl 就经过该机制来使得 View 的制作流程能够优先被处理
——————音讯屏障部分摘录自/post/690168…
IdleHandler
IdleHandler 是 MessageQueue 的一个内部接口,能够用于在 Loop 线程处于闲暇状态的时分履行一些优先级不高的操作,经过 MessageQueue 的 addIdleHandler
办法来提交要履行的操作。例如,ActivityThread 就向主线程 MessageQueue 增加了一个 GcIdler,用于在主线程闲暇时尝试去履行 GC 操作。
——————IdleHandler部分摘录自/post/690168…
Handler内存走漏
Handler作为内部类运用时,持有外部类的引证,比方Activity,所以当Activity销毁时,假如Handler中还有推迟任务,则走漏发生。因此要在合理的机遇移除Handler中的音讯。Handler.removeCallbacksAndMessages(null)
水平有限,请狠狠辅导。