目录
  1. 1. 简介
  2. 2. 源码分析
    1. 2.1. ActivityThread:
    2. 2.2. Handler:发送&接收消息
      1. 2.2.1. 构造函数
      2. 2.2.2. dispatchMessage
      3. 2.2.3. sendMessage
    3. 2.3. Looper: 轮询消息队列
      1. 2.3.1. 成员变量
      2. 2.3.2. prepare
      3. 2.3.3. prepareMainLooper
    4. 2.4. MessageQueue:用于存储消息管理消息的消息队列
      1. 2.4.1. enqueueMessage
    5. 2.5. 发送消息
  3. 3. ThreadLocal
    1. 3.1. ThreadLocal是如何发生内存泄露的?
    2. 3.2. 弱引用什么时候才会被回收?
  4. 4. 源码涉及开发模式
    1. 4.1. 享元模式
  5. 5. 总结
  6. 6. 剑指细节
    1. 6.1. Handler 的 post(Runnable) 与 sendMessage 的区别?
    2. 6.2. Looper.loop() 为什么不会阻塞主线程?
    3. 6.3. 延迟消息是如何得到保证的(Handler实现发送延迟消息的原理是什么)?
    4. 6.4. Handler 的 sendMessageDelayed/postDelayed 是如何实现的?
    5. 6.5. 为什么要通过Message.obtain() 方法获取Message对象?
    6. 6.6. 同步屏障 SyncBarrier 是什么?有什么用?
    7. 6.7. IdleHandler 是什么?有什么用?
    8. 6.8. 为什么非静态类的Handler会导致内存泄露,该如何解决?
    9. 6.9. 如何让子线程中弹出Toast?
重拾Android-【吃透源码系列】之Handler

简介

这一系列文章是专门研习底层源码原理的,本篇开始分析 Handler 的源码。

在开发中,我们经常会遇到在子线程中操作一些流程,当操作完毕后通过 Handler 发送数据给主线程,并通知主线程做相应操作的变更的场景。其背后原理: Handler 、Looper、MessageQueue 这三类想必大多数Android从业者都能说出其各自代表的功能实现。但是仅仅知道这个类的作用,却始终串联不起来其与底层源码的实现沟通,其实并不能算是完全理解,所以接下来要做的事就是从 应用启动 开始分析,摸清整个 Handler 的核心机制。

想象一下,在我们最亲近的身体里面,我们的眼睛所看,耳朵所听,肢体所摸,都是我们对外界作出反应的一个线程,它无时无刻不在感受着外界给他的信息作出反馈,我们的外表动作,比如:眨眼,抬手,打哈欠,等等…是我们大脑在不间断轮询各个感官器官所要表达的信息所做出的的一系列指令。细品一下,我们有这样的关系比对:

眼看耳听鼻闻 ≈ Handler

所看所听所闻 ≈ Message

眼耳鼻神经系统 ≈ sendMessage

大脑神经系统 ≈ Looper(如果大脑几乎啥都轮询不到了,那多半睡着了,同时符合先到先觉机制)

       Looper.prepare(人一出生就初始化,这一辈子也就初始化一次)

       Looper.loop(不断轮询)

神经元组织 ≈ MessageQueue

反馈神经 ≈ dispatchMessage

外表肢体 ≈ handlerMessage

综上对应我们接下来需要分析的源码类,如下:

源码分析

ActivityThread:

应用启动是从 ActivityThreadmain 方法开始,首先执行 Looper.prepare(),prepare 方法里 new 了一个 Looper 对象,在私有的构造方法里,创建了 MessageQueue 作为此 Looper 的对象成员变量,Looper 对象通过 ThreadLocal 和当前线程进行绑定。

public static void main(String[] args) {
...

Looper.prepareMainLooper(); // 初始化当前进程的 Looper 对象

...

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

...
Looper.loop(); // 开启无限循环

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

Handler:发送&接收消息

构造函数

// 这两个全局变量均是通过 Looper 来获取
@UnsupportedAppUsage
final Looper mLooper;

final MessageQueue mQueue;

public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}

mLooper = Looper.myLooper(); // ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ (1)
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆ (2)
mCallback = callback;
mAsynchronous = async;
}

dispatchMessage

public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

// 该空方法给创建Handler时进行覆盖
public void handleMessage(@NonNull Message msg) {
}

