Android Framework 专项 - Handler(一)

问题

1
2
The formulation of the problem is often more essential than its solution, which may be merely a matter of mathematical or experimental skill.
― Albert Einstein

Q:一个线程有几个 Handler?

Q: 线程间的通信的原理是怎样的?

Q: Handler 内存泄漏的原因?为什么其他的内部类没有说过这个问题?

Q: 为何主线程可以 new Handler ?如果想要在子线程中 new Handler 要做些什么准备?

Q: 子线程中维护的 Looper,消息队列无消息的时候的处理方案是什么?有什么用?

Q: 既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部时如何确保线程安全的?

Q: Looper 死循环为什么不会导致应用卡死

Q: 为什么主线程不需要自己创建和管理消息循环

Handler 在 Android 中的应用

1
Handler 是针对 Android 系统中与 UI 线程通信而专门设计的多线程通信机制

Retorfit,eventbus,rxjava,Looper

Handler 源代码分析

子线程 发送 MSG

​ android.os.Handler#sendMessage ->
​ android.os.Handler#sendMessageDelayed ->
​ android.os.Handler#sendMessageAtTime ->
​ android.os.Handler#enqueueMessage ->
​ android.os.MessageQueue#enqueueMessage ->
​ android.os.Looper#loop

android.os.Handler#sendMessage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    /**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
/**
* 在所有挂起的消息之后将消息推送到消息队列的末尾
* 当前时间之前。它将在 {@link #handleMessage} 中收到,
* 在附加到该处理程序的线程中。
*
* @return 如果消息成功放入则返回 true
* 消息队列。失败时返回 false,通常是因为
* 处理消息队列的 looper 正在退出。
*/
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
android.os.Handler#sendMessageDelayed
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
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
/**
* 将一条消息放入消息队列中,位于所有挂起的消息之后
* 之前(当前时间+delayMillis)。您将在以下时间收到它:
* {@link #handleMessage},在附加到该处理程序的线程中。
*
* @return 如果消息成功放入则返回 true
* 消息队列。失败时返回 false,通常是因为
* 处理消息队列的 looper 正在退出。请注意,一个
* true 的结果并不意味着该消息将被处理 -- 如果
* Looper 在消息发送之前退出
* 发生则消息将被丢弃。
*/
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
android.os.Handler#sendMessageAtTime
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
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
/**
* 将消息排队到消息队列中,在绝对时间(以毫秒为单位)<var>uptimeMillis</var>之后的所有挂起消息之后。
* <b>时间基准是 {@link android.os.SystemClock#uptimeMillis}。</b>
* 在深度睡眠期间花费的时间将会额外延迟执行。
* 您将在{@link #handleMessage}中接收它,该方法会在与此处理程序连接的线程中执行。
*
* @param uptimeMillis 消息应该传递的绝对时间,使用{@link android.os.SystemClock#uptimeMillis}作为时间基准。
*
* @return 如果消息成功放置到消息队列中,则返回 true 。如果失败,则返回 false ,通常是因为处理消息队列的消息循环正在退出。
* 请注意,返回true并不意味着消息将被处理 - 如果消息传递时间之前消息循环被退出,则消息将被丢弃。
*/
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);
}
android.os.Handler#enqueueMessage
1
2
3
4
5
6
7
8
9
10
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);
}

到这里,发送流程都没什么好说的

重点:android.os.Handler#handleMessage
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
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}

synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

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;
}
msg.target 是什么

注意:这里 msg.target 就是 msg 持有的 handler 也是 为什么会有内存泄漏风险的原因。

分析代码:

Handler 内存泄漏原因

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler持有,这样最终就导致 Activity 泄露。

