一个简单的跨进程通信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侧进行方法名的校验等等。

有错误欢迎指出。

相关推荐
陈旭金-小金子28 分钟前
发现 Kotlin MultiPlatform 的一点小变化
android·开发语言·kotlin
二流小码农2 小时前
鸿蒙开发:DevEcoStudio中的代码提取
android·ios·harmonyos
江湖有缘3 小时前
使用obsutil工具在OBS上完成基本的数据存取【玩转华为云】
android·java·华为云
移动开发者1号4 小时前
Android 多 BaseUrl 动态切换策略(结合 ServiceManager 实现)
android·kotlin
移动开发者1号4 小时前
Kotlin实现文件上传进度监听:RequestBody封装详解
android·kotlin
AJi7 小时前
Android音视频框架探索(三):系统播放器MediaPlayer的创建流程
android·ffmpeg·音视频开发
柿蒂8 小时前
WorkManager 任务链详解:优雅处理云相册上传队列
android
alexhilton8 小时前
使用用例(Use Case)以让Android代码更简洁
android·kotlin·android jetpack
峥嵘life8 小时前
Android xml的Preference设置visibility=“gone“ 无效分析解决
android·xml
用户2018792831679 小时前
通俗故事:驱动二进制文件在AOSP中的角色
android