第四章 Handler消息机制:Handler、Looper、MessageQueue工作原理、消息屏障与同步、IdleHandler、Handler内存泄漏分析
Handler消息机制,说白了就是Android系统的“内部通讯兵”。
我刚开始接触Android Framework时,总觉得这玩意儿不就是个线程切换工具吗?后来深入源码才发现,它背后藏着一整套精妙的设计哲学。今天咱们就把这块硬骨头啃下来。
4.1 核心三件套:Handler、Looper、MessageQueue
这三兄弟的关系,我习惯用一个比喻来理解:Looper是流水线工人,MessageQueue是传送带,Handler是操作员。
工人(Looper)不停从传送带(MessageQueue)上取任务(Message),操作员(Handler)负责往传送带上放任务,并决定任务由哪个工人处理。嗯,就是这么回事。
4.1.1 创建与绑定
每个线程只能有一个Looper。为什么?你想想看,一条流水线上站两个工人抢任务,不乱套才怪。
// 主线程Looper已经自动创建好了
// 子线程需要手动调用
class MyThread extends Thread {
@Override
public void run() {
Looper.prepare(); // 创建Looper,内部会new一个MessageQueue
Handler handler = new Handler(); // 绑定当前线程的Looper
Looper.loop(); // 开始循环取消息
}
}
这里有个坑,我踩过好几次:如果在子线程new Handler时没有Looper,会直接抛异常。因为Handler的构造方法里会去拿当前线程的Looper,拿不到就崩。
4.1.2 消息分发流程
一条消息从发送到处理,经历了什么?我画了张图,你看一眼就明白了。
你看,消息从Handler出发,经过MessageQueue排队,最后被Looper取出并回调到Handler。这个闭环设计得非常优雅。
4.2 消息屏障与同步机制
消息屏障,这概念我第一次看源码时懵了半天。说白了,它就是给普通消息加了个“插队开关”。
你想想看,UI线程要处理绘制任务,但消息队列里排了一堆用户点击事件。如果按顺序来,界面得卡成什么样?这时候就需要消息屏障出场了。
4.2.1 什么是消息屏障
消息屏障其实就是一个target为null的Message。普通消息都有target(指向某个Handler),屏障消息没有。Looper在取消息时,遇到屏障就会跳过所有普通消息,只处理异步消息。
// 源码位置:MessageQueue.java
Message next() {
for (;;) {
// 如果遇到屏障,就找第一个异步消息
if (msg != null && msg.target == null) {
// 屏障消息!跳过所有同步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// ...
}
}
4.2.2 同步与异步消息
默认情况下,我们发的消息都是同步消息。异步消息需要手动标记:
// 创建异步消息
Message msg = Message.obtain();
msg.setAsynchronous(true);
handler.sendMessage(msg);
// 或者通过Handler构造方法指定
Handler asyncHandler = new Handler(Looper.myLooper(), null, true); // 第三个参数true表示异步
我在项目中遇到过一个问题:自定义View的invalidate()有时会延迟。后来发现是消息队列里积压了太多同步消息,绘制任务被堵住了。解决方案就是让绘制Handler支持异步消息。
4.3 IdleHandler:空闲时的“摸鱼”机会
IdleHandler,名字起得很形象——当Looper闲下来时,可以干点别的事。
我习惯用它来做一些非紧急的初始化工作。比如启动页加载完后,趁用户还没操作,偷偷预加载一些数据。
// 添加IdleHandler
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 在这里做延迟初始化
preloadData();
// 返回false表示只执行一次,true表示每次空闲都执行
return false;
}
});
我曾经用它来优化一个列表页面的启动速度。把图片缓存、数据库预加载都放到IdleHandler里,首屏渲染时间直接降了30%。嗯,效果立竿见影。
4.4 Handler内存泄漏分析
这个话题,面试必问,项目必踩。我刚开始写Android时,就因为这个被线上bug追着跑。
4.4.1 泄漏是怎么发生的
简单说就是:Handler持有外部类的隐式引用,导致Activity无法被回收。
你想想看,内部类Handler默认持有外部Activity的引用。如果消息还没处理完,Activity就finish了,这条消息还在MessageQueue里排队。Looper持有MessageQueue,MessageQueue持有Message,Message持有Handler,Handler持有Activity。这一串引用链,GC根本动不了Activity。
// 错误写法:内部类隐式持有Activity引用
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
private void doSomething() {
mHandler.sendMessageDelayed(Message.obtain(), 10000); // 10秒后处理
// 如果此时Activity被销毁,泄漏就发生了
}
}
4.4.2 解决方案
我常用的方案有两个:
- 静态内部类 + 弱引用
- 在onDestroy中移除所有消息
// 推荐写法
public class MainActivity extends AppCompatActivity {
private static class SafeHandler extends Handler {
private final WeakReference<MainActivity> mActivityRef;
SafeHandler(MainActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivityRef.get();
if (activity == null || activity.isFinishing()) {
return; // Activity已销毁,直接返回
}
// 安全处理消息
}
}
private SafeHandler mHandler = new SafeHandler(this);
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null); // 移除所有消息
}
}
4.5 总结与思考
Handler消息机制,说白了就是一套线程间通信的优雅方案。它把并发问题简化成了队列问题,让开发者不用操心锁和同步。
我这些年看下来,理解Handler的关键在于:
- Looper是线程专属的——每个线程只能有一个,用ThreadLocal保证
- MessageQueue是优先级队列——按时间排序,支持屏障和异步
- Handler是双向桥梁——既能发送消息,又能处理消息
嗯,掌握了这些,你再看Android的源码,会发现很多地方都在用这套机制。比如Activity的生命周期回调、BroadcastReceiver的派发,底层都是Handler在驱动。
下次遇到ANR或者界面卡顿,不妨先看看是不是消息队列里堵了太多东西。有时候,一个IdleHandler就能解决问题。
公众号:蓝海资料掘金营,微信deep3321