它的主要作用是将消息按照时间顺序插入到消息队列中,并在必要时唤醒队列以处理这些消息。下面逐步分析代码的功能和逻辑。

  1. 首先,代码对传入的消息 msg 进行一些验证。它确保消息有一个非空的目标(msg.target != null),否则抛出 IllegalArgumentException。

  2. 然后,代码在同步块内执行以下操作:

    • 检查消息是否已经在使用中,如果是,则抛出 IllegalStateException。这可能是为了防止重复使用消息,确保每个消息只被处理一次。

    • 检查当前处理器是否正在退出(mQuitting 标志),如果是,就回收消息并返回 false,表示消息未被成功加入队列。

    • 将消息标记为正在使用,并设置消息的触发时间 msg.when 为传入的 when。

    • 获取消息队列的头部消息 p。

  3. 接下来,代码根据以下条件进行处理:

    • 如果消息队列为空,或者传入的触发时间 when 为 0,或者传入的 when 小于队列头部消息的触发时间 p.when,则将新消息插入到队列头部。如果队列当前被阻塞(mBlocked 标志),则设置需要唤醒队列(needWake = true)。

    • 否则,如果消息需要插入队列中间,则根据条件判断是否需要唤醒队列。具体判断条件是:队列被阻塞、队列头部消息的目标为 null,且传入的消息是异步消息。然后,代码在一个循环中遍历消息队列,找到合适的位置插入新消息。循环会一直迭代,直到找到合适的位置或者遍历完整个队列。

    • 在找到合适的位置后,代码将新消息 msg 插入到队列中。具体做法是,将 msg.next 设置为当前消息 p,然后将前一个消息 prev.next 设置为新消息 msg。

  4. 最后,代码根据之前的标志 needWake 来决定是否唤醒队列。如果需要唤醒,则调用本地的 nativeWake 方法(可能是一个底层的本地方法)来唤醒消息队列。

  5. 整个同步块结束后,代码返回 true,表示消息已经成功加入队列。

这段代码的核心功能是在消息队列中插入消息并进行适当的排序,以确保消息按照触发时间顺序进行处理。同时,它还处理了一些异常情况,如消息已经在使用中或者处理器正在退出。唤醒队列的逻辑也在代码的最后部分进行处理。

让我们逐步分析 MSG 插入队列的位置:

第一种插入头部的情况:
1
2
3
4
5
6
7
8
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 {
...
}
  1. 如果消息队列为空,意味着当前没有任何消息在队列中等待处理。在这种情况下,不需要比较触发时间,直接将新消息插入到队列的头部。这会让新消息成为队列的第一个要处理的消息。
  2. 如果传入的触发时间 when 为 0,这可能表示该消息需要尽快处理,因此同样将它插入到队列的头部。
  3. 如果传入的触发时间 when 小于队列头部消息的触发时间 p.when,这意味着新消息应该在队列中位于当前头部消息之前,因此同样将它插入到队列的头部。

综合上述情况,无论是队列为空,还是传入的 when 值为 0,或者传入的 when 值小于队列头部消息的触发时间,都会将新消息插入到队列的头部。

第二种插入中间的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if(p == null || when == 0 || when < p.when) {
...
} 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;
}
  1. 首先,如果不满足前面提到的条件(消息队列为空,传入的触发时间为0,或传入的触发时间小于队列头部消息的触发时间),那么意味着要插入的新消息 msg 应该位于队列中间。
  2. 在这种情况下,代码会进入一个循环。这个循环的目的是遍历消息队列,以找到合适的位置将新消息 msg 插入队列中。
  3. 循环的条件是 for (;;),这将创建一个无限循环,直到内部的某个条件满足而跳出循环。
  4. 在每次循环迭代中,代码会做以下事情:
    • 将当前消息 p 的引用赋值给 prev,这样可以记录下前一个消息。
    • 将 p 移动到下一个消息(p = p.next)。
  5. 然后,代码检查两个条件:
    • 如果当前消息 p 为空,意味着已经遍历了整个队列,或者队列只有一个消息且当前消息是最后一个消息。
    • 如果传入的触发时间 when 小于队列中当前消息 p 的触发时间 p.when。
  6. 如果满足以上任一条件,循环会被中断,这表示找到了合适的位置将新消息 msg 插入到消息队列中。
  7. 在循环的每个迭代中,代码还会检查以下条件:
    • 如果需要唤醒队列(needWake = true),并且当前消息 p 是异步消息(p.isAsynchronous() 返回 true),则将 needWake 设置为 false。这个步骤可能是为了控制是否需要在队列中插入异步消息时唤醒队列。
  8. 一旦找到了合适的位置,代码会执行以下操作:
    • 将新消息 msg 的 next 指针指向当前消息 p,这相当于将新消息插入到当前消息 p 之前。
    • 将前一个消息 prev 的 next 指针指向新消息 msg,以确保队列中消息的连接关系正确。
  9. 循环结束后,新消息 msg 已经被插入到队列的合适位置,保持了消息队列的有序性。

