Android消息机制:Handler、Looper和Message的深度剖析

16 阅读20分钟

Android消息机制:Handler、Looper和Message的深度剖析

一、Android 消息机制简介

在 Android 开发的世界里,消息机制犹如一条无形的纽带,紧密连接着各个线程,让它们能够有条不紊地协同工作。当我们在应用中发起网络请求获取数据后,想要将数据展示在界面上;或者在后台线程完成复杂计算后,需要更新 UI,这些场景都离不开消息机制的支持。可以毫不夸张地说,消息机制是保证 Android 应用程序流畅运行和实现复杂交互功能的基石,而 Handler、Looper 和 Message 则是构建这座基石的关键元素 。它们之间的紧密协作,实现了线程之间的通信和任务的调度,是每个 Android 开发者都必须深入理解的核心内容。

二、Handler:消息传递与任务执行

2.1 Handler 的基本作用

Handler 在 Android 消息机制中扮演着消息传递与任务执行的关键角色,就像是一位忙碌的信使,在不同线程之间传递着重要的 “信件”(消息)。它的核心功能在于能够将消息(Message)或任务(Runnable)发送到目标线程的消息队列中,并且可以在目标线程中处理这些消息和任务 。例如,当我们在后台线程进行网络数据请求后,需要将获取到的数据展示在界面上,这时候就可以通过 Handler 将包含数据的消息发送到主线程(UI 线程),然后在主线程中处理该消息,完成数据的展示,从而避免了在非 UI 线程直接操作 UI 导致的线程安全问题。

在实际使用中,Handler 提供了一系列丰富的方法来满足不同的消息发送需求。比如sendMessage(Message msg)方法,它可以将一个 Message 对象发送到消息队列中,这个 Message 对象可以携带我们自定义的数据,通过msg.what来标识消息的类型,msg.arg1msg.arg2以及msg.obj来传递具体的数据 。sendEmptyMessage(int what)方法则更为简洁,它用于发送一个不携带额外数据的空消息,仅通过what参数来标识消息类型,适用于一些简单的通知场景,比如告知主线程某个后台任务已经完成。

除了发送消息,Handler 还可以发送任务。post(Runnable r)方法可以将一个 Runnable 任务发送到消息队列中,当该任务被取出执行时,会在 Handler 所关联的线程中运行 Runnable 的run方法。postDelayed(Runnable r, long delayMillis)方法则允许我们设置任务的延迟执行时间,比如我们希望在 5 秒后执行某个任务来更新 UI,就可以使用这个方法 。这些方法为我们在多线程编程中灵活调度任务和传递消息提供了极大的便利,是我们在 Android 开发中不可或缺的工具。

2.2 Handler 的工作原理

Handler 的工作原理依赖于 Looper 和 MessageQueue,它们之间紧密协作,如同一个精密的机器,确保了消息的有序传递和任务的准确执行 。当我们创建一个 Handler 时,它会自动关联到当前线程的 Looper,如果当前线程没有 Looper,那么就会抛出异常,这也是为什么在子线程中使用 Handler 时,需要先调用Looper.prepare()来创建 Looper,再调用Looper.loop()来启动消息循环。

Handler 发送消息的过程就像是将一封信件投递到邮箱中。当我们调用 Handler 的sendMessage或者post等方法时,Handler 会将消息或者任务封装成一个 Message 对象,并将其发送到与之关联的 Looper 所管理的 MessageQueue 中。在这个过程中,Message 会被赋予一个目标 Handler,即msg.target = this,这样在消息被处理时,就知道应该交给哪个 Handler 来处理 。例如:


Handler handler = new Handler();
Message message = Message.obtain();
message.what = 1;
message.obj = "Hello, Handler";
handler.sendMessage(message);

上述代码中,我们创建了一个 Handler 和一个 Message,设置了 Message 的whatobj属性,然后通过 Handler 将 Message 发送出去。