sendMessage

代码时序为:sendMessage -> sendMessageDelayed -> sendMessageAtTime

最终调用 enqueueMessage 将 消息Message 插入 消息队列MessageQueue(ActivityThread main方法中通过 Looper 创建的)

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);
}
// 跟踪 MessageQueue 源码分析
return queue.enqueueMessage(msg, uptimeMillis);
}

Looper: 轮询消息队列

成员变量

// 线程内部存储类 维护 Looper:在一个本地线程里,永远都只会维护自身的副本变量,互不干扰
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

@UnsupportedAppUsage
private static Looper sMainLooper; // guarded by Looper.class

private static Observer sObserver;

@UnsupportedAppUsage
final MessageQueue mQueue;

final Thread mThread;

prepare

// 确保每个线程只会有一个Looper 
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 新建 Looper 对象,并将其设置到 线程本地变量 中,两者 绑定。
sThreadLocal.set(new Looper(quitAllowed));
}

prepareMainLooper

public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// 从 线程本地变量 中取出 Looper对象
sMainLooper = myLooper();
}
}

public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

这里有一个很巧妙的处理机制,我们知道运行一个Java程序,其入口函数 main 方法执行完毕之后,程序就会停止运行,也就是进程自动终止。但是当我们启动一个应用程序,却发现只要我们不退出这个app,程序就可以一直操作运行。熟悉源码我们就会发现,这是因为 启动一个应用程序后,在其 main 主方法里,一直存在一个Looper对象,它在内部维护一个无限循环,保证 APP 进程持续进行

小结:

  • prepare 方法在一个线程中只会被调用一次,也就是说一个线程只会有一个 Looper。

  • Looper 的构造方法在一个线程中只能被调用一次。

  • 正是因为第二个原因,导致 MessageQueue 在一个线程中也就只会初始化一次。

loop

public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

...

for (;;) {
// 不断取出 MessageQueue 中的 Message
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

... ...
// 如果取出的 msg 不为null
try {
// 取出 target 对象 分发消息处理
// 追踪 Message 对象 得知 target -> Handler
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);
}
}

...
}
}

这里有一个问题:Handler是何时被设置为一个 Message 的 target 的?

MessageQueue:用于存储消息管理消息的消息队列

enqueueMessage

boolean enqueueMessage(Message msg, long when) {
// 如果 target 为空,则抛出异常(所以必须要 new Handler(),才能使用)
// 这里解释一下 消息屏障 处理,因为 Google 工程师想要确保我们的界面任何操作都不影响 view 的渲染
// 于是想出了一套屏障机制,view 在响应页面操作发送消息的时候,设置target为空
// 这样在 同步消息队列取消息 之前,先根据 target == null,遍历取出异步消息,进行消费,这样就有别于我们send的消息
// 如果允许程序猿随意处理 target 为空,干扰异步消息优先级出队消费,就会影响16ms view渲染机制
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) {
...

msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake; // 是否需要同步队列休眠 标记

// 按照 Message 的 when,有序插入 MessageQueue 中
// 如果 没接收到消息 || 插入的消息的when==0(立即插入)|| 插入的执行时间 < 当前处理消息的时间点
// 上面的判断条件,无非就是 判断一个队列的插入几个特殊场景(头、尾)
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;
}

...
}
return true;
}

发送消息

  • post/send Message

  • postSyncBarrier 同步屏障消息

    • message.target=null,这类消息不会真的被执行,它起到了flag的作用,MessageQueue在遍历消息队列时,如果对头同步屏障消息,那么会被if判空拦截达到忽略同步消息的目的,优先让异步消息执行。这就是它的目的,一般异步消息和同步屏障消息会一同使用。让重要的紧急的消息优先执行,在系统层分发处理用的较多。

    • 异步消息&同步屏障 使用场景

      • ViewRootImpl接收屏幕垂直同步信息事件用于驱动UI测绘
      • ActivityThread接收AMS的事件驱动生命周期
      • InputMethodManager分发软键盘输入事件
      • PhoneWindowManager分发电话页面各种事件

ThreadLocal

  • ThreadLocal提供了线程独有的局部变量存储能力,可以在整个线程存活的过程中随时取用,方便了一些逻辑的实现。
  • ThreadLocal不具备线程安全的能力

