一、引言
Handler 在安卓中的地位是不言而喻的,几乎维系着整个安卓程序运行的生命周期,但是这么重要的一个东西,我们真的了解它吗?下面跟随着我的脚步,慢慢揭开Hanler的神秘面纱吧!
本文将介绍Handler 的运行机制、MessageQueue、Looper 的关系,ThreadLocal,以及Handler 导致的内存泄漏问题
二、Handler 系统组成概览
在 Handler
的源码中,主要涉及以下核心组件:
Message
:封装消息的数据结构。MessageQueue
:存储Message
的队列,内部是单链表。Looper
:负责循环读取MessageQueue
并分发消息。Handler
:对外提供sendMessage()
、post()
发送消息,并处理MessageQueue
中的消息。
它们之间关系如下图所示:
三、Handler
的创建
当 Handler
被创建时,它会绑定当前线程的 Looper
:
public Handler() {
this(Looper.myLooper(), null, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
最终调用:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,
boolean shared) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
mIsShared = shared;
}
mLooper
通过Looper.myLooper()
获取当前线程的Looper
。mQueue
由Looper
提供,确保所有Handler
在同一个Looper
线程内共享MessageQueue
。
重点:主线程默认初始化
Looper
,但子线程默认没有,需要手动Looper.prepare()
。如果一定要在子线程中使用,推荐使用
HandlerThread
,比于手动创建Looper
,HandlerThread
封装了Looper
的创建和管理逻辑,代码更加简洁,也更易于维护。同时,HandlerThread
有自己独立的消息队列,不会干扰主线程或其他线程的消息处理。
四、sendMessage()
如何发送消息
当我们调用 sendMessage()
时:
handler.sendMessage(msg);
实际上调用:
public boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
最终:
public boolean sendMessageDelayed(Message msg, long delayMillis) {
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
最终调用 enqueueMessage()
:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 绑定 Handler
return queue.enqueueMessage(msg, uptimeMillis);
}
@UnsupportedAppUsage
/*package*/ Handler target;
也就是说 Message 引用了 Handler,这也为内存泄漏埋下伏笔
五、MessageQueue
插入消息
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// 插入 MessageQueue
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;
}
enqueueMessage
方法负责将消息按照时间顺序正确地插入到单链表结构的队列中,按 when
进行排序。
六、Looper
如何处理消息
Looper.loop() 读取消息
public static void loop() {
for (;;) {
Message msg = queue.next(); // 取出消息
//...
msg.target.dispatchMessage(msg); // 交给 Handler 处理
}
}
MessageQueue.next()
Message next() {
// 检查消息队列是否已销毁,若销毁则返回 null
if (mPtr == 0) return null;
int nextPollTimeoutMillis = 0;
for (;;) {
// 若有超时时间,刷新 Binder 待处理命令
if (nextPollTimeoutMillis != 0) Binder.flushPendingCommands();
// 阻塞线程,等待新消息或超时
nativePollOnce(mPtr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message msg = mMessages;
// 若为屏障消息,找下一个异步消息
if (msg != null && msg.target == null) {
do { 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 {
// 若消息到处理时间,从队列移除并返回
mMessages = msg.next;
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 若无消息,一直阻塞
nextPollTimeoutMillis = -1;
}
// 若消息队列正在退出,释放资源并返回 null
if (mQuitting) {
dispose();
return null;
}
}
}
}
nativePollOnce()
让当前线程进入阻塞状态,直到有新的消息到来或者超时
nativePollOnce()
的主要功能是:
- 线程阻塞:让当前线程进入等待状态,避免空转消耗CPU资源
- 事件唤醒:当有新消息到达或超时发生时,立即唤醒线程处理
- Native 层集成:与 Linux 的 epoll 机制对接,实现高效I/O多路复用
void nativePollOnce(long ptr, int timeoutMillis)
ptr
:指向 Native Looper 对象的指针(C++对象地址)
timeoutMillis 的含义:
- 如果 timeoutMillis > 0
epoll_wait
最多阻塞 timeoutMillis 毫秒,期间如果有事件发生,则提前返回。
- 如果 timeoutMillis == 0
epoll_wait
立即返回(非阻塞)。
- 如果 timeoutMillis < 0
epoll_wait
无限等待,直到有事件触发。
最终调用了 Linux epoll 机制 来监听消息事件。
七、nativePollOnce 方法调用流程
Java 层调用
// MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis);
JNI 本地方法,由 MessageQueue
调用,用于等待消息。
在 MessageQueue.next()
方法中:
// MessageQueue.java
nativePollOnce(mPtr, nextPollTimeoutMillis);
它的作用是:
- 如果
MessageQueue
里有消息,立即返回。 - 如果没有消息,则阻塞,直到有新的消息到来或
timeoutMillis
超时。
JNI 层调用
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
MessageQueue* mq = reinterpret_cast<MessageQueue*>(ptr);
mq->pollOnce(timeoutMillis);
}
将 Java 传来的 mPtr
转换成 MessageQueue*
对象,并调用 pollOnce()
方法。
Native 层 pollOnce()
在 MessageQueue.cpp
:
void MessageQueue::pollOnce(int timeoutMillis) {
mLooper->pollOnce(timeoutMillis);
}
调用了 Looper::pollOnce()
,进入 消息轮询 逻辑。
Looper 的 pollOnce()
在 Looper.cpp
:
int Looper::pollOnce(int timeoutMillis) {
return pollInner(timeoutMillis);
}
这里调用 pollInner(timeoutMillis)
,它的核心逻辑是 使用 epoll_wait()
监听事件。
epoll
监听消息事件
pollInner(timeoutMillis)
的核心逻辑:
int Looper::pollInner(int timeoutMillis) {
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
if (eventCount > 0) {
for (int i = 0; i < eventCount; i++) {
// 处理事件
}
}
}
其中:
mEpollFd
是epoll
文件描述符,用于监听多个文件描述符(FD)。epoll_wait()
会 阻塞当前线程,直到:- 有 新消息可读
- 有 文件描述符事件触发
- 超时(
timeoutMillis
毫秒后自动返回)
到这里,我们清楚了 nativePollOnce
的主要作用是等待新消息到达消息队列。当调用这个方法时,如果当前消息队列中没有需要立即处理的消息,线程会被阻塞,从而释放 CPU 资源,直到有新消息到来或者发生其他唤醒条件。
那么 epoll_wait()
如何监听消息?
epoll_wait() 监听哪些事件?
MessageQueue 的 pipe(管道):当 Handler
发送消息时,写入 pipe,触发 epoll
事件。
输入事件:当用户触摸屏幕或按键时,触发 epoll
事件。
文件描述符(FileDescriptor):例如 Binder
进程间通信(IPC)事件。
等等…
消息如何触发 epoll?
Handler.sendMessage()
会向MessageQueue
写入数据:
write(mWakeEventFd, "W", 1);
-
epoll_wait()
监听到pipe
有数据,返回。 -
Looper
处理新消息,Java 层Handler
开始执行handleMessage()
。
epoll_wait
阻塞等待wakeFd
上的可读事件,当有数据写入wakeFd
,epoll_wait
返回,线程被唤醒,这里并不关心写入wakeFd
的具体数据是什么,只关心可读事件的发生。
pipe 的作用
让 Handler.sendMessage()
触发 epoll
事件,立即唤醒 Looper。
至此,综上,我们可以知道 epoll_wait() 只负责等待事件,不会提前返回“第一条消息”,它只会返回“有事件触发”的信号,具体执行哪个消息是 MessageQueue.next()
的逻辑,它会选择最早应该执行的消息,这就是 Handler
的阻塞唤醒的核心逻辑所在!
八、Handler 处理消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
msg.callback.run();
} else {
handleMessage(msg);
}
}
最终执行:
@Override
public void handleMessage(Message msg) {
// 需要用户实现
}
九、核心组件之间的关系
Thread
└── ThreadLocal<Looper>
└── Looper
└── MessageQueue
└── Message1 → Message2 → ...
↑
Handler
Handler
持有对MessageQueue
的引用(间接通过Looper
)因为Handler中的MessageQueue
是从Looper
中获取的;
public Handler(@Nullable Callback callback, boolean async) {
//..
mQueue = mLooper.mQueue;
//..
}
- 每个线程通过 ThreadLocal 绑定自己的 Looper;
- Looper 管理其对应的 MessageQueue;
这样它们的关系就清晰了,每个线程只有一个Looper(是由ThreadLocal确保的),可以有多个Handler。
public final class Looper {
// 线程本地存储,每个线程一个Looper实例
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static void prepare() {
prepare(true);
}
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));
}
}
关于ThreadLocal的详细介绍可以看这篇文章:深入剖析Java中ThreadLocal原理
十、内存泄漏问题分析及解决方案
我们都知道判断内存泄漏的依据是:短生命周期对象是否被长生命周期对象引用!既然使用Handler
不当会导致内存泄漏,那么我们只需要找到被引用的源头,然后去解决。
Handler 导致内存泄漏的完整引用流程
- 匿名内部类或非静态内部类的隐式引用:
众所周知,在Java中 匿名内部类或非静态内部类会持有外部类的引用,如下:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendEmptyMessageDelayed(0, 10000);
}
}
这里的mHandler
是一个非静态内部类。非静态内部类会隐式持有外部类(这里是MainActivity
)的引用。这意味着mHandler
对象中包含了对MainActivity
实例的引用。
- MessageQueue 对 Message 的持有:
在上面示例中,我们发送了一个延迟的Message,尽管只传了一个0,但是其内部也会封装为Message,这时候Handler 会将 Message
对象并将其发送到与之关联的MessageQueue
中,MessageQueue
会持有这个Message
对象,直到该消息被处理。
- Message 对 Handler 的持有:
由上面第四小节的sendMessage()
可知,在放入队列的时候,会将Handler
与 Message
关联:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 绑定 Handler
return queue.enqueueMessage(msg, uptimeMillis);
}
主要作用是,让Message
知道是从哪个Handler
发送的,并最终让那个Handler
的 handleMessage
去处理。
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@UnsupportedAppUsage
private static Looper sMainLooper; // guarded by Looper.class
//...
}
我们都知道,在主线程中,主线程的Looper
会一直运行下去(或者说 Looper
被 静态 ThreadLocal<Looper>
所引用),不能被停止,而MessageQueue
又被Looper
所引用,这就产生了一条完整的引用链:ThreadLocal<Looper>
- Looper
- MessageQueue
- Message
- Handler
- MainActivity
** 解决方案**
- 使用静态内部类 + WeakReference:
要解决内存泄漏,就是把引用链上任意一条引用断开,让GC不可达就行了,其实我们能操作的就只有 Handler
- **MainActivity
**这一条引用:
static class MyHandler extends Handler {
private final WeakReference<MyActivity> ref;
MyHandler(MyActivity activity) {
ref = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MyActivity activity = ref.get();
if (activity != null) {
// Safe to use activity
}
}
}
- 在 Activity 的
onDestroy()
中清除消息:
handler.removeCallbacksAndMessages(null);
其实,只要消息不是延迟很久或者反复堆积,就不会在 MessageQueue 中长时间滞留,从而也就不会延长 Handler 或其持有对象的生命周期。
想想,在实际开发中,谁会在Activity中延迟发送一个很长时间的消息,所以我们不必为 Handler
导致内存泄漏,过度紧张,稍微留意一下就可以避免了 😃
十一、最后
Handler 是 Android 消息机制的基础组成部分。通过对 Handler、Looper、MessageQueue 之间关系的理解,我们可以更深入掌握 Android 的线程模型与 UI 更新流程。
由于本人能力有限,并没有对 Handler
进行过度深入全面了解,比如同步屏障等,如果文章内容解读有误,还望不吝赐教。