而 MessageQueue 则像是一个有序的邮箱队列,它采用单链表的数据结构来存储 Message,并且会根据 Message 的when属性(即消息的执行时间)来对消息进行排序,保证消息按照时间顺序被处理。当有新的消息进入队列时,会根据when的值找到合适的位置插入,确保队列的有序性 。

Looper 则是这个消息处理机制的核心驱动,它不断地从 MessageQueue 中取出消息,并将其分发给对应的 Handler 进行处理。Looper 的loop方法是一个死循环,它会持续执行以下操作:


public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    while (true) {
        Message msg = queue.next(); // 可能会阻塞,直到有消息可用
        if (msg == null) {
            return;
        }
        msg.target.dispatchMessage(msg);
        msg.recycle();
    }
}

在这个循环中,queue.next()方法会从 MessageQueue 中取出下一条消息,如果当前队列中没有消息,该方法会阻塞线程,直到有新的消息到来,这样可以避免 CPU 的无效空转,节省资源 。当取出消息后,会通过msg.target.dispatchMessage(msg)将消息分发给目标 Handler 的dispatchMessage方法。在dispatchMessage方法中,如果 Message 的callback不为空,会优先执行callbackrun方法;如果callback为空,则会调用 Handler 的handleMessage方法来处理消息 。例如:


Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what == 1) {
            String data = (String) msg.obj;
            // 处理消息,更新UI等操作
        }
    }
};

通过这样的协作机制,Handler 实现了不同线程之间的通信和任务调度,使得 Android 应用能够在多线程环境下有条不紊地运行,为我们构建复杂而流畅的应用程序提供了坚实的基础。

三、Looper:消息循环的掌控者

3.1 Looper 的关键职责

Looper 在 Android 消息机制中就像是一位不知疲倦的监工,掌控着消息循环的核心流程,确保消息能够被及时处理,让整个消息处理系统有条不紊地运行 。它的关键职责主要包括两个方面:初始化消息循环和消息分发。

在初始化消息循环时,Looper 通过Looper.prepare()方法来创建当前线程的 Looper 对象,并为其关联一个 MessageQueue 。prepare方法的实现如下:


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来存储 Looper 对象,保证每个线程都有且仅有一个 Looper,实现了线程与 Looper 的一一对应关系 。在创建 Looper 对象时,会同时创建与之关联的 MessageQueue,为后续的消息存储和处理做好准备。例如,在子线程中如果需要使用 Handler 发送和处理消息,就必须先调用Looper.prepare()来初始化 Looper 。

而开启消息循环则是通过Looper.loop()方法,这个方法是一个死循环,它不断地从 MessageQueue 中取出消息,并将其分发给对应的 Handler 进行处理,是整个消息机制运行的核心驱动 。其核心代码如下:


public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    while (true) {
        Message msg = queue.next(); // 可能会阻塞,直到有消息可用
        if (msg == null) {
            return;
        }
        msg.target.dispatchMessage(msg);
        msg.recycle();
    }
}

在这个循环中,queue.next()方法会从 MessageQueue 中取出下一条消息,如果当前队列中没有消息,该方法会阻塞线程,直到有新的消息到来,这样可以避免 CPU 的无效空转,节省资源 。当取出消息后,会通过msg.target.dispatchMessage(msg)将消息分发给目标 Handler 的dispatchMessage方法,从而完成消息的处理流程。

3.2 Looper 与线程的关系

Looper 与线程的关系紧密相连,每个线程都可以拥有一个独立的 Looper,但需要注意的是,默认情况下只有主线程会自动初始化 Looper 。这也是为什么在主线程中我们可以直接创建 Handler 并使用它来发送和处理消息,而无需手动初始化 Looper 。例如:


Handler mainHandler = new Handler();
mainHandler.post(new Runnable() {
    @Override
    public void run() {
        // 在主线程中执行的代码
    }
});

在上述代码中,我们直接在主线程中创建了 Handler,并使用post方法发送了一个 Runnable 任务,这是因为主线程已经默认初始化了 Looper 。

