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

相关推荐
千里马学框架20 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台20 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone21 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc21 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work1 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work1 天前
ch21 签名、校验与发版
kotlin
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui