Handler 机制解析

本文整理自:


Handler机制组成部分

Hanler机制中的几个重要的组成部分:

组件 作用
ThreadLocal 将消息转存入其他指定的线程中
MessageQueue 消息队列,用于存储当前线程的转存入的消息
Looper 消息循环,不断的监控消息队列并从其中取出消息并处理
Handler 负责发送消息以及处理消息

使用方法

  • 常用于在其他线程中处理UI操作。

  • 在处理消息的线程中定义一个Handler,若该线程中没有定义Looper,则需要先定义Looper,否则会报错。其中主线程的Looper已在启动的时候默认加载了,所以在主线程中无需先定义Looper

  • 当在其中线程中需要进行UI操作的时候,需要在该线程中使用主线程的Handler变量的sendMessage方法或者post方法。

ThreadLocal注意

ThreadLocalAPI21以及API28中的代码实现方法不同:

  • localValues数组变为TheadLocalMap类。
  • ThreadLocalMap为弱引用。

Looper注意

  • Looper每个线程只有一个,除主线程外的线程默认不自动加载,需要自己主动使用Looper.prepare()方法加载。
  • 另外需要looper.loop()方法启动Looper
  • Looper启动时会同时加载MessageQueue
  • 推出Looper是可以使用Looper#quit或者Looper#quitSafely方法。

机制分析

handler.sendMessage(Message msg)出发,分析整个Handler的消息处理机制。

Handler#sendMessage方法

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
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 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) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

通过sendMessage方法的调用,以及中间的多个方法的调用,最终调用了enqueueMessage方法中的queue.enqueueMessage方法,将消息加入了MessageQueue中。

MessageQueue#enqueueMessage方法

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
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}

msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
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 (;;) {
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;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

这个方法比较简单,采用线程安全的方式将 Message 插入到消息队列中,插入的新消息有三种可能成为消息队列的 head

  1. MessageQueue 为空。
  2. 参数 when 为 0,因为此时 when 已经转成绝对时间,所以只有 AtFrontOfQueue 系列的 API 才会满足这个条件。
  3. 当前的 head Message 执行时间在 when 之后,即消息队列中无需要在此 Message 之前执行的 Message

Looper#loop方法

同时Looperloop方法不断监控MessageQueue中是否有待处理的消息,如果有的话,就调用MessageQueue#next方法,该方法将取出单链表第一个消息并返回给looper

MessageQueue#next方法链表中没有消息的情况下不返回任何信息,直到中间有了新的消息后才取出,否则将一直等待。

Looper#loop方法是会堵塞进程的方法,如果没有消息的话,就会继续永不停止的观测,直到MessageQueue#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
64
65
66
67
68
69
70
71
72
73
74
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}

me.mInLoop = true;
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);

...

for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
e.traceBegin(traceTag, msg.target.getTraceName(msg));
}

final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}

...

msg.recycleUnchecked();
}
}

在该方法的中间有msg.target.dispatchMessage(msg);这行代码。msg.target就是目标线程的handler对象,对该对象使用Handler#dispatchMessage方法,让其处理消息。

也就是说,取下一个消息的实际执行时间取决于上一个消息什么时候处理完。

MessageQueue#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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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;
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;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
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);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

看到 next() 实际上也有一个 for(;;),而出口只有两个:

  1. 消息队列已经退出,返回 null;
  2. 找到了一个合适的消息,将其返回。
    如果没有合适的消息,或者消息队列为空,会 block 或者由 IdleHandler 处理,不在本文问题范畴,暂不展开。

主要看找到合适的消息的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

可以看到:

  1. 如果在消息队列中顺序找到了一个消息 msg(前文分析过,消息队列的插入是由when顺序排列,所以如果当前的消息没有到执行时间,其后的也一定不会到),当前的系统时间小于 msg.when,那么会计算一个 timeout,以便在到执行时间时wake up;
  2. 如果当前系统时间大于或等于 msg.when,那么会返回msg给Looper.loop()。

所以这个逻辑只能保证在 when 之前消息不被处理,不能够保证一定在when时被处理。很好理解:

  1. 在 Loop.loop() 中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了 when,消息不可能在 when 时间点被处理。
  2. 即使 when 的时间点没有被处理其他消息所占用,线程也有可能被调度失去 cpu 时间片。
  3. 在等待时间点 when 的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。

所以由上述三点可知,Handler 提供的指定处理时间的 api 诸如 postDelayed() / postAtTime() / sendMessageDelayed() / sendMessageAtTime() ,只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。

Handler#dispatchMessage方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

该方法一共有三个执行等级:

  1. 如果传入的Message自己附带了UI操作的代码,那么就执行该代码。
  2. 如果Message没有附带的话,那么就是看mCallback是否存在,存在的话就执行该回调的handleCallback方法。
  3. 如果都不存在的话,就执行handler对象的handleMessage方法。

Handler#handleMessage方法

1
2
3
4
5
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}

可以发现,Handler#handleMessage方法是一个空方法,所以需要我们在创建的时候就进行重写。