对于子线程来说,如果需要在其中使用 Handler 进行消息处理,就必须手动调用Looper.prepare()来初始化 Looper,并在完成消息处理后调用Looper.loop()来启动消息循环 。例如:


class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 处理消息
            }
        };
        Looper.loop();
    }
}

在这个例子中,LooperThread继承自Thread,在run方法中首先调用Looper.prepare()初始化 Looper,然后创建 Handler 并实现handleMessage方法来处理消息,最后调用Looper.loop()启动消息循环 。这样,子线程就可以像主线程一样处理消息了。如果子线程没有初始化 Looper 就尝试创建 Handler,将会抛出RuntimeException异常 。

这种设计模式保证了每个线程的消息处理独立性,同时也使得开发者可以根据具体的业务需求灵活地控制线程的消息处理逻辑 。无论是在主线程中处理 UI 更新,还是在子线程中处理耗时任务并通过消息与主线程通信,Looper 都扮演着不可或缺的角色,是 Android 消息机制中实现线程间高效协作的关键因素之一 。

四、Message:信息的载体

4.1 Message 的结构与作用

Message 在 Android 消息机制中扮演着信息载体的关键角色,它就像是一个包裹,承载着我们需要传递的数据和信息,在不同线程之间穿梭,实现线程间的通信 。Message 类提供了丰富的字段来满足各种数据传递需求。其中,what字段是一个整数值,常用于标识消息的类型,比如我们可以定义what = 1表示网络请求成功的消息,what = 2表示网络请求失败的消息 。通过在handleMessage方法中判断msg.what的值,我们可以执行不同的处理逻辑 。例如:


Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what == 1) {
            // 处理网络请求成功的逻辑
        } else if (msg.what == 2) {
            // 处理网络请求失败的逻辑
        }
    }
};

arg1arg2字段也是整数值,通常用于传递一些简单的整数数据,比如操作的结果码、数量等 。当我们需要传递更复杂的数据时,可以使用obj字段,它可以存储任意类型的对象,比如一个字符串、一个自定义的实体类等 。假设我们从网络请求中获取到一个用户信息对象User,就可以将其通过msg.obj传递给目标线程 :


User user = new User("张三", 20);
Message message = Message.obtain();
message.what = 1;
message.obj = user;
handler.sendMessage(message);

除了携带数据,Message 还可以指定延迟时间或目标时间,实现任务的定时执行 。通过sendMessageDelayed(Message msg, long delayMillis)方法,我们可以设置消息在指定的延迟时间(以毫秒为单位)后被处理 。例如,我们希望在 5 秒后更新 UI,就可以这样做:


Message message = Message.obtain();
message.what = 1;
handler.sendMessageDelayed(message, 5000);

sendMessageAtTime(Message msg, long uptimeMillis)方法则允许我们指定消息在绝对时间点(从系统启动开始的毫秒数)被处理,这在一些对时间精度要求较高的场景中非常有用 。

4.2 Message 的创建与复用

在 Android 开发中,创建 Message 对象主要有两种方式:直接使用new关键字创建和通过Message.obtain()方法获取 。直接使用new Message()创建对象虽然简单直观,但在频繁的消息传递场景下,会导致大量的对象创建和销毁,增加内存分配和垃圾回收的开销,影响应用的性能 。例如,在一个频繁更新 UI 的动画场景中,如果每次更新都创建新的 Message 对象,随着时间的推移,内存占用会不断增加,可能导致应用卡顿甚至崩溃 。

为了优化内存使用,Android 提供了Message.obtain()方法,它采用了对象池技术,从一个消息池中获取可复用的 Message 对象 。当我们调用Message.obtain()时,系统会首先检查消息池中是否有可用的 Message 对象 。如果有,就从池中取出一个对象并将其返回,同时更新消息池的状态;如果消息池中没有可用对象,才会创建一个新的 Message 对象 。这种复用机制大大减少了对象的创建次数,降低了内存分配和垃圾回收的压力,提高了应用的性能和响应速度 。例如,在一个每秒需要发送多次消息的场景中,使用Message.obtain()方法可以显著减少内存开销,提升应用的流畅度 。

