type
status
date
slug
summary
tags
category
icon
password
前言
AIDL(Android 接口定义语言)是 Android 提供的一种进程间通信(IPC)机制,用于定义客户端与服务端之间的编程接口。在 Android 中,每个进程运行在独立的内存中,相互隔离。但有时我们需要应用之间进行数据传递或任务委托,这时就可以使用 AIDL。通过 AIDL,我们可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,满足进程间通信的需求。
有关 AIDL 详细介绍可直接阅读官网的 Android 接口定义语言 (AIDL) ,这篇博客,就让我们专注于通信的进程双方死亡的互相监听和对 AIDL 接口使用时的安全封装。
双向死亡监听
AIDL 通信的客户端和服务端,其中一方出现进程崩溃、进程被杀死、覆盖安装之类情况时,无法继续进行通信,而存活的一方持有的无效的引用还有可能导致
DeadObjectException
,这就需要采取适当的措施处理这种情况,例如重新绑定服务、捕获 DeadObjectException
异常或执行其他清理操作。如果客户端和服务端需要长期保持连接,例如下面的示例,客户端向服务发生消息并监听来自服务端的消息,便可使用双向死亡监听的方式处理异常情况的发生。
示例程序
定义
.aidl
文件如下服务端的实现如下
服务端注意事项说明
① 向客户端分发消息时,要处理有可能得 DeadObjectException 异常
② aidl 文件中定义的接口,如果包含回调,依然有可能发生 DeadObjectException 异常
③ 服务端没有客户端与其绑定时,会结束自己
④ 这个示例不包含前台服务的相关处理
客户端绑定服务的代码如下
客户端注意事项说明
①
application.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
的 serviceConnection
回调是在主线程中,服务绑定的结果通过 UI 线程的 handler 。② 可以不用注册服务的讣告监听,
onServiceDisconnected
会回调③ bindService 返回服务是否绑定成功,但回调之后才能拿到服务端的代理
连接后执行
Android 多进程通信,不可避免的会遇到客户端或者服务端 anr、crash、oom 或覆盖安装等问题,这些问题发生时,怎么合理地处理这些问题,在便捷和安全之间找到一种合适的平衡?便是下面要探讨的几种方式。
使用回调
客户端本质上主要处理一个问题:客户端与服务端尚未建立连接时,如何调用 AIDL 定义的方法。
对服务端代理的封装
客户端注意事项说明
① 绑定服务成功或失败,添加对应的回调,注意 ServiceConnection 回调在主线程,其它函数调用在当前所在线程。
② 调用 add 函数的示例,如果服务未连接,先进行服务连接,MethodCallback 回调在所调用它的线程中。
除了使用回调,也可以使用任务队列来保证先连接后执行;但回调的方式更易于理解,并且通常情况下我们希望调用 AIDl 方法的当时就能知悉执行结果。
使用协程
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
回调中拿到 IBinder
,我们可以使用 kotlin 协程把回调的写法转成流式的写法,这样就能线性地写代码,不用跳来跳去,kotlin 协程本质上是回调,在这个场景下,RxJava 并不适用(RxJava 流式写法,例如blockingFirst
阻塞执行,会阻塞主线程导致 ServiceConnection 无法正常回调)。对服务端代理的封装
客户端注意事项说明
① 使用 kotlin 协程把回调转成流式写法。
② 在 kotlin 协程作用域内,保证连接服务先执行。
如果项目代码大量使用 kotlin,自然使用 kotlin 协程这种方式更合适。
阻塞执行 bindService (Android Api >29 )
在 Android Api >29 是,bindService 增加了一个重载方法
基于这个方法,便可对客户端连接代码进行改写
对服务端代理的封装
客户端注意事项说明
① 同步工具类控制 bindService 阻塞执行,也可以使用别的加锁机制。
相对而言,这种方式代码量是最少的,还是要进行异常传递、耗时调用在子线程中进行等处理。
Feature
验证 Binder 传输数据大小限制, 二进制数据的长度限制确实是
1MB - 8KB
这篇写的好凌乱 o(╥﹏╥)o
Binder 机制原理,这个留在后续单独写一个 OpenBinder 的 demo,从 c++ 角度写一篇博客