Android多进程通信之带回调的AIDL实现

需求背景

我们都知道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,或者用全局的键值对存储工具,更大的需求就可以写共享文件,再发单次成功的通知让对方去获取。

相关推荐
一起搞IT吧5 分钟前
相机Camera日志实例分析之五:相机Camx【萌拍闪光灯后置拍照】单帧流程日志详解
android·图像处理·数码相机
浩浩乎@24 分钟前
【openGLES】安卓端EGL的使用
android
Kotlin上海用户组2 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19962 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸2 小时前
Flutter 生命周期完全指南
android·flutter·ios
阿幸软件杂货间2 小时前
阿幸课堂随机点名
android·开发语言·javascript
没有了遇见3 小时前
Android 渐变色整理之功能实现<二>文字,背景,边框,进度条等
android
没有了遇见4 小时前
Android RecycleView 条目进入和滑出屏幕的渐变阴影效果
android
站在巨人肩膀上的码农4 小时前
去掉长按遥控器power键后提示关机、飞行模式的弹窗
android·安卓·rk·关机弹窗·power键·长按·飞行模式弹窗
呼啦啦--隔壁老王4 小时前
屏幕旋转流程
android