除了Message.obtain()方法,我们还可以通过Handler.obtainMessage()方法来获取 Message 对象,该方法实际上也是调用了Message.obtain(),并自动为 Message 设置了目标 Handler ,使用起来更加方便 。在处理完消息后,我们应该调用Message.recycle()方法将 Message 对象回收至消息池,以便下次复用 。recycle()方法会清理 Message 对象的状态,将其重置为初始状态,并将其添加回消息池 。例如:


Message message = Message.obtain();
// 使用message
message.recycle();

通过合理地使用 Message 的创建与复用机制,我们能够有效地优化应用的内存使用,提升应用的性能,为用户带来更加流畅的使用体验 。在实际开发中,我们应尽量避免直接使用new关键字创建 Message 对象,而是优先选择Message.obtain()Handler.obtainMessage()方法,养成良好的编程习惯 。

五、Handler、Looper 和 Message 的关系梳理

5.1 三者紧密协作的关系图

为了更直观地理解 Handler、Looper 和 Message 之间的关系,我们通过一张关系图来展示 :


@startuml
package "Thread(线程)"{
    component "Looper(消息循环)" as looper{
        component "MessageQueue(消息队列)" as messageQueue
    }
    component "Handler(消息处理器)"{
        component "Message(消息)"{
            attribute what
            attribute arg1
            attribute arg2
            attribute obj
            attribute target -> Handler
        }
        component "Runnable(任务)"{
            attribute run()
        }
        component "sendMessage()" as sendMessage
        component "post()" as post
        component "handleMessage()" as handleMessage
    }
    looper -- messageQueue : 持有/管理
    Handler -- Looper : 创建时绑定,通过Looper获取
    Handler -- Message : 封装为,入队,target指向
    Handler -- Runnable : 封装为,入队
    Looper -- Handler : loop取出消息,出队分发到target
    Message -- Runnable : callback
}
@enduml

在这张图中,我们可以清晰地看到它们之间的协作关系 。Handler 绑定了某个线程的 Looper,通过调用sendMessagepost方法将消息或任务封装为 Message 对象,并将其入队到 MessageQueue 中 。Looper 通过loop方法不断从 MessageQueue 中取出 Message,然后根据 Message 的target回调到对应的 Handler 去分发并执行处理逻辑 。如果 Message 中设置了callback(即 Runnable 对象),则会优先执行callbackrun方法;如果没有设置callback,则会调用 Handler 的handleMessage方法来处理消息 。

5.2 详细关系阐述

从 Handler 与 Looper 的关系来看,每个 Handler 都必须绑定一个 Looper,这是它们协作的基础 。在创建 Handler 时,如果没有显式指定 Looper,Handler 会默认关联当前线程的 Looper 。例如,在主线程中创建 Handler 时,由于主线程已经默认初始化了 Looper,所以可以直接创建 Handler 并使用 。而在子线程中,如果需要使用 Handler,就必须先手动调用Looper.prepare()来创建 Looper,并在创建 Handler 之后调用Looper.loop()来启动消息循环 。这是因为 Handler 发送消息时,需要将消息发送到与之关联的 Looper 所管理的 MessageQueue 中,如果没有绑定 Looper,消息就无处可去,会导致程序出错 。

Looper 与 MessageQueue 的关系也非常紧密,每个 Looper 都维护着一个 MessageQueue,这个 MessageQueue 就像是 Looper 的 “任务清单”,用于存储待处理的消息 。Looper 的loop方法会不断地从 MessageQueue 中取出消息,并将其分发给对应的 Handler 进行处理 。在这个过程中,MessageQueue 起到了消息存储和调度的作用,它按照消息的when属性(即消息的执行时间)对消息进行排序,保证消息能够按照顺序被处理 。例如,当我们调用sendMessageDelayed方法发送一个延迟消息时,MessageQueue 会根据延迟时间将该消息插入到合适的位置,确保在指定的时间点才会被 Looper 取出处理 。