ThreadLocal是如何发生内存泄露的?

发生 内存泄露的场景是在 使用线程池。因为池子中的任务在执行完成之后,并不会被立刻回收,但是如果任务执行完成之后,我们手动将线程池中的线程置为空,那么被弱引用持有的TheadLocal就会被GC回收,但是这样就会导致Entry中的value被赋予强引用值,如果我们没有手动移除这个value,那么就很有可能造成内存泄露。

弱引用什么时候才会被回收?

当这个弱引用所持有的对象,它的强引用,也就是我们new出来的对象被置为空的时候——没有引用的时候,GC扫描到这个弱引用对象的时候才会被回收。如果没有被置为空,只能等到内存严重不足的时候,才会被回收。

源码涉及开发模式

享元模式

复用消息Message对象。(默认50个对象,回收复用recycleUnchecked,拷贝对象并初始化)

总结

handler.sendMessage 发送消息到 消息队列MessageQueue,然后 ActivityThread main()方法中的 looper.loop() 不断遍历消息队列消息队列 MessageQueue 里面的 Message,当 Message 达到了可执行时间(when) 开始执行,执行后 调用 Message 对象绑定的 target对象(Handler)来进行消息分发处理

至此,几大类里重要的方法均分析完毕,有关 Handler 的发送消息 和 消息处理流程也就相应结束。接下来,就是一些抠细节的地方,但也往往是细节,所以都不重视,导致面试的时候没有想到,得不偿失。

剑指细节

Handler 的 post(Runnable) 与 sendMessage 的区别?

代码序列:

post(Runnable) -> sendMessageDelayed(getPostMessage(Runnable)) -> sendMessageAtTime() -> enqueueMessage

-> Looper.loop # msg.target.dispatchMessage(msg);

上面分析了 dispatchMessage 方法,这里直达,可以看出,dispatchMessage 分两种情况:

1、如果 Message 的 Callback 不为 null,则优先选择 post(Runnable) 执行,之后则会调用 Runnable 的 run方法,这里 Runnabel只是一个回调接口,跟线程是没有关系的;

2、如果 Message 的 Callback 为 null,这种一般就会选择后者 sendMessage 来发送处理,之后会调用 Handler 的 handlerMessage 的方式处理接收消息

Looper.loop() 为什么不会阻塞主线程?

刚才我们分析过 Looper.loop() 是一个死循环方法的执行过程。但是UI线程却并没有阻塞,反而还能进行各种手势操作。这是因为在 MessageQueue 的 next 方法中,可以看到:

@UnsupportedAppUsage
Message next() {
...

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}

// native 方法:当调用此方法时,主线程会释放CPU资源进入休眠状态
// 直到下一条消息到达之前或者有事务发生
// 通过往 管道 写端写入数据来唤醒主线程工作
// 这里采用 epoll 机制:放弃CPU主权交给别的线程执行,因此只会阻挂起但不会阻塞主线程
nativePollOnce(ptr, nextPollTimeoutMillis);

// 当Looper 被唤醒后,会继续往下执行
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) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果上面检索出了消息,但是还没有到可以执行的时间
// 则更新 nextPollTimeoutMillis 也就是下一次循环需要阻塞的时间值(动态传递剩余时间,不剩余即可唤醒执行)
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 找到了需要处理的消息
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 {
// 如果没有找到消息,即队列为空,looper将进入永久休眠,知道新消息到达
nextPollTimeoutMillis = -1;
}

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

// 当队列消息为空时,也就是所有任务都处理完了,或者对头消息已经达到了可执行的时间点
// 此时派发通知Looper即将进入空闲状态
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);

}

// 注册 MessageQueue.IdleHandler,可以监听当前线程的Looper是否即将进入空闲状态
// 在主线程中可以监听这个事件来做延迟初始化,加载数据,日志上报等等
// 而不是有任务就提交,从而避免抢占重要资源。
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;

// 此时置为0,在这里既然监听到了线程的空闲,那么在这个 IdleHandler回调里,很有可能又会产生新的消息
// 为了让这个消息尽可能早的得到执行,所以此时epoll命令立刻唤醒,不需要休眠了。
nextPollTimeoutMillis = 0;
}
}