总之,这段代码的目的是在消息队列中将新消息插入到适当的位置,以保持消息的时间顺序。在找到合适位置时,会根据一些条件来决定是否需要唤醒队列,这可能与队列的处理机制相关。

此外,如果当前消息队列被阻塞(mBlocked 为 true),则将标志 needWake 设置为 true。这是为了确保在需要唤醒队列以处理消息的情况下,能够在适当的时候执行唤醒操作。唤醒队列的操作可能涉及到一些底层机制,具体如何唤醒可能需要查看更多上下文代码。

总的来说,这段代码逻辑的目的是在特定条件下将新消息插入到消息队列的头部,并根据当前队列的阻塞状态决定是否需要唤醒队列以确保消息能够被及时处理。

主线程 取出 MSG

​ android.os.Looper#loop ->
​ android.os.MessageQueue#next ->
​ android.os.Handler#dispatchMessage ->
​ android.os.Handler#handleMessage

主线程中的 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
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
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

// Install selective syscall interception
AndroidOs.install();

// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);

Environment.initForCurrentUser();

// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);

// Call per-process mainline module initialization.
initializeMainlineModules();

Process.setArgV0("<pre-initialized>");

Looper.prepareMainLooper();

// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

与 handler 相关的关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
...

Looper.prepareMainLooper();
...

if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
···

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

准备 Looper

1
Looper.prepareMainLooper();

android.os.Looper#prepareMainLooper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. See also: {@link #prepare()}
*
* @deprecated The main looper for your application is created by the Android environment,
* so you should never need to call this function yourself.
*/
@Deprecated
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

android.os.Looper#prepare(boolean)

1
2
3
4
5
6
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));
}

准备 Handler

1
2
3
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

android.app.ActivityThread#getHandler

1
2
3
4
@UnsupportedAppUsage
public Handler getHandler() {
return mH;
}

android.app.ActivityThread#mH

1
2
@UnsupportedAppUsage
final H mH = new H();

开启循环

1
Looper.loop();
重点:android.os.Looper#loop
#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
26
27
28
29
30
31
32
33
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;

// 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);

me.mSlowDeliveryDetected = false;

for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}

这段代码是 Android 框架中 Looper 类的 loop() 方法的实现,负责在当前线程的消息循环中执行消息的分发和处理。

  1. 首先,通过 myLooper() 获取当前线程的 Looper 实例 me,如果没有 Looper 实例则抛出异常,表示未调用 Looper.prepare() 来准备 Looper。
  2. 检查 me.mInLoop,如果当前线程已经在消息循环中,则打印警告日志。
  3. 将 me.mInLoop 标志设置为 true,表示当前线程正在消息循环中。
  4. 使用 Binder.clearCallingIdentity() 来清除当前线程的调用标识,然后再次调用它并将返回的标识 ident 保存下来。
  5. 获取一个可能的系统属性覆盖值 thresholdOverride,用于调整慢分发的阈值。
  6. 将 me.mSlowDeliveryDetected 设置为 false,用于标记是否检测到慢投递。
  7. 进入一个无限循环,不断地执行消息分发和处理。
  8. 在循环中,调用 loopOnce(me, ident, thresholdOverride) 来执行一次消息分发。如果返回值为 false,表示没有更多的消息需要分发,退出循环。

总之,这段代码描述了 Android 中消息循环的核心逻辑。它会在一个无限循环中,不断地从消息队列中获取消息并执行消息分发和处理,直到没有更多的消息需要处理为止。在循环中,还会检查是否有慢分发阈值的系统属性覆盖,并根据需要清除调用标识。如果发现当前线程已经在消息循环中,则会打印警告信息。

#loopOnce
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
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;
}

// 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);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;

final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;

if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}

final long dispatchStart = needSt
, ,