Handler 与 Message 之间则是一种发送和处理的关系 。Handler 负责创建、发送和处理 Message 。当我们调用 Handler 的sendMessagepost方法时,实际上是将一个 Message 对象发送到了 MessageQueue 中 。在这个过程中,Handler 会将自身作为target绑定到 Message 上,这样当 Looper 从 MessageQueue 中取出 Message 时,就知道应该将其分发给哪个 Handler 进行处理 。例如:


Handler handler = new Handler();
Message message = Message.obtain();
message.what = 1;
message.obj = "Hello, Message";
message.target = handler;
handler.sendMessage(message);

在上述代码中,我们手动创建了一个 Message 并设置了target为当前 Handler,然后通过 Handler 发送该 Message 。当 Looper 取出这个 Message 时,就会根据target找到对应的 Handler,并调用其dispatchMessage方法来处理消息 。在dispatchMessage方法中,如果 Message 的callback不为空,会优先执行callbackrun方法;如果callback为空,则会调用 Handler 的handleMessage方法来处理消息 ,从而完成整个消息处理流程 。

通过这样紧密的协作关系,Handler、Looper 和 Message 共同构建了 Android 强大的消息机制,使得应用程序能够在多线程环境下高效、有序地运行 ,为开发者实现复杂的业务逻辑和流畅的用户交互提供了坚实的基础 。

六、全局监听 Handler 消息

6.1 常见需求场景分析

在实际的 Android 开发中,全局监听 Handler 消息有着广泛的应用场景 。当我们需要分析应用中的任务执行情况时,全局监听 Handler 消息就像是给应用安装了一个 “监控摄像头”,能够实时捕捉到任务的执行轨迹 。比如在一个电商应用中,用户进行商品搜索、下单等操作时,这些操作可能会触发一系列的异步任务,通过全局监听 Handler 消息,我们可以了解每个任务的执行时间、执行顺序等信息,从而判断应用的性能瓶颈所在,为优化应用性能提供有力依据 。

统计某些特定类型任务的执行频率也是全局监听 Handler 消息的重要应用之一 。以社交应用为例,消息发送、接收任务是核心功能之一,通过监听这些任务相关的 Handler 消息,我们可以统计出用户在一定时间内发送和接收消息的次数,进而分析用户的活跃度和使用习惯 。这对于产品经理制定运营策略、开发团队优化功能都具有重要的参考价值 。

在调试阶段,排查异常的任务逻辑更是离不开全局监听 Handler 消息 。当应用出现卡顿、崩溃等异常情况时,我们可以通过监听 Handler 消息,查看任务的执行流程和参数,快速定位到问题所在 。例如,某个任务在执行过程中出现了空指针异常,通过监听 Handler 消息,我们可以获取到该任务的详细信息,包括任务执行时的上下文环境、传递的参数等,从而更容易找到异常的根源,提高调试效率 。

6.2 Android 的空闲监听机制

Android 提供了 MessageQueue.IdleHandler 接口,用于监听消息队列空闲时的状态 。当消息队列中没有待处理的消息或者只有尚未到执行时间的延时消息时,IdleHandler 就会被调用 。使用方法如下:


Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        // 消息队列空闲时执行的逻辑
        Log.d("IdleHandler", "当前消息队列空闲");
        return true; // 返回 true 表示继续监听,false 表示移除监听
    }
});

在上述代码中,我们通过Looper.myQueue().addIdleHandler方法添加了一个 IdleHandler,当消息队列空闲时,queueIdle方法会被调用,在这个方法中我们可以执行一些低优先级的任务,比如数据预加载、缓存清理等 。例如,在一个新闻应用中,当消息队列空闲时,我们可以利用这个时机从服务器预加载一些热门新闻,这样当用户打开新闻页面时,就可以更快地获取到新闻内容,提升用户体验 。

