为什么使用HermesEventBus
原有项目场景:Socket通信模块在子进程RemoteService中,主进程想要发送一个Socket协议,先从业务模块通过EventBus post一个Event到本地LocalService,本地Service通过ServiceConnection与RemoteService绑定,本地LocalService在ServiceConnection的连接上将AIDL接口封装为一个Messenger,通过Messenger向RemoteService发送Message,而Event就是被包裹在Message中的Bundle里。
说到这里感觉不借助图形很难描述:
问题在这里:
- 每个序列化的Object要放入Bundle中,Bundle对象再放入Message中,过程繁琐
- 每个Message都有what属性需要Handler在处理时判断
- 业务模块想要执行子进程的RemoteService的方法先通过EventBus post一个Object,在LocalService必须有相应的处理。
- 子进程中的RemoteService接收到了服务器发来的消息给业务模块,先要通过本地LocalService处理Message,再由EventBus把Message中的Object post出去。
综上所述,多了一个LocalService,显得很碍眼。
使用HermesEventBus
HermesEventBus是一种支持跨进程,跨APP通信的以EventBus为基础的框架,使用起来就像EventBus。
项目地址:https://github.com/elemers/HermesEventBus
** 如果你的应用是单个app,在Application中可以初始化HermesEventBus**:
private void initHermesEventBus() {
HermesEventBus.getDefault().init(getBaseApplication());
}
由于是多进程,以我的项目为例,主进程会跑一遍这个方法,RemoteService启动时子进程也会跑一遍这个方法。
** 如果项目是多个app,选择一个作为主APP注册Service**:
<service android:name="xiaofei.library.hermes.HermesService$HermesService0"/>
然后在其他app的Application的onCreate中执行:
HermesEventBus.getDefault().connectApp(this, packageName);
** 当进程不再需要使用HermesEventBus,通过调用以下方法销毁HermesEventBus**:
HermesEventBus.getDefault().destroy();
发送事件就像EventBus一样简单,在任意进程内执行:
HermesEventBus.getDefault().postSticky(event);
发送完事件,在任意进程内的注册的观察者就会处理事件:
/**
* 通信层处理发送SOCKET请求事件
* @param event
*/
@Subscribe(threadMode = ThreadMode.POSTING , sticky = true)
public void handleRequestEvent(RequestEvent event){
onHandleLocalSocketBizRequest(event);
}
HermesEventBus是怎么做到跨进程通信的
先看初始化方法:
public void init(Context context) {
mContext = context.getApplicationContext();
mMainProcess = isMainProcess(context.getApplicationContext());
if (mMainProcess) {
Hermes.init(context);
Hermes.register(MainService.class);
mMainApis = MainService.getInstance();
} else {
mState = STATE_CONNECTING;
Hermes.setHermesListener(new HermesListener());
Hermes.connect(context, Service.class);
Hermes.register(SubService.class);
}
}
在初始化时,区分了主进程和子进程,如果是子进程会通过Hermes.connect(context, Service.class)绑定主进程的Service。
打开编译好的APK,查看AndroidManifest.xml会发现:
<service
android:name="xiaofei.library.hermeseventbus.HermesEventBus$Service" />
没错,子进程就是绑定了主进程中的Service与主进程通信的。而通信的载体是一个Mail类:
private final IHermesService.Stub mBinder = new IHermesService.Stub() {
@Override
public Reply send(Mail mail) {
try {
Receiver receiver = ReceiverDesignator.getReceiver(mail.getObject());
int pid = mail.getPid();
IHermesServiceCallback callback = mCallbacks.get(pid);
if (callback != null) {
receiver.setHermesServiceCallback(callback);
}
return receiver.action(mail.getTimeStamp(), mail.getMethod(), mail.getParameters());
} catch (HermesException e) {
e.printStackTrace();
return new Reply(e.getErrorCode(), e.getErrorMessage());
}
}
@Override
public void register(IHermesServiceCallback callback, int pid) throws RemoteException {
mCallbacks.put(pid, callback);
}
@Override
public void gc(List<Long> timeStamps) throws RemoteException {
OBJECT_CENTER.deleteObjects(timeStamps);
}
};
public class Mail implements Parcelable {
private long mTimeStamp;
private int mPid;
private ObjectWrapper mObject;
private MethodWrapper mMethod;
private ParameterWrapper[] mParameters;
public static final Parcelable.Creator<Mail> CREATOR
= new Parcelable.Creator<Mail>() {
public Mail createFromParcel(Parcel in) {
Mail mail = new Mail();
mail.readFromParcel(in);
return mail;
}
public Mail[] newArray(int size) {
return new Mail[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeLong(mTimeStamp);
parcel.writeInt(mPid);
parcel.writeParcelable(mObject, flags);
parcel.writeParcelable(mMethod, flags);
parcel.writeParcelableArray(mParameters, flags);
}
public void readFromParcel(Parcel in) {
mTimeStamp = in.readLong();
mPid = in.readInt();
ClassLoader classLoader = Mail.class.getClassLoader();
mObject = in.readParcelable(classLoader);
mMethod = in.readParcelable(classLoader);
Parcelable[] parcelables = in.readParcelableArray(classLoader);
if (parcelables == null) {
mParameters = null;
} else {
int length = parcelables.length;
mParameters = new ParameterWrapper[length];
for (int i = 0; i < length; ++i) {
mParameters[i] = (ParameterWrapper) parcelables[i];
}
}
}
private Mail() {
}
public Mail(long timeStamp, ObjectWrapper object, MethodWrapper method, ParameterWrapper[] parameters) {
mTimeStamp = timeStamp;
mPid = Process.myPid();
mObject = object;
mMethod = method;
mParameters = parameters;
}
public int getPid() {
return mPid;
}
public ParameterWrapper[] getParameters() {
return mParameters;
}
public ObjectWrapper getObject() {
return mObject;
}
public MethodWrapper getMethod() {
return mMethod;
}
public long getTimeStamp() {
return mTimeStamp;
}
}
回顾一下AIDL通信,AIDL接口支持的参数类型除了基本类型、List、Map、AIDL接口就是Parcelable,而Mail这个实现Parcelable的类将Object封装在内部。
所以HermesEventBus本质上也是通过AIDL实现跨进程通信,依赖于主进程的Service载体。使用HermesEventBus省去了自己实现跨进程通信的烦恼。
小结几种跨进程方法
- BroadCastReceiver 由于面向整个系统注册的广播,跨进程消耗较大,性能不能保证。
- ContentProvider 支持跨进程数据共享
- AIDL 客户端调用AIDL接口是同步并且带返回结果的,如果执行时间较长,客户端的调用线程会一直等待。服务端执行AIDL接口是异步的,支持所有基本类型、AIDL接口、Parcelable、List、Map等类型的参数,实现起来繁琐。
- Messenger 本质是AIDL通信,客户端发送Message后不带返回结果,服务端接收到Message是通过一个线程的Handler轮询MessageQueue处理的,因此处理Message是在同一线程。
- HermesEventBus 本质也是AIDL通信,不需要自己实现绑定Service,发送事件也是不带返回结果的,使用简单。
- Binder机制 Android跨进程通信实现的核心,AIDL就是基于Binder机制实现的,其中transact方法是客户端向服务端发送消息,onTransact方法是客户端接收服务端的消息。