IPC
- 前言
- 跨进程通信方式
- 跨进程通信框架
- 涉及到的技术
- 使用Request-Response思想
- IPCRequest
- IPCResponse
- RemoteService
- 服务端
- 客户端
- 附带
- 项目依赖
前言
由于应用中存在多个进程,比如一个主进程,一个消息推送进程及一个应用守护进程,这使得进程间通信的开发提上日程,而为了提高开发效率,需要提炼出一套跨进程通信的基础框架,以本篇博客记录其编写过程
跨进程通信方式
目前在Android里存在哪些跨进程通信方式呢?主要有
- 文件共享:并发要求和实时性要求低的可以使用文件共享;同时Linux机制下,可以对文件并发写,需要处理同步;Windows下不支持并发读或写
- Bundle/Intent: 使用简单,不过只能发送Bundle支持的数据类型
- Messenger:Messenger是基于AIDL实现的,服务端(被动方)提供一个Service来处理客户端(主动方)连接,维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理
- ContentProvider:底层也是Binder实现,主要用来为其他APP提供数据,可以说天生就是为进程通信而生的,比较适合结构化数据集的访问
- Broadcase:一种单向的通信方式,当一个程序发送广播后,其他应用只能被动地接收,无法向发送者反馈;效率低,安全性低
- LocalSocket:适合于高并发,实时性要求高的需求,实际使用起来坑比较多,跟你使用Socket进行网络通信一样需要处理很多东西,比如心跳,粘包,断线重连等,还有长连接对资源消耗比较大,不过功能还是挺强大的
- AIDL:使用成本比较高,但是数据交互能力强大
跨进程通信框架
涉及到的技术
本篇文章搭建跨进程通信框架需要使用到的技术有:
- AIDL的使用
- 动态代理的使用
- 运行时注解的使用
- 反射的使用
如果大家对这四个技术不熟悉,可以翻看博主以前的文章,这些知识点都有文章进行解析
关于AIDL的使用可以参考博主的
Android四大组件之Service 远程服务解析,通过AIDL达到进程间通信交换基础数据Android四大组件之Service 远程服务 通过AIDL进行进程间复杂类型数据交换
关于动态代理的参考Android开发如何理解Java静态代理 动态代理及动态生成代理对象原理 看这篇就够了
关于反射的参考Android开发-带你玩转Android中的反射机制,再也不怕private修饰了
关于Binder的理解可以参考
从源码解析-Android中进程间通信Binder机制之Linux基础 【一】从源码解析-Android中进程间通信Binder机制之Binder驱动 【二】
使用Request-Response思想
通常情况下我们通过AIDL进行跨进程通信,最少需要编写一个aidl接口文件,比如
你可以在这个文件里定义很多方法,或者将方法定义在不同的aidl文件中
某些时候你可能在方法里传的参数越来越多,然后将这些参数封装成一个对象,这时候还得编写对象对应的aidl文件,如:
parcelable Result;
当你的业务需求越来越复杂,对应的aidl接口文件和aidl序列化对象文件就越来越多,将对后期的管理和维护造成很大的困扰;同时开发人员要处理好数据的序列化操作,同时还需要定义很多的远程Service,这些无疑是我们在使用AIDL进行进程间通信的痛点
本框架的编写思路来源于网络通信,在一个HTTP请求中,有Request,有Response,客户端不需要关注具体通信的细节,只需要知道发出一个Request,最后收到一个Reponse;这两个对象将我们的请求信息和返回信息全部封装起来,这样你也就不需要再定义其它的aidl文件了,只需要三个,如下
只需要这三个文件,且IRemoteService里只需要定义一个方法就能搞定我们的跨进程通信
IPCRequest
这个对象封装了我们的请求信息,因为在服务端定义了一个通用的远程Service,将客户端的请求转发给相应的接口处理,这样就与业务解耦;所以客户端需要告诉服务端自己的请求参数,要求某个类型的接口处理(客户端不指定,由服务端匹配决定),接口里的哪个方法具体执行,如下
package com.mango.ipcore;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
/**
* Author: mango
* Time: 2019/6/12 20:45
* Version:
* Desc: TODO(封装请求信息)
*/
public class IPCRequest implements Parcelable {
//获取对象
public static final int LOAD_INSTANCE = 1;
//执行方法
public static final int LOAD_METHOD = 2;
//请求类型
private int type;
//服务端处理请求的类名
private String className;
//服务端处理请求的方法名
private String methodName;
//服务端处理请求的方法的参数
private IPCParameter[] parameters;
public IPCRequest() {}
public IPCRequest(int type, String className, String methodName, IPCParameter[] parameters) {
this.type = type;
this.className = className;
this.methodName = methodName;
this.parameters = parameters;
}
protected IPCRequest(Parcel in) {
type = in.readInt();
className = in.readString();
methodName = in.readString();
Parcelable[] parcelables = in.readParcelableArray(IPCParameter.class.getClassLoader());
if (parcelables != null) {
parameters = Arrays.copyOf(parcelables, parcelables.length, IPCParameter[].class);
}
}
public static final Creator<IPCRequest> CREATOR = new Creator<IPCRequest>() {
@Override
public IPCRequest createFromParcel(Parcel in) {
return new IPCRequest(in);
}
@Override
public IPCRequest[] newArray(int size) {
return new IPCRequest[size];
}
};
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public IPCParameter[] getParameters() {
return parameters;
}
public void setParameters(IPCParameter[] parameters) {
this.parameters = parameters;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeString(className);
dest.writeString(methodName);
dest.writeParcelableArray(parameters,flags);
}
@Override
public String toString() {
return "IPCRequest{" +
"type=" + type +
", className='" + className + '\'' +
", methodName='" + methodName + '\'' +
", parameters=" + Arrays.toString(parameters) +
'}';
}
}
这里面涉及到一个IPCParameter对象,它封装了具体的参数信息,客户端调用方法传入的参数最终会转化成IPCParameter传递给服务端
IPCResponse
这个类就比较简单了,封装了执行情况及返回值
package com.mango.ipcore;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Author: mango
* Time: 2019/6/12 20:45
* Version:
* Desc: TODO(执行结果封装)
*/
public class IPCResponse implements Parcelable {
//方法返回值序列化的结果
private String result;
//本次请求执行情况的描述信息
private String message;
//是否执行成功
private boolean success;
public IPCResponse(String result, String message, boolean success) {
this.result = result;
this.message = message;
this.success = success;
}
protected IPCResponse(Parcel in) {
result = in.readString();
message = in.readString();
success = in.readByte() != 0;
}
public static final Creator<IPCResponse> CREATOR = new Creator<IPCResponse>() {
@Override
public IPCResponse createFromParcel(Parcel in) {
return new IPCResponse(in);
}
@Override
public IPCResponse[] newArray(int size) {
return new IPCResponse[size];
}
};
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(result);
dest.writeString(message);
dest.writeByte((byte) (success ? 1 : 0));
}
@Override
public String toString() {
return "IPCResponse{" +
"result='" + result + '\'' +
", message='" + message + '\'' +
", success=" + success +
'}';
}
}
RemoteService
接下来就是远程Service了,接收客户端的请求,根据IPCRequest里的信息转发给相应的接口处理
package com.mango.ipcore.handler;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import com.mango.ipcore.IPCCache;
import com.mango.ipcore.IPCRequest;
import com.mango.ipcore.IPCResponse;
import com.mango.ipcore.IRemoteService;
import com.mango.ipcore.utils.ParamsConvert;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static com.mango.ipcore.IPCRequest.LOAD_INSTANCE;
import static com.mango.ipcore.IPCRequest.LOAD_METHOD;
/**
* Author:mango
* Time:2019/6/19 15:59
* Version:1.0.0
* Desc:TODO()
*/
public class RemoteService extends Service {
/**
* 后续开发 缓存客户端请求
* 重连后将缓存数据返回
*/
private final Deque<IPCRequest> runningRequest = new ArrayDeque<>();
private Lock mLock = new ReentrantLock();
private IPCCache mIpcCache ;
{
mIpcCache = IPCCache.getDefault();
}
@Override
public IBinder onBind(Intent intent) {
return new IRemoteService.Stub(){
@Override
public IPCResponse sendRequest(IPCRequest request) {
mLock.lock();
IPCRequest rq = request;
executed(rq);
switch (rq.getType()) {
case LOAD_INSTANCE:
try {
/**
* 找到对应的处理客户端请求的Class和构造方法
* 然后通过反射实例化对象 保存起来
*/
Class<?> clazz = mIpcCache.getClass(rq.getClassName());
Method method = mIpcCache.getMethod(clazz, rq.getMethodName());
if (mIpcCache.getObject(rq.getClassName()) == null) {
Object object = method.invoke(null, ParamsConvert.unSerializationParams(rq.getParameters()));
mIpcCache.putObject(rq.getClassName(),object);
}
return new IPCResponse(null,"初始化处理对象成功",true);
} catch (Exception e) {
e.printStackTrace();
return new IPCResponse(e.getMessage(),"初始化处理对象失败",false);
}finally {
finished(rq);
mLock.unlock();
}
case LOAD_METHOD:
try {
/**
* 找到对应的处理客户端请求的Class和执行请求的方法
*/
Class<?> cl = mIpcCache.getClass(rq.getClassName());
Method me = mIpcCache.getMethod(cl, rq.getMethodName());
Object object = mIpcCache.getObject(rq.getClassName());
Object[] params = ParamsConvert.unSerializationParams(rq.getParameters());
Object result = me.invoke(object, params);
String r = ParamsConvert.mGson.toJson(result);
return new IPCResponse(r,"执行方法成功",true);
} catch (Exception e) {
e.printStackTrace();
return new IPCResponse(e.getMessage(),"执行方法失败",false);
}finally {
finished(rq);
mLock.unlock();
}
default:
}
return new IPCResponse(null,"未知类型请求,请指定type",false);
}
};
}
void executed(IPCRequest request){
runningRequest.offer(request);
}
void finished(IPCRequest request){
runningRequest.remove(request);
}
}
服务端
从RemoteService 里的处理逻辑可以看出,当客户端有请求过来时,根据其提供的信息从IPCCache缓存中找到对应的类和方法来处理请求,那么服务端就要提供这个处理类,为了后面在客户端能够使用JDK的动态代理,这里使用面向接口的方式
public interface IData {
List sendData(String params);
Object getStudent(String name);
}
/**
* Author:mango
* Time:2019/6/19 17:00
* Version:1.0.0
* Desc:注解值和客户端接口中的注解值保持一致
* 需要提供一个公共静态且方法名为getDefault的方法 返回实例
*/
@RequestHandler("IData")
public class DataManager implements IData {
private static final DataManager ourInstance = new DataManager();
public static DataManager getDefault(String params) {
return ourInstance;
}
private DataManager() {}
@Override
public List sendData(String params) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
return list;
}
@Override
public Student getStudent(String name) {
return new Student(name);
}
}
实现类上有一个注解,类似于它的一个TAG,客户端传递这个TAG过来,服务端根据这个TAG找到对应的处理类处理请求;同时当业务需求变化时,可以提供不同的实现类,更加灵活
当然了RemoteService是从缓存中找到它的,所以在某个时间,服务端需要将实现类注册到缓存中,同时不要忘记解除注册
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IPCMango.getDefault().register(DataManager.class);
}
@Override
protected void onDestroy() {
super.onDestroy();
IPCMango.getDefault().unRegister(DataManager.class);
}
客户端
既然服务端准备好了,那客户端要做的第一件事就是绑定服务,别忘了还有解绑
/**
* 远程服务的应用包名
*/
private String mRemotePackageName = "com.mango.ipc";
IPCMango.getDefault().bind(this,mRemotePackageName);
IPCMango.getDefault().unBind(this);
从RemoteService可以看出来,当客户端想要发送请求时,第一件事是获取服务端对应的处理类的实例,但是因为是跨进程,不可能真的将这个实例传递过来,但是客户端又要模拟得到这个实例,去调用这个处理类提供的方法,所以客户端就需要编写一个跟处理类实现的接口相同的接口
/**
* 注解值需要和服务端实现该接口的类注解值保持一致
* 接口名不需要和服务端一致,只需要接口里的方法和服务端一致就行
*/
@RequestHandler("IData")
public interface IData {
List sendData(String params);
Object getStudent(String name);
}
这时候就可以通过动态代理技术生成一个代理对象,客户端就可以像调用普通方法一样去调用服务端的处理类,最后服务端根据注解值在缓存中找到对应的处理类处理请求
public void sendData(View v){
//如果服务端会执行耗时操作,这里需要放在子线程
//发送请求给框架提供的RemoteService
if (iData == null) {
iData = IPCMango.getDefault().loadRequestHandler(IData.class,"伙计");
}
if (iData != null) {
List list = iData.sendData("小米");
Object student = iData.getStudent("阿菜");
}
}
public <T> T loadRequestHandler(String serviceName, Class inter, IPCRequestError listener, Object... params){
if (!checkService(serviceName)) {
if (listener != null) {
listener.sendResult(new IPCResponse("","未绑定远程Service",false));
}
return null;
}
//获取处理客户端请求的对象的Key,以此在服务端找出对应的处理者
String className;
RequestHandler annotation = (RequestHandler) inter.getAnnotation(RequestHandler.class);
if (annotation == null) {
className = inter.getName();
} else {
className = annotation.value();
}
IPCRequest ipcRequest = buildRequest(IPCRequest.LOAD_INSTANCE,className,"getDefault",params);
/**
* 发消息给服务端, 来实例化 处理客户端请求 的对象, 要求实例该对象的方法名为 getDefault
* 如果构造成功 就通过动态代理构造一个实现该接口的对象并返回
* 以接收客户端后续请求
*/
IPCResponse ipcResponse = sendRequest(ipcRequest,serviceName);
if (ipcResponse.isSuccess()) {
return (T) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{inter},
new IPCInvocationHandler(listener,serviceName, className));
} else {
if (listener != null) {
listener.sendResult(ipcResponse);
}
}
return null;
}
到此框架的简要架构就介绍完了,使用该框架,不需要再定义繁杂的aidl文件了,也不用考虑其细节,只需要像普通方法调用那样去使用就可以完成跨进程通信了
附带
当然了,如果你不使用RemoteService,你也可以在服务端自定义一个Service,然后在清单文件中注册
客户端只需要先绑定,再发送请求就可以了
/**
* 远程服务的包名和类名
*/
private String mRemotePackageName = "com.mango.ipc";
private String mRemoteServiceName = "com.mango.ipc.MyRemoteService";
IPCMango.getDefault().bind(this,mRemotePackageName,mRemoteServiceName);
public void sendMyData(View view){
//发送请求给服务端自定义的远程Service
IPCResponse ipcResponse = IPCMango.getDefault().sendRequest(
IPCMango.getDefault().buildRequest(0,null,null),
mRemoteServiceName);
}
项目依赖
详细代码和使用步骤可参考
欢迎star