【答】
主线程确实是通过Looper.loop()进入了循环状态,也正因为这样,主线程才不会像我们一般创建的线程一样,当可执行代码结束之后,线程生命周期就终止了。

在主线程的 MessageQueue 没有消息时,便阻塞在了 MessageQueue.**next()** 中的 **nativePollOnce()**方法里,此时主线程会释放CPU资源进入休眠状态,直到新消息达到。所以主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

这里采用Linux的epoll机制,是一种IO的多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪(读或写就绪),则立刻通知相应程序进行读或者写操作拿到最新的消息,进而唤醒等待的线程。

延迟消息是如何得到保证的(Handler实现发送延迟消息的原理是什么)?

我们常用 postDelayed() 与 sendMessageDelayed() 来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过 sendMessageAtTime() -> enqueueMessage -> queue.enqueueMessage 这一系列方法将消息插入到MessageQueue中。所以并不是先延迟再发送消息,而是直接发送消息,再借助 MessageQueue的设计来实现消息的延迟处理的。原理如下:

首先通过 SystemClock.uptimeMillis + 需要延迟的时间 计算这个消息它应该执行的时间戳,然后借入native的epoll命令阻塞一段时间,超时之后自动恢复,然后继续往下检查执行,找出需要执行的符合条件的message。如果有,就拿出来处理,延时消息就是这么来的。

有关 nativePollOnce 的函数分析,可以参考 《深入理解Android:卷2》一文。

Handler 的 sendMessageDelayed/postDelayed 是如何实现的?

post 一类的方法发送的是 Runnable 对象,但是其最后还是会被封装成 Message 对象,将Runnable对象赋值给Message对象中的Callback变量,然后交由 sendMessageAtTime()方法发送出去。在处理消息时,会在dispatchMessage() 方法里首先被 handleCallback(msg) 方法执行,实际上就是执行Message对象里面的Runnable对象的run方法。

而sendMessage一类的方法发送的直接是Messgae对象,处理消息时,在dispatchMessage里优先级会低于handleCallback(msg)方法,是通过重写的 handleMessage(msg) 方法执行。

为什么要通过Message.obtain() 方法获取Message对象?

obtain方法可以从全局消息池中得到一个空的Message对象,提供了对象复用能力,避免重复创建对象造成的资源的浪费以及频繁GC的问题,这样可以有效节省系统资源。同时,通过各种obtain重载方法还可以得到一些Message的拷贝,或对Message对象进行一些初始化达到复用,可参考上面的涉及开发模式

同步屏障 SyncBarrier 是什么?有什么用?

在一般情况下,同步和异步消息处理起来没什么不同。只有在设置了同步屏障之后才会有差异。同步屏障从代码层面上看是一个Message对象,但是其target是为空,用以区分普通消息还是屏障消息。在 MessageQueue.next() 中如果当前消息是一个同步屏障,则跳过之后的所有同步消息,并遍历找到一个异步消息使其优先得到处理。但是这个api开发者是调用不了的。一般是供系统使用。比如:在ViewRootImpl的UI绘制流程中就有体现。

IdleHandler 是什么?有什么用?

当消息队列没有消息时调用 或者 如果队列中仍有待处理的消息,但都未到执行时间时(也会调用此方法)。用以监听主线程即将进入空闲状态,在这里可以进行一些数据懒加载、日志上传等业务操作。

为什么非静态类的Handler会导致内存泄露,该如何解决?

首先,非静态内部类、匿名内部类、局部内部类都会隐式的持有其外部类的引用(这个在字节码里能看到隐式的持有this),也就是说在Activity中创建的Handler会因此持有Activity的引用。

当我们在主线程使用Handler的时候,Handler会默认绑定这个线程的Looper对象,并关联其MessageQueue,Handler发出的所有消息都会加入到这个MessageQueue中。Looper对象产生的生命周期贯穿整个主线程的生命周期,所以当Looper对象中的MessageQueue里还有未处理完的Message时,因为每个Message都持有Handler的引用,所以Handler无法被回收,自然其持有的引用的外部类Activity也无法被回收,从而造成内存泄露。

如何让子线程中弹出Toast?

调用Looper.prepare 以及 Looper.loop(),但是切记线程任务执行完成后,需要手动调用 Looper,quitSafely() ,否则线程是不会结束的。

打赏
  • 微信
  • 支付宝

评论