然而,这种方式存在一定的局限性 。它只能监听消息队列的空闲状态,无法获取所有或指定的任务信息 。比如我们想要知道某个特定类型的任务(如网络请求任务)的执行情况,IdleHandler 就无法满足需求 。而且,IdleHandler 的执行时机是不确定的,它依赖于消息队列的空闲状态,如果消息队列一直处于繁忙状态,IdleHandler 可能长时间无法被调用 。因此,在需要深入了解应用任务执行情况时,我们需要更强大的全局监听方案 。

6.3 实现全局监听的方案

6.3.1 自定义 Handler

我们可以创建一个自定义的 Handler 类,通过重写dispatchMessage方法,在其中对所有发送和接收的消息进行拦截和记录 。自定义 Handler 类的实现如下:


public class MonitoringHandler extends Handler {
    public MonitoringHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void dispatchMessage(@NonNull Message msg) {
        // 在处理消息之前进行拦截
        Log.d("MonitoringHandler", "Dispatching message: " + msg.what);
        // 调用父类的方法继续分发消息
        super.dispatchMessage(msg);
        // 在处理消息之后进行额外操作
        Log.d("MonitoringHandler", "Message processed: " + msg.what);
    }
}

在这个自定义 Handler 类中,dispatchMessage方法在消息分发前和分发后分别添加了日志记录,这样我们就可以追踪消息的处理过程 。使用时,我们可以在主线程中创建这个自定义 Handler 的实例:


MonitoringHandler handler = new MonitoringHandler(Looper.getMainLooper());
handler.sendMessage(Message.obtain(handler, 1));

上述代码中,我们在主线程中创建了MonitoringHandler实例,并发送了一条消息 。当这条消息被处理时,MonitoringHandlerdispatchMessage方法会被调用,我们就可以在日志中看到消息的处理情况 。通过这种方式,我们可以方便地对特定 Handler 发送和接收的消息进行监控,了解消息的处理流程和时间,有助于我们分析应用中与该 Handler 相关的任务执行情况 。

6.3.2 Hook 系统 Looper

如果需要全局监听所有线程中的消息,可以通过 Hook 系统的 Looper 来实现 。这种方法的原理是利用反射获取主线程的 MessageQueue,并通过动态代理 Hook MessageQueue 的enqueueMessage方法,从而实现对所有入队消息的监听 。具体实现步骤如下: 首先,获取主线程的 MessageQueue:


Field mQueueField = Looper.class.getDeclaredField("mQueue");
mQueueField.setAccessible(true);
MessageQueue mainQueue = (MessageQueue) mQueueField.get(Looper.getMainLooper());

然后,获取enqueueMessage方法并设置其可访问:


Method enqueueMethod = MessageQueue.class.getDeclaredMethod("enqueueMessage", Message.class, long.class);
enqueueMethod.setAccessible(true);

接下来,使用动态代理创建一个代理对象来 Hook enqueueMessage方法:


InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("enqueueMessage".equals(method.getName())) {
            Message msg = (Message) args[0];
            // 在这里可以添加自定义的监听逻辑,例如打印消息信息
            Log.d("GlobalMonitor", "Enqueue message: " + msg.what);
        }
        return method.invoke(mainQueue, args);
    }
};
MessageQueue proxyQueue = (MessageQueue) Proxy.newProxyInstance(
        MessageQueue.class.getClassLoader(),
        new Class[]{MessageQueue.class},
        handler);

最后,通过反射将代理后的 MessageQueue 设置回 Looper 中:


Field mQueueField = Looper.class.getDeclaredField("mQueue");
mQueueField.setAccessible(true);
mQueueField.set(Looper.getMainLooper(), proxyQueue);

通过上述步骤,我们就实现了对主线程中所有入队消息的全局监听 。在InvocationHandlerinvoke方法中,当enqueueMessage方法被调用时,我们可以添加自定义的监听逻辑,比如打印所有入队消息的what值 。这样,无论应用中哪个部分通过 Handler 发送消息,我们都可以获取到相关信息,从而实现对应用中所有 Handler 消息的全局监控,为深入分析应用的任务执行情况提供了有力的工具 。