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

相关推荐
程序leo源26 分钟前
C语言:操作符详解1
android·java·c语言·c++·青少年编程·c#
sunly_6 小时前
Flutter:AnimatedPadding动态修改padding
android·flutter
诸神黄昏EX6 小时前
Android 常用命令和工具解析之GPU相关
android
顾北川_野6 小时前
Android 布局菜单或按钮图标或Menu/Item设置可见和不可见
android
练习本6 小时前
android 动画原理分析
android
别拿曾经看以后~6 小时前
原生Android调用uniapp项目中的方法
android·vue.js·uni-app
Winston Wood6 小时前
Android Binder技术概览
android·binder·进程通信
踏雪羽翼6 小时前
android 使用实现音效--Equalizer
android·音效·eqequalizer·bassboost·presetreverb
老码沉思录6 小时前
Android开发实战班 - Android开发基础之 Kotlin语言基础与特性
android·微信·kotlin
峥嵘life7 小时前
Android adb shell dumpsys audio 信息查看分析详解
android·adb