Messenger:Android 最轻量的 IPC 方式

一句话理解: Messenger 就是给 Handler 套了一个跨进程的壳。服务端用 Handler 创建 Messenger,客户端绑定后通过 Messenger.send(Message) 发消息,服务端再通过 msg.replyTo.send(...) 把结果回传。

最大优点: 不需要写 .aidl 文件,也不需要手写 Stub/Proxy 三件套,是 Android 里最轻量的 IPC 方案。


一、适用场景

场景 是否适合 Messenger
只需要"发消息",不需要"调方法" ✅ 非常适合
需要同步等待返回值(阻塞调用) ❌ 不适合,Messenger 是纯异步的
数据传输量大、频率高 ❌ 不适合,Binder 传输有 1MB 限制
快速实现双向通信 ✅ 非常适合,自带 replyTo 机制

二、服务端实现

服务端的核心思路:创建一个运行在子线程的 Handler,再用它构造 Messenger,最后把 MessengerBinder 暴露给客户端。

MessengerService.java

java 复制代码
package com.qhsoft.foodsafety.module;
​
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
​
public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
​
    // 定义消息类型常量,客户端和服务端必须保持一致
    public static final int MSG_SAY_HELLO = 1;
    public static final int MSG_ADD = 2;
​
    private Messenger mMessenger;      // 服务端的 Messenger
    private HandlerThread mHandlerThread;
​
    /**
     * 内部 Handler:处理客户端发来的消息
     * 运行在 HandlerThread 的子线程中,避免阻塞主线程
     */
    private class ServiceHandler extends Handler {
        ServiceHandler(android.os.Looper looper) {
            super(looper);
        }
​
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    // 从 Message 中读取客户端传来的数据
                    String clientName = msg.getData().getString("name", "client");
                    Log.d(TAG, "收到客户端问候: " + clientName);
​
                    // msg.replyTo 是客户端留给我们的"回信地址"
                    Messenger replyTo = msg.replyTo;
                    if (replyTo != null) {
                        // 构造回复消息
                        Message reply = Message.obtain(null, MSG_SAY_HELLO);
                        Bundle bundle = new Bundle();
                        bundle.putString("reply", "Hello " + clientName + ", I am MessengerService!");
                        reply.setData(bundle);
​
                        try {
                            replyTo.send(reply);  // 向客户端回传结果
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
​
                case MSG_ADD:
                    // 从 Message.arg1 / arg2 中读取整数参数
                    int a = msg.arg1, b = msg.arg2, sum = a + b;
                    Log.d(TAG, "收到计算请求: " + a + " + " + b + " = " + sum);
​
                    Messenger replyToAdd = msg.replyTo;
                    if (replyToAdd != null) {
                        Message reply = Message.obtain(null, MSG_ADD);
                        reply.arg1 = sum;  // 把计算结果放在 arg1 里回传
                        try {
                            replyToAdd.send(reply);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
​
                default:
                    super.handleMessage(msg);
            }
        }
    }
​
    @Override
    public void onCreate() {
        super.onCreate();
        // 创建子线程的 Looper,避免服务端处理消息时卡主线程
        mHandlerThread = new HandlerThread("MessengerServiceThread");
        mHandlerThread.start();
​
        // 用子线程的 Looper 创建 Handler,再用 Handler 创建 Messenger
        mMessenger = new Messenger(new ServiceHandler(mHandlerThread.getLooper()));
    }
​
    @Override
    public IBinder onBind(Intent intent) {
        // 把 Messenger 内部的 Binder 返回给客户端
        // 客户端拿到这个 IBinder 就能向服务端发消息了
        return mMessenger.getBinder();
    }
​
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mHandlerThread != null) {
            mHandlerThread.quitSafely();  // 优雅停止子线程
        }
    }
}

💡 为什么要用 HandlerThread? 服务端收到客户端消息后需要处理业务逻辑(比如上面的加法运算),如果直接放在主线程 Handler 里,耗时操作会卡死主线程。用 HandlerThread 可以把处理逻辑放到子线程。


三、在 AndroidManifest.xml 中注册服务

ini 复制代码
<service
    android:name="com.qhsoft.foodsafety.module.MessengerService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.qhsoft.foodsafety.module.MESSENGER_SERVICE" />
    </intent-filter>
</service>

⚠️ 注意: android:exported="true" 允许其他应用绑定。如果是内部应用通信,建议加上 android:permission 限制访问权限。


四、客户端实现

客户端的核心思路:绑定服务端 Service,拿到 IBinder 后构造 Messenger,然后通过 Messenger.send(Message) 发消息。为了接收服务端回复,客户端也需要创建一个 Messenger(通过 Handler),并把它塞进 Message.replyTo 里。

MainActivity.kt

kotlin 复制代码
class MainActivity : BaseActivity() {
    private val TAG by lazy { MainActivity::class.simpleName }
​
    private var messenger: Messenger? = null      // 指向服务端的 Messenger
    private var messengerBound = false
​
    private val MSG_SAY_HELLO = 1
    private val MSG_ADD = 2
​
    /**
     * 客户端的 Handler:处理服务端回传的消息
     * 运行在主线程,可以安全更新 UI
     */
    private val clientHandler = Handler(Looper.getMainLooper()) { msg ->
        when (msg.what) {
            MSG_SAY_HELLO -> {
                val reply = msg.data?.getString("reply")
                Log.d(TAG, "【Messenger】收到服务端问候回复: $reply")
            }
            MSG_ADD -> {
                val sum = msg.arg1
                Log.d(TAG, "【Messenger】收到计算结果: $sum")
            }
        }
        true
    }
​
    /**
     * 客户端的 Messenger:给服务端用来"回信"
     * 它内部持有一个 Binder,会被序列化传给服务端
     */
    private val clientMessenger = Messenger(clientHandler)
​
    private val messengerConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            // service 是服务端 Messenger 的 Binder 代理(跨进程)
            // 用它构造客户端的 Messenger 对象
            messenger = Messenger(service)
            messengerBound = true
            Log.d(TAG, "【Messenger】服务连接成功")
​
            // 构造第一条消息:向服务端问好
            val msg = Message.obtain(null, MSG_SAY_HELLO)
            val bundle = Bundle()
            bundle.putString("name", "Sorting")
            msg.data = bundle
​
            // ★ 关键:把客户端 Messenger 贴到信封上
            // 服务端收到后通过 msg.replyTo 就能向客户端回消息
            msg.replyTo = clientMessenger
​
            try {
                messenger?.send(msg)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
​
        override fun onServiceDisconnected(name: ComponentName) {
            messenger = null
            messengerBound = false
            Log.w(TAG, "【Messenger】服务断开")
        }
    }
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
​
        val messengerIntent = Intent().apply {
            action = "com.qhsoft.foodsafety.module.MESSENGER_SERVICE"
            `package` = "com.qhsoft.foodsafety"  // 指定服务端应用包名
        }
        val messengerBindResult = bindService(
            messengerIntent,
            messengerConnection,
            Context.BIND_AUTO_CREATE
        )
        Log.d(TAG, "【Messenger】bindService result=$messengerBindResult")
    }
​
    override fun onDestroy() {
        super.onDestroy()
        if (messengerBound) {
            unbindService(messengerConnection)
            messengerBound = false
        }
    }
}

五、为什么代码里有两个 Messenger?

这是很多新手容易混淆的地方。实际上,两个 Messenger 分别住在客户端服务端,各司其职:

scss 复制代码
┌─────────────────┐                      ┌─────────────────┐
│     客户端       │                      │     服务端       │
│                 │                      │                 │
│  clientMessenger│◄──── replyTo.send ───┤  mMessenger     │
│  (回信地址)      │                      │  (收信邮箱)      │
│        ↑        │                      │        ↑        │
│  clientHandler  │                      │  ServiceHandler │
│  (处理回信)      │                      │  (处理来信)      │
└─────────────────┘                      └─────────────────┘
代码 角色 作用
mMessenger(服务端) 服务端信箱 接收客户端发来的消息,交给 ServiceHandler 处理
messenger(客户端) 指向服务端的代理 客户端用它调用 send(msg),把消息发到服务端
clientMessenger(客户端) 客户端的回信地址 塞进 msg.replyTo,让服务端知道"回消息找谁"

