目录
大家有没有发现,我们在Android开发过程中,很少遇到过多线程并发的问题,这个就得益于Android为我们提供的线程间通信工具 Handler了,所以我们要了解它是怎么实现跨线程通信的。
Handler的使用
我们首先知道Handler是怎么用的,再去剖析其核心源码。
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTvMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
@Override
protected void onStart() {
super.onStart();
new Thread(() -> {
try {
// 模拟耗时操作
Thread.sleep(5000);
// 获取 Message 实例对象
Message msg = Message.obtain();
// 设置 Message 对象的识别内容
msg.what = Constants.MSG_UPDATE_TEXT;
// 通过 Handler 把消息发送出去
handler.sendMessage(msg);
} catch (InterruptedException e) {
Log.e(TAG, "onStart: InterruptedException");
}
}).start();
}
private void initView() {
mTvMain = findViewById(R.id.tv_main);
}
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case Constants.MSG_UPDATE_TEXT:
mTvMain.setText("已完成更新操作");
}
}
};
}
接下来我们就按照上面代码案例去剖析Handler源码,顺序依次是Handler初始化、
Handler初始化
最终会调用如下方法:
/*
* 将此标志设置为 true 以检测扩展此 Handler 类的匿名类、本地类或成员类。 这种类可能会造成泄漏。
*/
private static final boolean FIND_POTENTIAL_LEAKS = false;
// 如果我们没有调用Looper.prepare(),sThreadLocal.get() 将返回null
// @UnsupportedAppUsage 不让我们开发者使用
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final Looper mLooper;
final MessageQueue mQueue;
// @UnsupportedAppUsage 不让我们开发者使用
final Handler.Callback mCallback;
final boolean mAsynchronous;
public Handler(@Nullable Handler.Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
// 如果我们创建的Handler类对象是匿名类,或者是成员类(内部类)、或者局部类(方法中创建的类)并且该类不是静态的
// 就有内存泄漏的风险。
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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
/**
* 返回与当前线程关联的 Looper 对象。 如果调用线程未与 Looper 关联,则返回 null。
*
* @return Looper对象
*/
public static @android.annotation.Nullable
Looper myLooper() {
return sThreadLocal.get();
}
这里我们重点关注一下 mLooper = Looper.myLooper(); 这行代码,myLooper的实现是:sThreadLocal.get();这里我们就要说说 ThreadLocal了,可参考我的另一篇博文:并发编程基础(二)—— ThreadLocal及CAS基本原理剖析
其实说白了,ThreadLocal 正如其名,它就是一个线程本地副本,我们的线程内部会有一个ThreadLocal.ThreadLocalMap的成员:
再来看看具体的get方法:
/**
* 返回此线程中 ThreadLocalMap成员以ThreadLocal为key对应的值(Object类型),如果ThreadLocalMap成员为null,
* 则首先将其初始化,调用 {@link #initialValue} 。
*
* @return 返回当前线程的中 ThreadLocal 对应的值
*/
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的 ThreadLocal.ThreadLocalMap 成员
ThreadLocalMap map = getMap(t);
// 如果map不为空
if (map != null) {
// 通过 ThreadLocal 获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);// 这里的this就是当前线程副本 ThreadLocal 的实例
// 如果 Entry 不为空,返回它的 value 属性的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
//否则设置初始值
return setInitialValue();
}
上面的代码包含了 ThreadLocalMap.Entry:
我们发现 Entry 的构造函数包含 ThreadLocal 和 Object,而它又是 ThreadLocalMap 的内部类,那我们就可以理解为 ThreadLocalMap 是以 ThreadLocal 为 key,任意对象为 value 的键值对结构(Map结构),即是一一对应的。
既然有 get 方法,那就有 set 方法了,我们发现在 Looper.prepare() 调用了 set:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
// 如果已经给当前线程的 ThreadLocal 设置过 一个Looper,则抛出异常,这就保证了 一个 ThreadLocal 只对应一个 Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 否则 new 一个 Looper对象,并设置给当前线程的 ThreadLocal
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 的构造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
如上代码通过判断 sThreadLocal.get() 是否为 null 的逻辑,保证了每一个线程的 sThreadLocal 只会调用一次 set 方法把 sThreadLocal 和Looper 绑定起来,而 sThreadLocal 又是static final的,那也就是说一旦线程调用了 Looper 的方法, sThreadLocal 就会被赋值,并且一旦赋值就不可更改,说明 Looper 与 ThreadLocal 是一一对应的。而且我们在 Android 源码中全局搜索 MessageQueue 的构造方法,发现只有在 Looper 的构造方法中调用了
new MessageQueue(quitAllowed),并且 MessageQueue 的构造方法是包管理权限,也就是说我们普通开发者是不能调用的,那也就说明了通过 Looper 构造方法唯一创建 MessageQueue 对象,实现了 Looper 和 MessageQueue 的一一对应。再结合上面我们对 ThreadLocal 的学习,我们知道了 一个线程对应唯一的 ThreadLocal , 那么就说明 Android的 Handler 机制中,Thread、ThreadLocal、Looper、MessageQueue这四者是一一对应。
发送消息
Handler的主要方法
调用流程:
Handler.sendMessage() --> Handler.sendMessageDelayed()--> Handler.sendMessageAtTime()--> Handler.enqueueMessage()--> MessageQueue.enqueueMessage()
可以看到最终会调用 MessageQueue.enqueueMessage() 将 Message 插入队列。

那么接下来我们剖析enquque方法:
Message next; // Message的成员
Message mMessages;// MessageQueue 的成员,可以看作是队列的队头
boolean enqueueMessage(Message msg, long when) {
// 省略不是很重要的代码
// 加锁保证线程安全,防止多个线程同时给MessageQueue中插入消息
synchronized (this) {
// 省略不是很重要的代码
// 给要插入消息队列(MessageQueue)中的消息指定延迟时间,也就是在多久之后处理此消息
msg.when = when;
// 把消息队列的队头赋值给 p
Message p = mMessages;
// 是否唤醒消息队列
boolean needWake;
// 最开始,消息队列肯定是空的。那如果当前消息队列中没有消息,或者我们插入的消息的等待时间是0,那我们就把消息插入,
// 并让其指向null;
// 或者消息队列中有一个即将处理的消息,而我们插入的消息等待时间小于即将要处理的消息,那就把我们插入的消息放在其前面。
if (p == null || when == 0 || when < p.when) {
// 插入的消息的下一个消息指向p,p可能为null
msg.next = p;
// 把我们要插入的消息赋值给消息队列的队头,实现插入队列操作
mMessages = msg;
// 当消息队列中没有消息时,就会阻塞,此时 mBlocked为 true
needWake = mBlocked;
}
// 这种情况是消息队列中有 N多个消息了
else {
// 插入队列中间。 通常我们不必唤醒消息队列,除非队列头部有屏障,并且消息是队列中最早的异步消息。
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 通过死循环,不停地比较我们要插入的消息与队列中已有的消息
for (;;) {
// 当前消息队列中用于作比较的消息作为前一个消息
prev = p;
// 然后让 prev指向 p(p指向p.next)
p = p.next;
// 从消息队列中的第一个消息开始遍历,直到消息队列的末尾即null值,方可跳出循环;
// 或者要插入的消息等待时间小于当前我们正在做比较的消息,此时也跳出循环。
if (p == null || when < p.when) {
break;
}
}
// 插入消息
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
return true;
}

处理消息
入队我们讲完了,下来该讲出队了。出队我们是通过 Looper.loop()实现:
/**
* 在当前线程中运行消息队列。 请务必调用 {@link #quit()} 来结束循环。以下代码省略不必要(看不懂)的代码进行解析
*/
public static void loop() {
// 获取当前线程的 Looper 对象
final Looper me = myLooper();
if (me == null) {
// 这个异常在我们初学者会经常遇到,因为初学者总是忘记在子线程调用 Looper.prepare()
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 获取当前线程的 MessageQueue 对象
final MessageQueue queue = me.mQueue;
for (; ; ) {
// 取出消息 --- 出队
Message msg = queue.next(); // 可能会阻塞,当MessageQueue没有消息是,会调用nativePollOnce(ptr, -1);一直挂起
if (msg == null) {
// 没有消息表示消息队列正在推出
return;
}
try {
// target是Message中的 Handler 成员
msg.target.dispatchMessage(msg);
} catch (Exception exception) { }
finally { }
// 回收可能正在使用的消息
msg.recycleUnchecked();
}
}
loop方法中包含一个死循环,通过 MessageQueue.next() 不停地取出消息:
/**
* 消息出队,同样省略 N多行不是很重要的(看不懂的)代码
*
* 尝试检索下一条消息, 如果找到返回。
*
* @return 返回即将要被处理的消息
*/
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
// 调用 native 方法睡眠 nextPollTimeoutMillis 这么多毫秒,如果该值为-1,则会无限等待
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 自启动以来的非睡眠正常运行时间毫秒数。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;// 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 = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 下一条消息已就绪,等待被处理,mBlocked置为 false
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
// 返回一个消息等待处理
return msg;
}
} else {
// 没有消息的情况
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
// mIdleHandlers是一个ArrayList,通过 addIdleHandler 添加,一般我们不会调用此方法,
// 所以大多数情况下 mIdleHandlers.size()是0
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 没有要运行的空闲 Handler ,循环并等待更多时间 ,所以大多情况下是一直阻塞的,这也就解释了为什么我们的
// Activity 显示出来之后,我们只要一直亮屏,它就不会结束,因为ActivityThread的main方法调用了Looper.loop,
// 使得程序一直挂起。
mBlocked = true;
continue;
}
}
}
}
最终调用 Handler.dispatchMessage(),而 Handler.dispatchMessage() 就会回调 handleMessage()。
MessageQueue的阻塞和唤醒
阻塞
其实在上面的代码示例中,我已经讲过在调用 MessageQueue.next() 时,会调用 nativePollOnce(ptr, nextPollTimeoutMillis) 实现阻塞,当 nextPollTimeoutMillis 是-1时,会一直阻塞。没明白的同学再好好看看上面的代码(一定要看注释)。
唤醒
在MessageQueue.enqueueMessage() 中有这样一段逻辑:
// 是否需要唤醒
boolean needWake;
// 如果消息队列没有消息,那么 mBlocked 就为 true,那我插入消息时就得唤醒消息队列
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// 如果 mBlocked 为true,即队列阻塞,则需要唤醒
needWake = mBlocked;
}
if (needWake) {
// 调用 native 方法唤醒队列
nativeWake(mPtr);
}
// 没有 Handler 要处理时,或者可以理解为消息队列中没有消息时,消息队列就会阻塞
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
上面的代码意思是说,在 MessageQueue 中没有 Message 时,它就会阻塞,此时我们插入 Message,就会唤醒 Message。
Handler对我们开发者的启发
亮点一
/**
* 在此处处理系统消息。
*/
public void dispatchMessage(@NonNull Message msg) {
// 如果 Message 自己有callback,就调用 Message.callback.run()
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果我们创建 Handler 时给 Callback 赋值了,就走这里
if (mCallback != null) {
// 当 Callback 处理完消息之后,我们可以根据返回值确定,要不要走最后面的 handleMessage(msg);
// 这个就与我们的View事件分发机制有些相似点了,根据返回值决定事件是否继续往下分发
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
/**
* 可以在实例化 Handler 时使用回调接口,以避免必须实现自己的 Handler 子类。
*/
public interface Callback {
/**
* @param msg 一个{@link android.os.Message Message} 对象实例
* @return 如果不需要进一步处理,则为 true
*/
boolean handleMessage(@NonNull Message msg);
}
上面代码就体现面向对象的封装思想,Message 封装了自己的 Callback ,如果 Message 是设置了它自己的 Callback,就回调自己的 callback.run();同样 Handler 封装了自己的 Callback ,如果 Handler 是设置了它自己的 Callback, 就回调自己的 handleMessage(),并且我们可以根据返回值决定要不要执行 Handler 子类重写的 handleMessage()。这样使得程序很灵活,有点像责任链模式。
亮点二
大家有没有想过,Handler 调用了 dispatchMessage 之后,就把消息出来完了,那消息是怎么回收的?
其实在 Looper.loop() 中,最终会调用 Message.recycleUnchecked() 进行所谓的消息回收(其实消息并未被回收),我们来看源码:
public static final Object sPoolSync = new Object();
/**
* 回收可能正在使用的消息。 处理排队的消息时,由 MessageQueue 和 Looper 在内部使用。
*/
@UnsupportedAppUsage
void recycleUnchecked() {
// 将消息标记为正在使用,同时保留在回收对象池中。清除所有其他详细信息。
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.obtain():
/**
* 从消息池中返回一个 Message 实例。 避免我们在很多情况下创建新的消息对象。
*/
public static Message obtain() {
synchronized (sPoolSync) {
// 消息池队头不为空
if (sPool != null) {
// 队头赋值给要返回的消息对象
Message m = sPool;
// 队头指向 m 的下一个节点
sPool = m.next;
// m 的下一个节点置空
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
我们发现上述代码跟我们登机之前做安检的场景很像,Message 就好比是放行李的盒子,Message的成员what、arg1、arg2、objtct就相当于是行李,当我们过完安检之后,盒子不会被丢弃,而是放在安检门口以备下一个过安检的人使用,这好比我们的消息池,其实这里所用的就是享元模式。
那么这样做有什么好处,从内存优化的角度思考,通过 Message.obtain() 获取消息大大减小了 new Message() 的调用,也就减少了连续内存空间被过度破坏,即不至于过度碎片化,也就是内存中连续空间更多了,那OOM出现的概率就小了。可能有同学说GC是干什么吃的,那大家有没有想过,既然 GC会帮我们回收垃圾,释放内存,为什么还会出现OOM。其实 GC即便用了标记整理算法,使得内存空间连续,但是GC线程工作的时候,会STW(stop the world),即其他所有线程都得挂起。而且我们不断地new 对象,又不断地触发 GC,会产生内存抖动,从而导致卡顿,所以从性能优化的角度来讲,我们尽量避免不必要的内存开销。
Looper什么时候推出
我们如果在子线程中创建 Looper 经常会有内存泄漏的问题,因为大部分同学都没有释放 Looper。那怎么办释放呢?通过调用 Looper.quitSafely() 或者 Looper.quit()
/**
* 安全退出Looper。
* 处理完消息队列中所有剩余的消息后,立即终止 {@link #loop} 方法。 但是,在 loop 终止之前,将不会传递未来到期的待处理的延
* 迟消息。在要求 Looper 退出后,任何向队列发布消息的尝试都将失败。 例如,{@link Handler#sendMessage(Message)} 方法
* 将返回 false。
*/
public void quitSafely() {
mQueue.quit(true);// 安全退出传的参数是 true,而Looper.quit()传的参数是 false
}
最终会调用到MessageQueue.quit():
/**
* 退出Looper
*
* @param safe 是否需要安全退出
*/
void quit(boolean safe) {
// 如果不允许退出,会抛异常,ActivityThread中的Looper就不允许退出。
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
// 如果正在退出,则return
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
// 安全退出
removeAllFutureMessagesLocked();
} else {
// 不安全退出
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
安全退出的实现:
private void removeAllFutureMessagesLocked() {
// 获取当前时间
final long now = SystemClock.uptimeMillis();
// 消息队列的队头赋值给 p
Message p = mMessages;
// 如果队头存在
if (p != null) {
// 如果队头延迟时间大于当前时间,移除所有消息
if (p.when > now) {
removeAllMessagesLocked();
} else {// 继续判断,取队列中所有大于当前时间的消息
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
// 将所有所有大于当前时间的消息回收,延迟时间小于当前时间的消息即使消息队列退出了,仍然会继续被取出执行
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
不安全退出的实现:
/**
* 移除消息队列所有消息,包括延迟时间小于当前时间的消息
*/
private void removeAllMessagesLocked() {
Message p = mMessages;
// 轮循队列中所有 Message对象,一一缓存到消息池中
while (p != null) {
Message n = p.next;
// 缓存到消息池中
p.recycleUnchecked();
p = n;
}
// 队头置空
mMessages = null;
}
Handler常见面试题
1.一个线程有几个 Handler?
在一个线程中我们可以创建多个 Handler。
2.一个线程中有几个 Looper?是如何保证的?
一个线程中只有一个 Looper;
我们在线程中使用 Looper之前,先要调用 Looper.preprare,而prepare 中用到了 ThreadLocal,ThreadLocal是线程本地副本,是每个线程独有的成员,线程就是通过它实现线程数据隔离的,并且 ThreadLocal 存储数据是通过其内部类 ThreadLocalMap中的内部类 Entry 存储的,Entry 的构造方法中包含两个成员,ThreadLocal 和 Object,所以在 Handler机制中,可以把 ThreadLocalMap 理解为以 ThreadLocal 为 key,Looper为 value的键值对。然后我们发现 Looper中的 ThreadLocal 成员是被 static final 修饰的,那也就是说 Looper 被加载时,它的成员 ThreadLocal 就被初始化了,且不可更改。那就保证了 ThreadLocal 的唯一性,那我只要再保证,Looper 的唯一性,就可以说明在 Handler机制中,Thread、Looper、ThreadLocal三者是一一对应的 。我们发现 prepare 方法中会先判断是否给 ThreadLocal 已经设置过值,如果没有设置过,new Looper()设置给它,否则会抛出异常。这就说明了 ThreadLocal 和 Looper 也是一一对应的了。
3.Handler内存泄漏原因? 为什么其他的内部类没有说过有这个问题?
4.为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?
5.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
6.既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?取消息呢?
很简单,加 synchronized 锁
7.我们使用 Message 时应该如何创建它?
Message.obtain();因为它采用享元模式,重复利用回收的消息,大大减少new Message() 的概率,从内存优化的角度看,减少不必要的内存开销,可以有效避免内存过度碎片化,从而降低出现OOM的概率。
8.Looper死循环为什么不会导致应用卡死
某些题目答案过段时间再公布, 大家开动聪明的小脑袋先思考,欢迎评论区与我交流,文章有不准确之处,还望指正 ヾ( ̄ー ̄)X(^▽^)ゞ