一个简单的跨进程通信SDK

前言

介绍下工作中一个有意思的跨进程SDK,其原理是通过ContentProvider+Binder来实现跨进程通信的封装,且客户端与服务提供端的接入成本较低。

客户端在发起调用时仅需要几行代码即可实现跨进程通信调用。

kotlin 复制代码
val methodName = "methodName" 
val params = "{\"key\":\"value\"}" 
val res = apiClientImpl.call(methodName, params) Log.d(TAG, "API call result: $res")

客户端不知道服务端是谁,服务端也不知道自己的客户端是谁,全由SDK来充当服务发现者,颇有SOA架构的味道。

周末抽空简单复现了下实现。

前置要求

客户端

实现ApiClient

创建继承自SDK库中的 ApiClient 的实现类:

kotlin 复制代码
class ApiClientImpl(val activityContext: Context) : ApiClient() {
    override fun getModuleName(): String {
        return "demo"
    }

    override fun getAppId(): String {
        return "server"
    }

    override fun getContext(): Context {
        return activityContext
    }

}
  • getAppId()是用于获取服务应用的包名。
  • getModuleName()是识别获取服务应用的服务名。
  • getContext()是用来获取服务端的ContentProvider。

服务端

1. 注册ContentProvider

在服务端的 AndroidManifest.xml 中注册SDK中的ApiPublisher

xml 复制代码
<provider
    android:name="com.ben.ipc.ApiPublisher"
    android:authorities="com.ben.server.provider"
    android:enabled="true"
    android:exported="true" />

2. 实现服务逻辑

创建实现 IpcServerListener 接口的服务类:

kotlin 复制代码
object ServerImplementation : IpcServerListener {

    val TAG = ServerImplementation::class.simpleName

    fun init() {
        Log.d(TAG, "ServerImplementation initialized")
        RemoteApiImpl.listener = this
    }

    override fun onCall(
        moduleName: String,
        method: String?,
        arg: String?
    ): String {
        Log.d(TAG, "onCall moduleName: $moduleName, method: $method, arg: $arg")
        return "Invokes method $method with arg $arg in module $moduleName"
    }
    
}

SDK实现

SDK采用了分层架构设计,通过AIDL和ContentProvider实现跨进程通信。以下是各个核心组件的实现细节:

1. 核心接口层

AIDL接口定义

定义了跨进程通信的标准接口:

aidl 复制代码
interface IRemoteApi {
    String onCall(String moduleName, String method, String param);
}

这个接口采用了通用的方法调用模式,通过 moduleNamemethodparam 三个参数实现灵活的服务调用。

  • moduleName是用于区分同一服务端的不同服务类或服务模块。
  • method是要调用具体的方法。
  • param的参数。

2. 客户端实现层

ApiClient抽象基类

提供了客户端的抽象接口:

kotlin 复制代码
abstract class ApiClient {
    abstract fun getModuleName(): String
    abstract fun getAppId(): String
    abstract fun getContext(): Context
    
    fun call(methodName: String, paras: String): String {
        return ApiManager.call(getAppId(), getModuleName(), methodName, paras, getContext())
    }
}

设计特点:

  • 抽象化客户端配置信息(模块名、应用ID、上下文)
  • 提供统一的调用入口 call() 方法
  • 将具体的路由逻辑委托给 ApiManager
ApiManager服务管理器

负责应用ID到包名的映射和路由:

kotlin 复制代码
object ApiManager {
    private val appIdToPackageId = mutableMapOf<String, String>().apply {
        put("server", "com.ben.server")
    }
    
    fun call(appId: String, moduleName: String, method: String, params: String, context: Context): String {
        val packageId = appIdToPackageId[appId]
            ?: throw IllegalArgumentException("No package ID found for app ID: $appId")
        return Router.route(packageId, moduleName, method, params, context)
    }
}

核心功能:

  • 维护应用ID与包名的映射关系
  • 提供服务发现机制
  • 参数验证和错误处理
Router路由器

实现具体的跨进程通信逻辑:

kotlin 复制代码
object Router {
    fun route(packageId: String, moduleName: String, method: String, arg: String?, context: Context): String {
        val builder = Uri.Builder()
        builder.scheme("content").authority(packageId + "." + ApiPublisher::class.simpleName)
        val uri = builder.build()
        
        val contentResolver = context.contentResolver
        val contentProviderClient = contentResolver.acquireContentProviderClient(uri)
        
        if (contentProviderClient != null) {
            val bundle = contentProviderClient.call(method, arg, null)
            val binder = bundle?.getBinder("binder")
            val remoteApiImpl = IRemoteApi.Stub.asInterface(binder)
            val res = remoteApiImpl.onCall(moduleName, method, arg)
            contentProviderClient.release()
            return res
        }
        return "-1"
    }
}

实现细节:

  • 构建ContentProvider的URI(格式:content://{packageId}.ApiPublisher
  • 通过ContentResolver获取远程ContentProvider
  • 调用ContentProvider的 call() 方法获取IBinder
  • 将IBinder转换为AIDL接口代理
  • 执行远程方法调用并返回结果

3. 服务端实现层

ApiPublisher服务发布者

作为ContentProvider暴露服务:

kotlin 复制代码
class ApiPublisher : ContentProvider() {
    private val remoteApi: IBinder = RemoteApiImpl
    
    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
        val res = Bundle()
        res.putBinder("binder", remoteApi)
        return res
    }
}

核心功能:

  • 继承ContentProvider提供跨进程访问能力
  • call() 方法中返回IBinder对象
  • 作为服务注册中心的角色
RemoteApiImpl服务实现

实现AIDL接口:

kotlin 复制代码
object RemoteApiImpl : IRemoteApi.Stub() {
    var listener: IpcServerListener? = null
    
    override fun onCall(moduleName: String, method: String?, param: String?): String {
        return listener?.onCall(moduleName, method, param) ?: "No listener set"
    }
}

设计模式:

  • 采用委托模式,将具体业务逻辑委托给 IpcServerListener
  • 作为AIDL接口的具体实现,处理跨进程调用
  • 使用单例模式确保全局唯一
IpcServerListener业务接口

定义服务端业务接口:

kotlin 复制代码
interface IpcServerListener {
    fun onCall(moduleName: String, method: String?, arg: String?): String
}

5. SDK架构优势

分层解耦
  • 接口层:AIDL定义标准通信协议
  • 客户端层:ApiClient、ApiManager、Router处理客户端逻辑
  • 服务端层:ApiPublisher、RemoteApiImpl处理服务端逻辑
  • 业务层:IpcServerListener处理具体业务逻辑
扩展性设计
  • 服务发现:通过ApiManager的映射表支持多服务注册
  • 协议统一:通过AIDL接口统一通信协议
  • 业务解耦:通过监听器模式分离框架和业务代码
通信流程
  1. 客户端调用 → ApiClient.call()
  2. 服务发现 → ApiManager查找目标包名
  3. 路由转发 → Router构建URI并获取ContentProvider
  4. 获取服务 → ContentProvider返回IBinder
  5. 远程调用 → 通过AIDL代理调用远程方法
  6. 业务处理 → RemoteApiImpl委托给IpcServerListener
  7. 结果返回 → 沿原路径返回调用结果

这种设计实现了客户端和服务端的完全解耦,支持动态服务发现,具有良好的可扩展性和维护性。

写在最后

上述的代码是简化了的实现,是存在可优化点的,如缓存Binder实例,如在SDK侧进行方法名的校验等等。

有错误欢迎指出。

相关推荐
阿巴斯甜12 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker13 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952714 小时前
Andorid Google 登录接入文档
android
黄林晴15 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android