如何使用 Messenger 实现单向和双向通信?
解读
国内面试中,Messenger 不是“会不会用”的问题,而是“为什么用它、怎么用才安全、怎么证明你懂 Binder”的综合性考点。
面试官常把 Messenger 与 AIDL、广播、ContentProvider、Socket 并列,考察你对“轻量级 IPC”选型边界的理解;同时通过“单向/双向”追问,验证你对 Binder 单向序列化、Handler 线程模型、内存泄漏、Crash 隔离的掌握程度。
回答时必须给出可落地的模板代码,并主动说出“服务端回发时 Client 的 Messenger 从哪来”“如何避免 Client 端 Handler 内存泄漏”“服务端 crash 后如何快速感知”这三点,才能拿到高分。
知识点
- Messenger 本质是对 Binder 的二次包装,底层仍走 /dev/binder,数据载体是 Message,天然线程安全。
- 单向通信:Client 持有 Server 的 Messenger,只发不收;Server 端在 Service.onBind 返回 Messenger.getBinder()。
- 双向通信:Client 在 send() 前把自身的 Messenger 塞进 Message.replyTo 字段;Server 端收到后通过 msg.replyTo.send() 回传。
- 线程模型:Server 端 Messenger 内部构造的 Handler 运行在主线程,耗时任务需手动切线程;Client 端回传 Handler 建议放在子线程并配合 WeakReference,防止 Service 持引用导致 Activity 泄漏。
- 数据限制:Message.obj 只能传实现了 Parcelable 的对象,且大小受 Binder 1M-8K 缓冲区限制;大图必须走共享内存或 ContentProvider openFile。
- 稳定性:服务端进程被杀后 Client 会收到 DeadObjectException,需捕获并重连;Client 端可通过 DeathRecipient 监听 BinderDied。
- 国内 ROM 特性:华为、小米、OPPO 后台冻结策略下,Service 拉回前台需同时调用 startForeground 并申请“自启动”白名单,否则双向回传会延迟甚至丢失。
- 与 AIDL 对比:Messenger 队列化、单线程、无并发,适合低频调用;AIDL 支持并发回调,但需写 .aidl 文件,维护成本高。
- 与广播对比:Messenger 点对点、隐私数据不外泄;广播易被系统拦截且 Android 14 后台广播受限。
- 安全加固:服务端在 onBind 时校验调用方包名/签名,防止第三方 App 通过隐式 Intent 绑定;敏感指令配合自定义权限 signature|privileged。
答案
-
单向通信(Client → Server)
服务端class SingleWayService : Service() { private val handler = Handler(Looper.getMainLooper()) { msg -> when (msg.what) { 1 -> Log.d("Server", "收到单向消息:${msg.arg1}") } true } private val messenger = Messenger(handler) override fun onBind(intent: Intent): IBinder = messenger.binder }客户端
val intent = Intent().setComponent( ComponentName("com.example.server", "com.example.server.SingleWayService") ) bindService(intent, object : ServiceConnection { var serverMessenger: Messenger? = null override fun onServiceConnected(name: ComponentName, binder: IBinder) { serverMessenger = Messenger(binder) val msg = Message.obtain(null, 1).apply { arg1 = 10086 } serverMessenger?.send(msg) } override fun onServiceDisconnected(name: ComponentName) {} }, BIND_AUTO_CREATE) -
双向通信(Client ⇄ Server)
服务端class TwoWayService : Service() { private val handler = Handler(Looper.getMainLooper()) { msg -> when (msg.what) { 2 -> { val clientMessenger = msg.replyTo val reply = Message.obtain(null, 3).apply { arg1 = msg.arg1 * 2 } try { clientMessenger?.send(reply) } catch (e: RemoteException) { // Client 进程被杀 } } } true } private val messenger = Messenger(handler) override fun onBind(intent: Intent): IBinder = messenger.binder }客户端
class ClientActivity : AppCompatActivity() { private var serverMessenger: Messenger? = null private val clientHandler = object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { if (msg.what == 3) { Log.d("Client", "收到服务端回执:${msg.arg1}") } } } private val clientMessenger = Messenger(clientHandler) private val conn = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, binder: IBinder) { serverMessenger = Messenger(binder) val msg = Message.obtain(null, 2).apply { arg1 = 42 replyTo = clientMessenger // 关键字段 } serverMessenger?.send(msg) } override fun onServiceDisconnected(name: ComponentName) {} } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val intent = Intent().setComponent( ComponentName("com.example.server", "com.example.server.TwoWayService") ) bindService(intent, conn, BIND_AUTO_CREATE) } override fun onDestroy() { super.onDestroy() unbindService(conn) clientHandler.removeCallbacksAndMessages(null) // 防泄漏 } }要点小结
- replyTo 必须手动设置,否则服务端无法回传。
- 服务端回传前捕获 RemoteException,防止 Client 被杀导致 Service Crash。
- Client 端在 onDestroy 释放 Handler 回调,避免 Service 持引用造成内存泄漏。
- 国内后台限制场景下,Service 端需在 5 秒内调用 startForeground(),否则双向通信会被系统中断。
拓展思考
- 如果业务需要“一对多”广播式回传,Messenger 模型会因 replyTo 只能指向单 Client 而失效,此时应改用 AIDL + RemoteCallbackList 或 BroadcastReceiver + IntentFilter(Android 14 需加 RECEIVER_EXPORTED 标记)。
- 对性能要求极高的车载场景,Messenger 的序列化/队列化会成为瓶颈,可改用共享内存(MemoryFile + ParcelFileDescriptor)+ Binder 一次握手传递 fd,实现零拷贝。
- 国内隐私合规趋严,若 Messenger 传输 IMEI、定位等敏感信息,需在 Manifest 声明 android:protectionLevel="signature" 的自定义权限,并在服务端 onBind 通过 checkCallingPermission() 校验;否则应用市场审核会被驳回。
- 为了灰度容错,可在 Client 端封装 MessengerManager,内部维护“BinderDied + 指数退避重连”机制,当检测到 RemoteException 或 DeathRecipient 回调时,自动释放旧 Messenger 并重新 bindService,保证用户无感知。