需求背景
我们都知道Android系统的IPC(多进程通信)方式有很多种,像全局广播,系统数据库监听,还有最广为使用的AIDL接口。
最近我收到了来自语音app的需求,他们为了可以顺利注册可见即可说(扫描模拟点击),需要我在打开和关闭界面的时候给他们发个通知。因为我们的应用是采用windowmanager添加的一个临时弹窗,不能通过ActivityStack栈监听到变化,所以需要做这么一个接口给他们。
由于之前已经给语音侧打包过AIDL依赖包,提供过打开关闭界面,获取模式状态等接口了,所以希望基于已有的接口上进行扩展,使用注册监听的形式来给他们发回调通知,告诉他们界面状态有变动。
刚着手准备做,突然想起来这个带回调的AIDL接口好像之前还没有开发过,于是简单看了下,记录下开发过程。不得不感叹谷歌真的懒,这套AIDL的写法,感觉还是有点繁琐。光记录这个加回调AIDL方法的过程实在太短了,这里将我给外部app提供依赖包的习惯也一起记录下吧。
以aar的形式提供AIDL接口
Google官方的例程一般是,作为服务端的app和作为客户端的app两边建立一个同样包名的AIDL文件夹,客户端再去自己做一个bindService的操作,连接成功时,用返回来的binder对象将服务给实例化,使用其中的方法。但是就拿语音距离,大家都这么给语音客户端提供接口的话,他们那边的项目复杂度要爆炸了,所以一般是由提供接口的Server端将客户端连服务的操作也自己实现了,作为一个本地的library模组,一起打一个aar包提供出去。
接下来以两个Demo的代码示例演示下这个打包的流程,我们以一个车速模拟的需求为例:
新建模组配置gradle
在项目里new一个Module,记得选择Library类型,AS会自动帮我们填到setting.gradle.kts中。
接下来我们在新module的gradle构建配置中开启aidl,这样才能新建aidl接口声明文件。第二个改动就是配置构建输出文件名。
kotlin
buildFeatures {
aidl = true
}
val aarName = "CarSpeedMock"
android.libraryVariants.configureEach {
outputs.all {
if (this is com.android.build.gradle.internal.api.LibraryVariantOutputImpl) {
this.outputFileName = "${aarName}_1.0.aar"
}
}
}
这样我们在打包的时候就可以顺利生成一个aar依赖包的输出文件,将这个依赖包提供给接口使用方,可以大大简化他们的工作量,解耦代码,可增强可维护性。
开始实现AIDL接口回调需求
配置Server与Client代理的aidl文件
我们需要模拟一下车速数据的回调,需要新建一个主要的接口声明文件,里面添加一个设置回调的方法,然后需要再声明一个回调接口,里面是onChange的方法。
java
// ISpeedCallback.aidl
interface ISpeedCallback {
void onSpeedChange( int speed);
}
// SpeedMockManager.aidl
import com.stephen.vehiclesettings.ISpeedCallback;
interface SpeedMockManager {
void addSpeedCallback(ISpeedCallback callback);
}
然后将整个文件夹都复制到新建的模组中去,保证客户端和服务端两边的aidl文件夹完全相同。
项目里的两边同步之后,我们点击小锤子build一下,以便AS可以生成格式化的stub文件。
实现服务端和客户端的逻辑
在服务端里,我们在onBind方法里返回一个binder对象,它要实现 SpeedMockManager.Stub接口,才可以和客户端顺利对接。
服务端代码如下:
kotlin
class SpeedMockService : Service() {
override fun onCreate() {
super.onCreate()
infoLog()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
infoLog()
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent) = object : SpeedMockManager.Stub() {
@Throws(RemoteException::class)
override fun addSpeedCallback(callback: ISpeedCallback?) {
// 采用协程循环和延时来模拟回调车速数据
CoroutineScope(Dispatchers.IO).launch {
repeat(1000) { data ->
delay(2000L)
infoLog("aidl send int data: $data")
try {
// 调用传进来的1回调接口的方法,将数据返回去
callback?.onSpeedChange(data)
} catch (e: Exception) {
e.printStackTrace()
this.cancel()
}
}
}
}
}
}
同样的在客户端代理里面,去执行bindService。需要注意的是,我们的客户端代理类里面,需要多设置一个连接服务成功的接口,在连接成功的时候通知到使用方,我们的服务这时候绑定成功了,以后可以使用里面的方法了。
客户端代理代码如下:
kotlin
object CarSpeedProxy {
private const val TAG = "CarSpeedProxy"
private lateinit var carspeedManager: SpeedMockManager
fun init(context: Context, connectListener: CarSpeedServiceConnectListener) {
context.bindService(Intent("com.stephen.vehiclesettings.service.SpeedMockService").apply {
setPackage("com.stephen.vehiclesettings")
}, object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i(TAG, "onServiceConnected: name:$name")
// 接口实例化
carspeedManager = SpeedMockManager.Stub.asInterface(service)
// 连接成功回调,告知使用方
connectListener.onServiceConnected()
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.i(TAG, "onServiceDisconnected: name:$name")
connectListener.onServiceDisConnected()
}
}, Context.BIND_AUTO_CREATE)
}
fun addCarSpeedCallback(callback: ISpeedCallback) {
// 添加回调接口,使用方就可以接收数据了
try {
carspeedManager.addSpeedCallback(callback)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
/**
* 服务连接接口
*/
interface CarSpeedServiceConnectListener{
fun onServiceConnected()
fun onServiceDisConnected()
}
}
需求方使用aar
服务端和客户端的逻辑都完成后,将这个依赖包提供给需求方展开调试,看看服务连接和数据回调有没有问题。注意此Demo运行在我自己编译的系统中,而且配置了系统签名,拥有System权限,所以不用考虑服务保活问题。
使用这个接口的app,需要将我们提供的aar依赖包放在libs文件夹下,并在gradle里添加编译路径。这里我也新建一个需求方的Demo来测试我们的aar是否工作正常,使用我们aar里的客户端代理类去初始化连接服务,添加回调,并接收来自服务端模拟的车速数据了。
我们在Demo里也使用一个单例类来专门管理这个aar的业务逻辑,进一步解耦。在Application初始化调用init方法连接服务,在正式的使用处确保连接成功后,再添加回调,拿取数据:
kotlin
object AidlCallbackTest {
private var isServiceReady = false
fun init() {
CarSpeedProxy.init(appContext, object : CarSpeedProxy.CarSpeedServiceConnectListener {
override fun onServiceConnected() {
LogUtils.i("AidlCallbackTest", "onServiceConnected")
isServiceReady = true
}
override fun onServiceDisConnected() {
LogUtils.i("AidlCallbackTest", "onServiceDisConnected")
}
})
}
fun addCallback(callback: ISpeedCallback) {
if (isServiceReady)
CarSpeedProxy.addCarSpeedCallback(callback)
}
}
在方法调用处,我们需要传入的这个回调,即一个匿名内部类继承自ISpeedCallback.Stub,实现它的onSpeedChange(a:Int)回调方法,就可以接收到数据了:
kotlin
AidlCallbackTest.addCallback(object : ISpeedCallback.Stub() {
override fun onSpeedChange(speedData: Int) {
LogUtils.i(TAG, "onSpeedChange speed:$speedData")
}
})
先run进去服务端,再run刚刚写的客户端,两个进程都运行起来后,查看AS的Log打印我们可以看到服务成功连接了。再点击Demo的按钮添加上callback回调,可以看到Server和Client两边的模拟数据的打印也都打印出来了,设备不在手边就不再截图了。
这样一个带回调的AIDL接口,就成功地以aar的形式提供给需求方,由他们去使用数据做业务了。
扩展
最后,如果需要扩展接口功能,我们不止可以在AIDL接口中回调像int和String这种基本数据类型,也可以传输一个实现了实现了Parcelable接口的实体类,获取其中的多样化的属性数据。
总结一下就是,简单需求用AIDL接口基本数据类型就足够了。大量数据我们可以用系统数据库的方式传输JSON,或者用全局的键值对存储工具,更大的需求就可以写共享文件,再发单次成功的通知让对方去获取。