之后目标线程就可以执行想要其执行的操作了。


相关面试问题

主线程中的 Looper.loop() 为什么不会造成 ANR

正如我们所知,在android中如果主线程中进行耗时操作会引发ANR(Application Not Responding)异常。

造成ANR的原因一般有两种:

  1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)。
  2. 当前的事件正在处理,但没有及时完成。

ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。

我们知道了消息循环的必要性,那为什么这个死循环不会造成ANR异常呢?

因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。

也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。

如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。

主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从 管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

总结:Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

sendMessage与sendMessageDelay如何保证Message放入MessageQueue中的顺序

通过对 MessageQueue#enqueue() 以及 MessageQueue#next() 源码的阅读,我们可以看到两个方法都是通过synchronized来保证了线程的安全性。

由于多线程的性能开销,所以我们能够保证 Message 的顺序的正确性,但是无法保证这些 Message 执行的时间的精确性。

Handler 内存泄漏问题

当不再需要某个实例后,这个对象却仍然被引用,阻止被垃圾回收(Prevent from being bargage collected),这个情况就叫做内存泄露(Memory Leak)。

考虑以下的代码;

1
2
3
4
5
6
7
8
public class MainActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}

虽然不明显,但是这段代码可能导致内存泄露。Android Lint会提示以下信息:

1
In Android, Handler classes should be static or leaks might occur.

它到底是如何泄露的呢?

  1. 当一个Android应用程序启动的时候,Android框架为这个程序的主线程即UI线程创建了一个Looper对象,用于处理Handler中的Message。
    Looper实现了一个简单的消息队列messagequeue,不断循环的处理其中的message。
    所有的应用程序框架的事件(比如Activity生命周期的调用,按钮的点击等)都被封装在这个Message对象里,然后被加入到Looper的Messagequeue,最后一个一个的处理这些Message。
    注意,Looper在整个应用程序的生命周期中一直存在。

  2. 在主线程中实例化一个Handler对象的时候,就和它关联了主线程Looper的消息队列Messagequeue。
    被发送到这个消息队列的Message将保持对这个Handler对象的引用,这样框架就可以在处理这个Message的时候调用Handler.handleMessage(Message)来处理消息了。
    (也就是说,只要没有处理到这个Message,Handler就一直在队列中被引用)。

  3. 在Java中,非静态内部类和匿名内部类都隐式的保持了一个对外部类outerclass的引用。但是静态内部类不会有这个引用。

正确的解决方法:

  1. 静态内部类+WeakReference
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends Activity {

// 静态内部类不会持有外部类的信用
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;

public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}

@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}

private final MyHandler mHandler = new MyHandler(this);
}
  1. 静态Runnable,避免对外部类的引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends Activity {

// 匿名类用static修饰后,不会再持有外部类的引用
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
// TODO
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}

静态内部类和非静态内部类的区别很微小,但是开发人员必须了解。

那么底线是什么?

当内部类可以独立于Activity的生命周期而存在的时候,应该避免使用非静态内部类,应该用静态内部类并且使用WeakReference保持对Activity的引用。

深入理解

像下面这样使用handler的时候,其实是将handler定义为了匿名内部类:

1
2
3
4
5
6
7
8
public class MainActivity extends Activity {
private Handler mLeakyHandler=new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}

匿名内部类会默认持有外部类(MainActivity)的引用

学过handler的都知道,handler发送消息后,消息会进行入队操作,在enqueueMessage方法中:

1
msg.targer = this;

this指的就是handler,所以handler被message持有了,而message放入消息队列后,message又被MessageQueue持有了,而MessageQueue是在创建Looper的时候生成的:

1
2
3
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
}

所以MessageQueue又被looper所持有。如果这个handler是主线程的handler,那么此时的looper就是指的主线程的Looper,它的声明如下:

1
private static Looper sMainLooper;

可以看到主线程的looper是static静态变量,而static静态变量在垃圾回收的时候是会被当做GC Root的,静态变量的生命周期与APP的生命周期、与虚拟机的生命周期是一样的,所以正是因为这个持有链的存在,导致了内存泄露。

引用链大致如下:

(static)Looper -->|持有| MessageQueue -->|持有| Message -->|持有| Handler -->|持有| MainActivity

所以解决 handler 内存泄露的办法就是要破坏这个持有链,比如只要 handler 不被 activity 持有就可以,所以可以把 handler 定义为static,因为 static 不会持有外部类,这样 handler 就不会持有 activity 了。

怎样判断一个内部类有没有被持有外部类?

比如上面的handler定义,没有加static的时候,在handleMessage方法里面可以正常使用MainActivity.this,这说明它持有了外部类。而一旦Handler加上static关键字,在handleMessage方法内部就不能再使用MainActivity.this,说明它没有持有外部类。

(为什么static变量,不会造成内存泄露?static 不会去持有外部类)


Handler 机制解析
https://luoyuy.top/posts/6805656561ea/
作者
LuoYu-Ying
发布于
2023年3月7日
许可协议