关键代码拆解:

scss 复制代码
// 1. 发消息时,把"我家地址"贴到信封上
msg.replyTo = clientMessenger
​
// 2. 通过服务端的 Messenger 把消息发过去
messenger?.send(msg)
​
// 3. 服务端收到后,从信封上找到回信地址,把结果回传
replyTo.send(reply)

六、Messenger 跨进程原理

Messenger 本质上是对 Binder 的轻量封装,底层走的仍然是 Android 的 Binder IPC 机制。下面是完整的数据流转过程:

6.1 服务端侧:Messenger = IBinder + Handler

服务端创建 Messenger 时:

ini 复制代码
mMessenger = new Messenger(new ServiceHandler(looper));

内部做了两件事:

  1. 创建一个 IMessenger.Stub 对象 (继承自 Binder),实现 asBinder() 返回自身
  2. 保存传入的 Handler :当收到跨进程消息时,把 Message 投递到这个 Handler 的消息队列

onBind() 返回 mMessenger.getBinder(),实际上返回的就是这个 IMessenger.Stub(一个 Binder 对象)。

6.2 客户端侧:Messenger = 指向远程 Binder 的代理

客户端在 onServiceConnected 中:

scss 复制代码
messenger = Messenger(service)  // service 是 IBinder

这里的 service 是 Binder 驱动分配给客户端的远程 Binder 代理 (Proxy)。Messenger 把它包装起来,调用 send(msg) 时,内部会执行:

  1. Message 写入 Parcel(序列化 what/arg1/arg2/data/replyTo 等字段)
  2. 调用 mRemote.transact(Stub.TRANSACTION_send, data, reply, 0)
  3. Binder 驱动Parcel 数据拷贝到服务端进程
  4. 服务端 IMessenger.Stub.onTransact() 被调用,反序列化出 Message
  5. Message 投递到 ServiceHandlerLooper 队列,最终执行 handleMessage()

6.3 replyTo 的双向通信原理

MessagereplyTo 字段也是一个 Messenger(实现了 Parcelable 接口)。

当客户端设置 msg.replyTo = clientMessenger 时:

  1. clientMessenger 内部持有一个 IMessenger.Stub(Binder),会被序列化Parcel
  2. 服务端反序列化后,replyTo 变成指向客户端 Messenger 的远程代理
  3. 服务端调用 replyTo.send(reply) 时,走的也是同样的 Binder IPC 流程,只是方向相反

这样就实现了 客户端 → 服务端 → 客户端 的完整双向通信,而两端代码都是基于 Handler 的异步消息模型。

八、写在最后

Messenger 是 Android IPC 的"轻量级选手",它牺牲了一部分灵活性(不能同步返回值、不能随意定义接口),换来了极简的开发体验。如果你的跨进程需求只是"发消息、等回调",Messenger 往往是比 AIDL 更优雅的选择。

📌 一句话记住: Messenger 就是 Handler 的"跨进程版",一端发消息、一端 Handler 处理,双向通信靠 replyTo 贴地址。

相关推荐
gxgldyh2 小时前
Android Framework源码解析(十):Service启动全流程——从startService到Service执行的源码深度解析
android
换元不配限2 小时前
Android Studio Dolphin 新版Logcat 使用指南:告别混乱日志,高效定位问题
android·android studio·logcat
Android-Flutter3 小时前
android Binder 应用层开发 详解
android·binder
文人sec16 小时前
MySQL主库出问题了,从库怎么办?备库为什么会延迟好几个小时?
android·数据库·mysql
hai_android17 小时前
不用 AIDL,手把手教你手写 Binder 代理类
android·kotlin
mmsx18 小时前
osmdroid 地图实战 03|地图的"分层世界":离线方案、图层模型与 Overlay 体系
android·前端
张文是假的啊19 小时前
IOC依赖注入问题:@Primary 和 @Qualifier的使用
android
换元不配限19 小时前
ConstraintLayout核心用法详解(三):引导线、屏障、组和占位
android·placeholder·barrier·约束布局·guideline
雨白20 小时前
Kotlin 泛型进阶:攻克型变与实化,直击痛点
kotlin