前言
Handler 音讯机制是 Android 中用于处理线程间通讯的重要机制。在 Android 开发中,UI 线程(主线程)是一个十分重要的线程,用来处理用户交互、界面更新等操作。可是,假如在 UI 线程中履行耗时操作,就会导致界面卡顿乃至 ANR(Application Not Responding)的状况发生。
为了处理这个问题,Android 供给了 Handler 音讯机制,经过它能够完结不同线程之间的通讯。Handler 首要用于向特定的线程发送音讯,然后在该线程中处理这些音讯。经过 Handler,咱们能够将耗时操作放到子线程中履行,然后运用 Handler 将结果发送回 UI 线程进行界面更新。
在运用 Handler 音讯机制时,通常会涉及到以下几个中心概念:
- Handler:用于发送和处理音讯的类,能够与 Looper 和 MessageQueue 一起工作来完结音讯的处理。
- Looper:用于不断地从 MessageQueue 中取出音讯,并将其分发给对应的 Handler 进行处理。
- MessageQueue:用于存储音讯的行列,Handler 发送的音讯会被增加到 MessageQueue 中等待处理。
- Message:音讯的载体,包含了要传递的数据及相关信息。
经过 Handler 音讯机制,咱们能够完结线程间的通讯,方便地在不同线程之间进行数据传递和操作处理,保证 UI 界面的流畅性和呼应性。在 Android 开发中,Handler 音讯机制被广泛应用于异步使命处理、守时使命调度等场景。
下图是网上撒播较为形象的 Handler 音讯机制的流程图解: 下面咱们将剖析上图流程,并经过源码来解析 Handler 音讯机制的原理。
1、Handler 处理音讯的流程解析
1)发送音讯
要处理音讯,首要要发送,Handler 类供给了很多发送音讯的办法供给给用户依据需求调用,但不论哪个办法,终究都会调用到 Handler 的私有办法:Handler.enqueueMessage()
-> MessageQueue.enqueueMessage()
,如下图:
这儿的 MessageQueue 相当于一个单链表,每个 Message 内部都有一个 Message 类型的成员变量 next
,在 MessageQueue.enqueueMessage()
办法中,便是将 MessageQueue 中的最终一个 Message 的 next
变量赋值为当前 Message。
2)接收音讯、处理音讯
音讯发送之后,怎样接收并处理呢?这儿就要用到 Looper,咱们来从源码看看履行流程:
public static void loop() {
......
......
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) {
return false;
}
......
......
try {
msg.target.dispatchMessage(msg);
......
} catch (Exception exception) {
......
} finally {
......
}
......
......
}
来看 me.mQueue.next()
办法:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
......
......
}
}
这个 next()
办法其实便是从 MessageQueue 中轮询取一个 Message,假如没有就回来 null
。假如取到了 Message,那么上述 loopOnce()
办法会继续向下运行,到了 msg.target.dispatchMessage(msg)
这行调用的便是 Handler 的 dispatchMessage()
办法,dispatchMessage()
办法又会回调到 Handler 的 handleMessage()
办法处理音讯。
全体流程便是:Looper.loop()
-> MessageQueue.next()
-> handler.dispatchMessage()
-> handler.handleMessage()
2、Handler 的创立
了解完上述流程之后,咱们知道 Handler 音讯机制中最重要的便是 Looper,假如没有正确运用 Looper,Handler 的功能就不能正常完结,那么假如咱们要自定义运用一个 Handler,该怎样做呢?
咱们知道 Android 程序的主线程履行入口是 ActivityThread 的 main()
办法,其间就会开启主线程的 Looper,咱们在自定义的时候模仿它的行为就好了,来看看这儿是怎样做的吧:
public static void main(String[] args) {
......
......
Looper.prepareMainLooper();
......
......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
......
......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
只看创立 Handler 有关的代码,咱们发现体系创立主线程的 Handler 首要分三步:
- 调用
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();
}
}
能够看到办法里检查了 sMainLooper
是否为空,因而这个办法体系调用了之后咱们再调用就会报错,而咱们运用自定义 Handler 时需求调用的是 Looper.prepare()
。
- 调用
sMainThreadHandler = thread.getHandler()
,这步便是实例化主线程 Handler,那么咱们也应当在prepare()
之后实例化一个 Handler。 - 调用
Looper.loop()
,最终就再用这个办法发动音讯轮询,处理音讯。
可是实例化 Handler 的次序不是固定的,而 Looper.prepare()
办法和 Looper.loop()
办法有必要先后履行。
3、Message 的创立
正常状况下,咱们需求实例化一个目标都是直接调用结构函数,可是在 Handler 机制中运用 Message 则不能直接创立,原因如下:
拿一块 60Hz 的屏幕来说,每秒钟会改写 60 次 UI,每次改写都会发送至少 3 个 Message,分别是:1、保证 UI 能改写的音讯屏障 Message;2、改写 UI 的 Message;3、移除音讯屏障的 Message(这儿说到的音讯屏障下文会解释)。那么每秒钟就会创立至少 180 个 Message 目标,而目标太多就会占内存,从而导致 GC,而重复创立目标再 GC 的行为,就会导致内存抖动,咱们知道每次 GC 都会有一段 STW(Stop The World) 的时刻,频频的 GC 就会导致应用卡顿,因而咱们不能经过直接调用结构函数的方式来创立 Message,下面让咱们看看该如何来防止上述问题。
首要咱们来了解一下 Message 的规划形式:享元规划形式
,经过这个规划形式能很好的防止上述问题,和 RecyclerView 的机制类似,Message 也有自己的缓存复用机制 recycle()
和 obtain()
,咱们来看看这两个办法的源码:
recycle():
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
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) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
回收的首要流程便是将一个 Message 的目标的成员变量初始化,然后判别当前缓存池的巨细是否小于缓存池最大容量(50),假如小于就存入缓存池中,假如大于,则缓存池满了,就不再回收。
obtain():
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
public 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 并回来,为空则只能 new()
一个 Message 并回来。
咱们在运用 Message 的过程中应该经过 Handler 中的 obtainMessage()
办法来获取一个 Message 目标:
public final Message obtainMessage()
{
return Message.obtain(this);
}
这儿的调用链是:handler.obtainMessage()
-> Message.obtain(Handler h)
-> Message.obtain()
4、音讯屏障机制
在了解音讯屏障机制前,咱们首要要知道这儿的 Message 首要分为三类:一般 Message、屏障 Message、异步 Message,其间由于音讯屏障机制,异步 Message 的履行优先级最高,比方咱们上面说到了 UI 改写的例子,为了不让界面卡顿,那么每次履行 UI 改写的 Message 都有必要马上履行,可是正常状况下 MessageQueue 中的 Message 履行是按次序的,因而就将 UI 改写的 Message 类型设置为异步音讯,使得 UI 改写的 Message 能马上履行。
音讯屏障机制的作用首要体现在以下几个方面:
- 控制音讯处理次序: 音讯屏障机制能够保证音讯行列中的音讯按照特定的次序被处理。这对于一些需求严格控制音讯处理次序的场景十分有用,能够防止并发问题,保证音讯的处理契合预期。
- 优化功能: 经过音讯屏障,能够将一组相关的音讯放在一个屏障音讯后边,保证它们在一起被处理。这样能够削减音讯处理的次数,优化程序的功能。
- 防止死锁: 在多线程环境下,假如音讯处理的次序不正确,可能会导致死锁问题。音讯屏障机制能够协助防止这种状况的发生,保证音讯的处理次序是安全的。
下面咱们来了解一下音讯屏障机制的履行流程。
1) postSyncBarrier()
发送音讯屏障 Message
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
能够看到,先是经过 obtain()
办法获取了一个 Message,随后在音讯行列适宜的方位刺进这个 Message,那一般 Message 和音讯屏障 Message 有什么区别呢?仔细看上面代码,发现并没有给获取到的 Message 的 target
变量赋值(这个 target
变量便是处理这个 Message 的 Handler),也便是说:target
为 null
的 Message 便是屏障音讯,MessageQueue 便是靠这个变量来区别音讯屏障和其他 Message 的。
2) postCallbackDelayedInternal()
发送需求优先处理的异步 Message
发送异步音讯终究都会调用到下面这个办法:
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
能够看到在获取音讯之后,调用了 msg.setAsynchronous(true)
办法,将标志位设置为 true
,然后才发送出去,这样在 next()
办法中就能分辨出异步 Message 并优先处理。
3) 处理音讯屏障 Message
还记得咱们上文说到的从 MessageQueue 中取出 Message 的 next()
办法吗,不记得的能够翻上去看看,便是那个 for()
循环句子里悉数省略没有张贴上来的那块,
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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;
if (msg != null && msg.target == null) {
// 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) {
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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
......
......
}
咱们能够看到同步代码块里的第一个 if
句子就判别了当前 Message 的 target
是否为 null
,即是否为音讯屏障,假如满意条件,则进入 do{} while()
循环找到满意 msg != null && !msg.isAsynchronous()
条件的 Message 并履行,这个 isAsynchronous()
便是判别是否为异步音讯的标志,咱们下面会介绍。
4) removeSyncBarrier()
移除音讯屏障 Message
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
这儿便是调用了 Message 的 recycleUnchecked()
办法回收了这个 Message。
那么,为什么要移除音讯屏障呢?
由于假如不移除的话,依据 next()
办法中的 for()
循环履行逻辑,在有音讯屏障的状况下只会处理异步音讯,那么一般的音讯就不能履行,因而有必要移除。
5、IdleHandler
IdleHandler 又是什么东西? 来看
public static interface IdleHandler {
boolean queueIdle();
}
它是一个接口,在 MessageQueue 闲暇时,就会履行它的 queueIdle()
办法,咱们能够经过下面的办法增加 IdleHandler:
Looper.myQueue().addIdleHandler(new IdleHandler(){
@Override
public boolean queueIdle(){
// 在这儿处理你想做的事,比方打印一个日志
Log.v("TAG", String.valueOf(System.currentTimeMillis()));
// 这儿的回来值决议了是否保留,假如回来 true,则每次闲暇都履行,回来 false,则一次闲暇履行完之后就被移除了。
return false;
}
});
或 Kotlin 版本:
Looper.myQueue().addIdleHandler {
Log.v("${System.currentTimeMillis()}")
return true
}
那么这个 queueIdle()
办法在什么时候履行呢?仍是在上面说到的 MessageQueue 的 next()
办法中(这么屡次说到,可见这个 next()
办法有多重要):
for (;;) {
......
synchronized (this) {
......
......
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
能够看到,上面流程是先找到一切的 IdleHandler,遍历履行 queueIdle()
并获取回来值,之后依据回来值判别是否移除这个 IdleHandler。
说了这么多,IdleHandler 一般用在哪呢?用在:
- Activity 发动优化:
onCreate()
,onStart()
,onResume()
中耗时较短但非必要的代码能够放到 IdleHandler 中履行,削减发动时刻。 - 想要一个 View 绘制完结之后,增加其他依附于这个 View 的 View,当然这个用 View 的
post()
办法也能完结,区别便是前者会在 MessageQueue 闲暇时履行。 - 增加一个回来值为 true 的 IdleHandler,在里面履行办法让某个 View 不断履行某个操作。
- 一些三方库中会运用,比方 LeakCanary、Glide 中运用到,用来检测内存泄漏。
以上便是对 Handler 音讯机制原理的解析。