Android进阶宝典 -- 告别繁琐的AIDL吧,手写IPC通信框架,5行代码实现进程间通信(下)
接:Android进阶宝典 -- 告别繁琐的AIDL吧,手写IPC通信框架,5行代码实现进程间通信(上)
2.3 内部通讯协议完善
当客户端发起请求,想要执行某个方法的时候,首先服务端会先向Registery中查询注册的服务,从而找到这个要执行的方法,这个流程是在内部完成。
override fun send(request: Request?): Response? {
//获取服务对象id
val serviceId = request?.serviceId
val methodName = request?.methodName
val params = request?.params
// 反序列化拿到具体的参数类型
val neededParams = parseParameters(params)
val method = Registry.instance.findMethod(serviceId, methodName, neededParams)
Log.e("TAG", "method $method")
Log.e("TAG", "neededParams $neededParams")
when (request?.type) {
REQUEST_TYPE.GET_INSTANCE.ordinal -> {
//==========执行静态方法
try {
var instance: Any? = null
instance = if (neededParams == null || neededParams.isEmpty()) {
method?.invoke(null)
} else {
method?.invoke(null, neededParams)
}
if (instance == null) {
return Response("instance == null", -101)
}
//存储实例对象
Registry.instance.setServiceInstance(serviceId ?: "", instance)
return Response(null, 200)
} catch (e: Exception) {
return Response("${e.message}", -102)
}
}
REQUEST_TYPE.INVOKE_METHOD.ordinal -> {
//==============执行普通方法
val instance = Registry.instance.getServiceInstance(serviceId)
if (instance == null) {
return Response("instance == null ", -103)
}
//方法执行返回的结果
return try {
val result = if (neededParams == null || neededParams.isEmpty()) {
method?.invoke(instance)
} else {
method?.invoke(instance, neededParams)
}
Response(gson.toJson(result), 200)
} catch (e: Exception) {
Response("${e.message}", -104)
}
}
}
return null
}
当客户端发起请求时,会将请求的参数封装到Request中,在服务端接收到请求后,就会解析这些参数,变成Method执行时需要传入的参数。
private fun parseParameters(params: Array<Parameters>?): Array<Any?>? {
if (params == null || params.isEmpty()) {
return null
}
val objects = arrayOfNulls<Any>(params.size)
params.forEachIndexed { index, parameters ->
objects[index] =
gson.fromJson(parameters.value, Class.forName(parameters.className))
}
return objects
}
例如用户中心调用setUserInfo方法时,需要传入一个User实体类,如下所示:
UserManager().setUserInfo(User("ming",25))
那么在调用这个方法的时候,首先会把这个实体类转成一个JSON字符串,例如:
{
"name":"ming",
"age":25
}
为啥要”多此一举“呢?其实这种处理方式是最快速直接的,转成json字符串之后,能够最大限度地降低数据传输的大小,等到服务端处理这个方法的时候,再把Request中的params反json转成User对象即可。
fun findMethod(serviceId: String?, methodName: String?, neededParams: Array<Any?>?): Method? {
//获取服务
val serviceClazz = serviceMaps[serviceId] ?: return null
//获取方法集合
val methods = methodsMap[serviceClazz] ?: return null
return methods[rebuildParamsFunc(methodName, neededParams)]
}
private fun rebuildParamsFunc(methodName: String?, params: Array<Any?>?): String {
val stringBuffer = StringBuffer()
stringBuffer.append(methodName).append("(")
if (params == null || params.isEmpty()) {
stringBuffer.append(")")
return stringBuffer.toString()
}
stringBuffer.append(params[0]?.javaClass?.name)
for (index in 1 until params.size) {
stringBuffer.append(",").append(params[index]?.javaClass?.name)
}
stringBuffer.append(")")
return stringBuffer.toString()
}
那么在查找注册方法的时候就简单多了,直接抽丝剥茧一层一层取到最终的Method。在拿到Method之后,这里是有2种处理方式,一种是通过静态单例的形式拿到实例对象,并保存在服务端;另一种就是执行普通方法,因为在反射的时候需要拿到类的实例对象才能调用,所以才在GET_INSTANCE的时候存一遍。
3 客户端 - connect
在第二节中,我们已经完成了通讯协议的建设,最终一步就是客户端通过绑定服务,向服务端发起通信了。
3.1 bindService
/**
* 绑定服务
*
*/
fun connect(
context: Context,
pkgName: String,
action: String = "",
service: Class<out IPCService>
) {
val intent = Intent()
if (pkgName.isEmpty()) {
//同app内的不同进程
intent.setClass(context, service)
} else {
//不同APP之间进行通信
intent.setPackage(pkgName)
intent.setAction(action)
}
//绑定服务
context.bindService(intent, IpcServiceConnection(service), Context.BIND_AUTO_CREATE)
}
inner class IpcServiceConnection(val simpleService: Class<out IPCService>) : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val mService = IIPCServiceInterface.Stub.asInterface(service) as IIPCServiceInterface
binders[simpleService] = mService
}
override fun onServiceDisconnected(name: ComponentName?) {
//断连之后,直接移除即可
binders.remove(simpleService)
}
}
对于绑定服务这块,相信伙伴们也很熟悉了,这个需要说一点的就是,在Android 5.0以后,启动服务不能只依赖action启动,还需要指定应用包名,否则就会报错。
在服务连接成功之后,即回调onServiceConnected方法的时候,需要拿到服务端的一个代理对象,即IIPCServiceInterface的实例对象,然后存储在binders集合中,key为绑定的服务类class对象,value就是对应的服务端的代理对象。
fun send(
type: Int,
service: Class<out IPCService>,
serviceId: String,
methodName: String,
params: Array<Parameters>
): Response? {
//创建请求
val request = Request(type, serviceId, methodName, params)
//发起请求
return try {
binders[service]?.send(request)
} catch (e: Exception) {
null
}
}
当拿到服务端的代理对象之后,就可以在客户端调用send方法向服务端发送消息。
class Channel {
//====================================
/**每个服务对应的Binder对象*/
private val binders: ConcurrentHashMap<Class<out IPCService>, IIPCServiceInterface> by lazy {
ConcurrentHashMap()
}
//====================================
/**
* 绑定服务
*
*/
fun connect(
context: Context,
pkgName: String,
action: String = "",
service: Class<out IPCService>
) {
val intent = Intent()
if (pkgName.isEmpty()) {
intent.setClass(context, service)
} else {
intent.setPackage(pkgName)
intent.setAction(action)
intent.setClass(context, service)
}
//绑定服务
context.bindService(intent, IpcServiceConnection(service), Context.BIND_AUTO_CREATE)
}
inner class IpcServiceConnection(val simpleService: Class<out IPCService>) : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val mService = IIPCServiceInterface.Stub.asInterface(service) as IIPCServiceInterface
binders[simpleService] = mService
}
override fun onServiceDisconnected(name: ComponentName?) {
//断连之后,直接移除即可
binders.remove(simpleService)
}
}
fun send(
type: Int,
service: Class<out IPCService>,
serviceId: String,
methodName: String,
params: Array<Parameters>
): Response? {
//创建请求
val request = Request(type, serviceId, methodName, params)
//发起请求
return try {
binders[service]?.send(request)
} catch (e: Exception) {
null
}
}
companion object {
private val instance by lazy {
Channel()
}
/**
* 获取单例对象
*/
fun getDefault(): Channel {
return instance
}
}
}
3.2 动态代理获取接口实例
回到1.2小节中,我们定义了一个IUserManager接口,通过前面我们定义的通信协议,只要我们获取了IUserManager的实例对象,那么就能够调用其中的任意普通方法,所以在客户端需要设置一个获取接口实例对象的方法。
fun <T> getInstanceWithName(
service: Class<out IPCService>,
classType: Class<T>,
clazz: Class<*>,
methodName: String,
params: Array<Parameters>
): T? {
//获取serviceId
val serviceId = clazz.getAnnotation(ServiceId::class.java)
val response = Channel.getDefault()
.send(REQUEST.GET_INSTANCE.ordinal, service, serviceId.name, methodName, params)
Log.e("TAG", "response $response")
if (response != null && response.result) {
//请求成功,返回接口实例对象
return Proxy.newProxyInstance(
classType.classLoader,
arrayOf(classType),
IPCInvocationHandler()
) as T
}
return null
}
当我们通过客户端发送一个获取单例的请求后,如果成功了,那么就直接返回这个接口的单例对象,这里直接使用动态代理的方式返回一个接口实例对象,那么后续执行这个接口的方法时,会直接走到IPCInvocationHandler的invoke方法中。
class IPCInvocationHandler(
val service: Class<out IPCService>,
val serviceId: String?
) : InvocationHandler {
private val gson = Gson()
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
//执行客户端发送方法请求
val response = Channel.getDefault()
.send(
REQUEST.INVOKE_METHOD.ordinal,
service,
serviceId,
method?.name ?: "",
args
)
//拿到服务端返回的结果
if (response != null && response.result) {
//反序列化得到结果
return gson.fromJson(response.value, method?.returnType)
}
return null
}
}
因为服务端在拿到Method的返回结果时,将javabean转换为了json字符串,因此在IPCInvocationHandler中,当调用接口中方法获取结果之后,用Gson将json转换为javabean对象,那么就直接获取到了结果。
3.3 框架使用
服务端:
UserManager2.getDefault().setUserInfo(User("ming", 25))
IPC.register(UserManager2::class.java)
同时在服务端需要注册一个IPCService的实例,这里用的是IPCService01
<service
android:name=".UserService"
android:enabled="true"
android:exported="true" />
<service
android:name="com.lay.ipc.service.IPCService01"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.GET_USER_INFO" />
</intent-filter>
</service>
客户端:
调用connect方法,需要绑定服务端的服务,传入包名和action
IPC.connect(
this,
"com.lay.learn.asm",
"android.intent.action.GET_USER_INFO",
IPCService01::class.java
)
首先获取IUserManager的实例,注意这里要和服务端注册的UserManager2是同一个ServiceId,而且接口、javabean需要存放在与服务端一样的文件夹下。
val userManager = IPC.getInstanceWithName(
IPCService01::class.java,
IUserManager::class.java,
"getDefault",
null
)
val info = userManager?.getUserInfo()
通过动态代理拿到接口的实例对象,只要调用接口中的方法,就会进入到InvocationHandler中的invoke方法,在这个方法中,通过查找服务端注册的方法名从而找到对应的Method,通过反射调用拿到UserManager中的方法返回值。
这样其实就通过5-6行代码,就完成了进程间通信,是不是比我们在使用AIDL的时候要方便地许多。
4 总结
如果我们面对下面这个类,如果这个类是个私有类,外部没法调用,想通过反射的方式调用其中某个方法。
@ServiceId(name = "UserManagerService")
public class UserManager2 implements IUserManager {
private static UserManager2 userManager2 = new UserManager2();
public static UserManager2 getDefault() {
return userManager2;
}
private User user;
@Nullable
@Override
public User getUserInfo() {
return user;
}
@Override
public void setUserInfo(@NonNull User user) {
this.user = user;
}
@Override
public int getUserId() {
return 0;
}
@Override
public void setUserId(int id) {
}
}
那么我们可以这样做:
val method = UserManager2::class.java.getDeclaredMethod("getUserInfo")
method.isAccessible = true
method.invoke(this,params)
其实这个框架的原理就是上面这几行代码所能够完成的事;通过服务端注册的形式,将UserManager2中所有的方法Method收集起来;当另一个进程,也就是客户端想要调用其中某个方法的时候,通过方法名来获取到对应的Method,调用这个方法得到最终的返回值。
作者:layz4android
来源:juejin.cn/post/7192465342159912997