第四章 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,拿不到就崩。

注意:Handler必须绑定一个Looper才能工作。主线程的Looper由ActivityThread自动创建,子线程需要手动调用Looper.prepare()。

4.1.2 消息分发流程

一条消息从发送到处理,经历了什么?我画了张图,你看一眼就明白了。

Handler消息分发流程图 Handler 发送/处理消息 MessageQueue 消息队列(传送带) Looper 循环取消息 sendMessage() next() dispatchMessage() 回调 流程说明: 1. Handler.sendMessage() 将消息插入 MessageQueue 2. Looper.loop() 不断调用 MessageQueue.next() 取出消息 3. Looper 将消息交回给 Handler.dispatchMessage() 处理

你看,消息从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());
        }
        // ...
    }
}
核心要点:消息屏障让异步消息可以“插队”执行。Android的输入事件、绘制任务都用了这个机制来保证优先级。

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在Looper处理完所有消息后调用。如果返回true,它会一直留在队列里,每次空闲都触发。小心别让它变成“永动机”。

我曾经用它来优化一个列表页面的启动速度。把图片缓存、数据库预加载都放到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 解决方案

我常用的方案有两个:

  1. 静态内部类 + 弱引用
  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);  // 移除所有消息
    }
}
避坑指南:我曾经在项目里只用了弱引用,忘了在onDestroy里removeCallbacks。结果消息虽然不会泄漏Activity了,但消息本身还在队列里,等它触发时activity已经是null,导致一些逻辑没执行。所以两个措施最好一起用

4.5 总结与思考

Handler消息机制,说白了就是一套线程间通信的优雅方案。它把并发问题简化成了队列问题,让开发者不用操心锁和同步。

我这些年看下来,理解Handler的关键在于:

  • Looper是线程专属的——每个线程只能有一个,用ThreadLocal保证
  • MessageQueue是优先级队列——按时间排序,支持屏障和异步
  • Handler是双向桥梁——既能发送消息,又能处理消息

嗯,掌握了这些,你再看Android的源码,会发现很多地方都在用这套机制。比如Activity的生命周期回调、BroadcastReceiver的派发,底层都是Handler在驱动。

下次遇到ANR或者界面卡顿,不妨先看看是不是消息队列里堵了太多东西。有时候,一个IdleHandler就能解决问题。


公众号:蓝海资料掘金营,微信deep3321