Handler 机制
核心:
Handler:
是 Android 系统中用于线程间通信的核心机制之一,它允许你在不同线程之间发送和处理消息(Message)或可运行任务(Runnable)。
Looper:
- 和线程是一对一的关系; 主线程的Looper创建在Android的
ActivityThread
的main函数中通过Looper.prepareMainLooper()
创建的;
子线程中创建Looper调用的是Looper.prepare()
; 和主线程的区别是 主线程创建的Looper不能被quit, 子线程的Looper可以quit; - 循环器, 通过
loop()
方法处理Message的分发
Message:
是经过Parcelable序列化的消息对象 负责消息的承载 回收 和 复用
MessageQueue:
管理消息队列在Looper的初始化方法中创建,每个Looper对应一个MessageQueue, 通过enqueueMessage
插入消息到队列中, 判断是否激活线程, 线程激活后 looper通过MessageQueue的next方法获取消息, 如果有消息则返回,无消息标记线程为闲置
ThreadLocal:
1. 绑定当前线程 和 Looper 线程和Looper是 一一对应的关系; 2. 获取对应message加入队列时的uid(Message -> workSourceUid 检测电量时使用)
一 Handler类核心源码
- 构造函数
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());
}
}
// 获取当前线程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// 获取当前Looper的MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- 消息执行
所有的sendMessage方法最终都会调用到enqueueMessage方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 将当前Hadler对象赋值给Message的target属性
msg.target = this;
// message加入队列时的uid
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 消息插入队列
return queue.enqueueMessage(msg, uptimeMillis);
}
二 MessageQueue 部分源码
1. 插入队列方法源码和详解如下
// 将消息插入消息队列,when参数表示消息应该被处理的时间(基于SystemClock.uptimeMillis())
boolean enqueueMessage(Message msg, long when) {
// 检查消息是否有目标Handler,没有则抛出异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 同步代码块,保证线程安全
synchronized (this) {
// 检查消息是否已经被使用(防止重复使用同一个Message对象)
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 检查消息队列是否正在退出(如Looper已退出)
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) {
// 情况1:队列为空,或者新消息需要立即执行(when=0),或者新消息的执行时间早于队首消息
// 将新消息的next指向原来的队首
msg.next = p;
// 更新队首为新消息
mMessages = msg;
// 如果当前队列是阻塞状态,则需要唤醒
needWake = mBlocked;
} else {
// 情况2:新消息需要插入到队列中间
// 只有当队列头部有同步屏障且新消息是队列中最早的异步消息时才需要唤醒
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;
}
// 如果需要唤醒队列(mPtr是native层的指针,不为0表示队列有效)
if (needWake) {
nativeWake(mPtr); // 调用native方法唤醒队列
}
}
return true; // 插入成功
}
2. 关键方法 next()详解
在 MessageQueue
的 next()
方法中,有几个关键点展示了如何避免线程阻塞以及如何标记线程的状态为不繁忙。以下是详细的解释:
Message next() {
// 检查native层的消息队列指针,如果为0表示队列已退出/释放
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 初始化空闲处理器计数和轮询超时时间
int pendingIdleHandlerCount = -1; // -1表示第一次迭代
int nextPollTimeoutMillis = 0; // 0表示不阻塞
// 无限循环,直到获取到消息或退出
for (;;) {
// 如果有等待时间,先刷新Binder命令
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 调用native方法阻塞等待,ptr是native对象指针,nextPollTimeoutMillis是超时时间
nativePollOnce(ptr, nextPollTimeoutMillis);
// 同步块开始,保证线程安全
synchronized (this) {
// 获取当前时间(系统启动后的毫秒数,不包括深度睡眠时间)
final long now = SystemClock.uptimeMillis();
Message prevMsg = null; // 前一个消息指针(用于移除消息)
Message msg = mMessages; // 当前队列头消息
// 处理同步屏障情况(当msg.target == null时表示遇到同步屏障)
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; // 标记队列为非阻塞状态
// 从链表中移除该消息
if (prevMsg != null) {
prevMsg.next = msg.next; // 中间节点移除
} else {
mMessages = msg.next; // 队首移除
}
msg.next = null; // 断开消息的next引用
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); // 标记消息为"正在使用"状态
return msg; // 返回找到的消息
}
} else {
// 没有消息,设置无限等待
nextPollTimeoutMillis = -1;
}
// 检查是否正在退出
if (mQuitting) {
dispose(); // 释放native资源
return null;
}
// 第一次空闲时,检查是否需要运行IdleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 如果没有IdleHandler需要运行,则标记阻塞状态并继续循环
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// 准备IdleHandler数组
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} // 同步块结束
// 运行IdleHandler(此时不在同步块内)
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // 释放引用防止内存泄漏
boolean keep = false;
try {
// 调用queueIdle()方法,返回值决定是否保留该IdleHandler
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 如果返回false,则从列表中移除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置IdleHandler计数
pendingIdleHandlerCount = 0;
// 在处理IdleHandler期间可能有新消息到达,重置超时时间为0立即检查
nextPollTimeoutMillis = 0;
}
}
nativePollOnce(ptr, nextPollTimeoutMillis)
nativePollOnce(ptr, nextPollTimeoutMillis);
- 这是核心的阻塞操作。
nativePollOnce
是一个本地方法,它会根据nextPollTimeoutMillis
的值决定是否阻塞当前线程。- 如果
nextPollTimeoutMillis == 0
,则不会阻塞,立即返回。 - 如果
nextPollTimeoutMillis > 0
,则会阻塞指定的时间(以毫秒为单位)。 - 如果
nextPollTimeoutMillis == -1
,则会无限期地阻塞,直到有新消息到达。
- 如果
mBlocked
标志位
mBlocked = false;
// 和
mBlocked = true;
-
mBlocked
是一个布尔变量,用于标记线程是否处于阻塞状态。- 当线程从
nativePollOnce
返回并且没有找到可处理的消息时,mBlocked
被设置为true
,表示线程处于等待状态。 - 当找到可处理的消息时,
mBlocked
被设置为false
,表示线程不再阻塞,可以继续处理消息。
- 当线程从
- 计算
nextPollTimeoutMillis
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;
// 处理消息...
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
- 这里通过计算
nextPollTimeoutMillis
来控制线程的阻塞时间:- 如果下一个消息还没有到时间(即
now < msg.when
),则设置一个超时时间nextPollTimeoutMillis
,让线程在该时间后被唤醒。 - 如果没有更多消息,则将
nextPollTimeoutMillis
设置为-1
,使线程无限期地阻塞,直到有新消息到来。
- 如果下一个消息还没有到时间(即
- 处理空闲处理器(Idle Handlers)
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
- 在没有待处理的消息时,
MessageQueue
可能会运行一些空闲处理器(IdleHandler
)。如果没有任何空闲处理器需要运行,线程会被标记为阻塞状态 (mBlocked = true
) 并继续等待新消息。
- 返回消息时解除阻塞
mBlocked = false;
// 处理消息...
return msg;
- 当找到一条可以处理的消息时,
mBlocked
被设置为false
,表示线程不再阻塞,并且开始处理该消息。
总结
-
避免线程阻塞:通过
nativePollOnce(ptr, nextPollTimeoutMillis)
控制线程的阻塞行为。nextPollTimeoutMillis
决定了线程的阻塞时间,可能是有限的、无限的或立即返回。 -
标记线程状态为不繁忙:当找到可处理的消息时,
mBlocked
被设置为false
,表示线程不再阻塞并开始处理消息。否则,mBlocked
被设置为true
,表示线程处于等待状态。
这些机制共同作用,确保了 MessageQueue
在没有消息时不会占用 CPU 资源,同时能够高效地处理新到达的消息。
三 Looper 循环器核心源码
/**
* 运行消息队列的消息循环,必须在调用线程中已经通过Looper.prepare()创建了Looper
*/
public static void loop() {
// 获取当前线程关联的Looper对象
final Looper me = myLooper();
// 检查Looper是否存在,不存在抛出异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 检查是否已经在循环中(防止重复调用loop())
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
// 标记Looper为"正在循环"状态
me.mInLoop = true;
// 清除调用者身份信息,确保线程身份是本地进程的
Binder.clearCallingIdentity();
// 获取当前线程的身份标识
final long ident = Binder.clearCallingIdentity();
// 从系统属性中获取慢消息检测的阈值覆盖值(用于调试)
// 例如可以通过adb命令设置: adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "." // 当前进程UID
+ Thread.currentThread().getName() // 当前线程名
+ ".slow", 0);
// 重置慢消息检测标志
me.mSlowDeliveryDetected = false;
// 无限循环处理消息
for (;;) {
// 调用loopOnce处理单个消息,如果返回false表示循环应该结束
if (!loopOnce(me, ident, thresholdOverride)) {
return; // 退出循环
}
}
}
loopOnce函数详解: 消息分发的核心代码msg.target.dispatchMessage(msg);
msg.target指向的是当前创建Handler对象,也就是把消息给到Handler去分发
/**
* 处理单个消息的循环迭代
* @param me 当前线程的Looper对象
* @param ident 线程身份标识
* @param thresholdOverride 慢消息阈值覆盖值
* @return 是否继续循环 (false表示应该退出循环)
*/
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
// 从消息队列获取下一条消息(可能会阻塞)
Message msg = me.mQueue.next();
// 如果消息为null,表示消息队列正在退出
if (msg == null) {
return false; // 返回false让外层循环退出
}
// 获取日志打印器(用于调试消息分发过程)
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what);
}
// 获取全局观察者(用于监控消息分发)
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 = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting(); // 通知观察者消息分发开始
}
// 设置工作源UID(用于电池统计)
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
// 关键步骤:将消息分发给目标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);
}
}
// 慢投递检测逻辑
if (logSlowDelivery) {
if (me.mSlowDeliveryDetected) {
// 如果之前已检测到慢投递,检查队列是否已排空
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
me.mSlowDeliveryDetected = false;
}
} else {
// 检测并记录慢投递
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg)) {
me.mSlowDeliveryDetected = true; // 标记已检测到慢投递
}
}
}
// 慢处理检测逻辑
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
// 记录消息处理完成日志
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// 验证线程身份是否被更改
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
// 回收消息对象
msg.recycleUnchecked();
return true; // 继续循环
}
四 在Android的Handler机制中,为什么使用ThreadLocal来关联Thread和Looper,并且详细说明其设计原因
4.1 这样设计的原因:
// Looper.java 源码实现
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
public static void prepare() {
sThreadLocal.set(new Looper()); // 每个线程设置自己的 Looper
}
// ThreadLocal 存储结构示意
class Thread {
ThreadLocal.ThreadLocalMap threadLocals;
}
class ThreadLocalMap {
Entry[] table; // Entry extends WeakReference<ThreadLocal<?>>
}
// ActivityThread.java 中初始化主线程 Looper
public static void main(String[] args) {
Looper.prepareMainLooper(); // 内部使用 ThreadLocal
ActivityThread thread = new ActivityThread();
Looper.loop();
}
// ThreadLocal底层实现
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取线程的 threadLocals
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) return (T)e.value;
}
return setInitialValue(); // 首次访问初始化
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 直接返回线程私有字段
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
-
确保线程独享Looper
:每个线程的Looper都是该线程私有的,其他线程无法直接访问。这样每个线程的消息队列就是独立的,消息处理不会混乱。 -
无需全局查找
: 通过ThreadLocal,可以非常快速地获取当前线程的Looper,而无需维护一个全局映射表(如Map<Thread, Looper>)来记录线程与Looper的对应关系。这既提高了效率(避免了线程同步开销),也简化了设计。 -
安全访问
: 实际上,ThreadLocal内部使用了ThreadLocalMap,该映射以当前ThreadLocal实例为键,而不是Thread对象。但在每个线程内部,多个ThreadLocal变量互不干扰,所以不同线程之间不会相互影响。
4.2 深入ThreadLocal:
ThreadLocal的实现依赖于每个线程内部的一个私有变量threadLocals(类型为ThreadLocal.ThreadLocalMap)。当第一次调用ThreadLocal的set方法时,会初始化当前线程的threadLocals,并以当前ThreadLocal对象为键,传入的值为值,存储在这个Map中。在获取时,同样根据当前ThreadLocal对象去当前线程的ThreadLocalMap中取值。
在Looper中,sThreadLocal是静态的,所以整个进程只有一份。但是每个线程调用sThreadLocal.get()时,得到的是不同的Looper实例,因为每个线程的threadLocals中存放了以sThreadLocal(同一个对象)为键,本线程的Looper为值的条目。
这样设计的好处:
- 高效:每个线程直接通过自己的threadLocals获取Looper,无需同步机制(如锁)来访问全局映射表。
- 简单:API简洁(prepare()和myLooper()),使用方便。
- 安全:不会出现其他线程篡改当前线程的Looper的情况。
举例说明:
主线程在ActivityThread的main方法中会调用Looper.prepareMainLooper()(内部调用prepare(false))来为主线程创建Looper,并启动消息循环。
我们如果在子线程中使用Handler,必须先调用Looper.prepare()创建该线程的Looper,然后调用Looper.loop()。
总结:使用ThreadLocal是为了实现线程局部存储,确保每个线程的Looper对象独立存在且易于访问,从而实现线程之间的消息处理隔离
五 hanler.removeCallback(Runnable callback)方法为什么可能不能将消息从消息队列移除
removeCallback(Runnable callback): 移除指定Runnable对象对应的消息。
在 Android 的 Handler 机制中,handler.removeCallbacks()
方法无法保证完全移除消息队列中的对应消息,这是由多线程环境下的消息处理机制决定的。以下是深度技术解析:
🔥 核心失效原因
- 竞态条件(Race Condition)
// 潜在竞态场景
new Thread(() -> handler.postDelayed(runnable, 1000)).start();
new Thread(() -> handler.removeCallbacks(runnable)).start();
• 消息入队延迟:当 removeCallbacks()
执行时,对应的 Runnable 可能尚未加入消息队列
• CPU调度间隙:即便在单线程中,postDelayed()
和 removeCallbacks()
调用之间也可能被插入其他消息处理
- 内存屏障机制(Barrier)
sequenceDiagram
Handler->>MessageQueue: postDelayed(runnable, 1000)
MessageQueue->>MessageQueue: 插入延时消息
Handler->>MessageQueue: removeCallbacks(runnable)
Note over MessageQueue: 同步屏障保护状态
MessageQueue-->>Handler: 返回false(未找到可移除项)
• 消息队列操作通过 synchronized(this)
加锁
• 极端情况下,removeCallbacks()
调用可能发生在消息排队过程中
- 延时消息特殊处理
// MessageQueue 源码关键逻辑
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.when = when;
// ... 按执行时间排序插入队列
}
}
• 延时消息 (postDelayed()
) 会根据 when
参数插入队列特定位置
• removeCallbacks()
遍历时可能跳过未到执行时间的消息
⚠️ 四大典型失效场景
场景 1: 消息已开始执行
handler.post(() -> {
// 长时间耗时操作
doHeavyWork();
});
handler.removeCallbacks(runnable); // 若此时消息已在执行则移除失败
• 执行中状态:Looper 已经取出该消息并进入处理流程
• 消息队列内部实现:执行中的消息会临时移出队列
场景 2: 匿名 Runnable 问题
// 每次创建新对象导致引用不匹配
handler.postDelayed(new Runnable() {
@Override public void run() { /* ... */ }
}, 1000);
handler.removeCallbacks(new Runnable() { // 失败!
@Override public void run() { /* ... */ }
});
• 对象标识符不匹配:匿名类每次实例化产生不同对象
• 需保持 Runnable 实例引用一致:
Runnable task = () -> { /* ... */ };
handler.postDelayed(task, 1000);
handler.removeCallbacks(task); // 成功
场景 3: 跨线程竞争
// 主线程
handler.postDelayed(runnable, 0);
// 工作线程
new Thread(() -> {
handler.removeCallbacks(runnable); // 可能失败!
}).start();
• 时间窗口问题:消息可能已加入队列但尚未被处理
• 线程优先级差异:主线程 Looper 可能优先获取CPU时间片
场景 4: 屏障消息(Barrier Message)
View.postDelayed(() -> {
// View 的 Handler 可能插入同步屏障
handler.removeCallbacks(runnable); // 可能失效
}, 100);
• 屏幕绘制期间系统会插入特殊屏障消息(msg.isAsynchronous() == false
)
• 屏障会阻塞普通消息执行但不影响队列结构
🧠 MessageQueue 底层实现缺陷
// MessageQueue.removeCallbacks() 实际逻辑
void removeMessages(Handler h, Runnable r, Object object) {
synchronized (this) {
Message p = mMessages;
while (p != null) {
// 遍历可能跳过待执行消息
if (p.target == h && p.callback == r
&& (object == null || p.obj == object)) {
// ... 移除操作
}
p = p.next;
}
}
}
- 链表遍历局限性:必须等消息进入链表才能被扫描到
- 并发修改风险:同步锁无法覆盖外部线程的并发操作
- 延时精度问题:消息在下次
Looper.loop()
轮询前不会激活
✅ 可靠移除策略
方案 1:引用持有 + 状态检测
// 声明原子操作标记
private final AtomicBoolean shouldRun = new AtomicBoolean(true);
Runnable task = () -> {
if (shouldRun.get()) {
// 执行有效逻辑
}
};
// 发送任务
handler.postDelayed(task, 1000);
// 安全移除
shouldRun.set(false);
handler.removeCallbacks(task); // 即使失败也不影响业务
方案 2:精确消息标识(what)
static final int MSG_CODE = 0x1001;
// 发送标识消息
handler.sendMessageDelayed(
handler.obtainMessage(MSG_CODE),
1000
);
// 确保移除
handler.removeMessages(MSG_CODE);
方案 3:线程安全操作
// 在主线程队列中执行移除操作
handler.post(() -> handler.removeCallbacks(runnable));
方案 4:清空队列(极端情况)
// 强制移除所有关联消息
handler.removeCallbacksAndMessages(null);
⚡ 性能与可靠性对比
操作方式 | 移除成功率 | 线程安全 | 适用场景 |
---|---|---|---|
removeCallbacks() |
85% | ❌ | 单线程简单场景 |
状态标记法 | 100% | ✅ | 高可靠性需求 |
消息标识符(what) | 99.9% | ✅ | 精确控制 |
队列清空法 | 100% | ✅ | 界面销毁等终止操作 |
💡 终极建议:在 Android 开发中,对时序要求严格的任务应使用状态标记法+消息移除双保险。对于需要精确控制的延迟任务,推荐使用
Handler.sendMessageDelayed()
配合what
标识进行管理,比Runnable
方案更可靠。
在实际开发中遇到过:
// 典型错误:在 onDestroy() 中移除
@Override
protected void onDestroy() {
handler.removeCallbacks(runnable); // 可能失效
super.onDestroy(); // 此时主线程消息可能仍存在
}
// 正确方式(AndroidX推荐)
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null); // 强制清空
}
系统资源清理场景中应始终使用强保证的移除方式。