优势
- 不需要手动绑定service
- 获取远程服务是同步的
- 自动保持服务进程优先级
- 跨进程事件总线支持
- 支持IPC的Callback
不足
- 默认只支持 同App 内的不同进程之间进行通信,无法支持 跨App进行通信;
- 无法在代码层面指定通信进程,只能在gradle脚本中配置 ,不够灵活;
整体架构图
问题解析
1. 如何做到不需要手动绑定service ?
每个进程初始化时,互换Binder
Client 端 到 中间层 通过 Intent 传递Binder
//让ServiceDispatcher注册到当前进程
public void sendRegisterInfo() {
if (dispatcherProxy == null) {
//后面考虑还是采用"has-a"的方式会更好
BinderWrapper wrapper = new BinderWrapper(this.asBinder());
Intent intent = new Intent(context, DispatcherService.class);
intent.setAction(Constants.DISPATCH_REGISTER_SERVICE_ACTION);
intent.putExtra(Constants.KEY_REMOTE_TRANSFER_WRAPPER, wrapper);
intent.putExtra(Constants.KEY_PID, android.os.Process.myPid());
ServiceUtils.startServiceSafely(context, intent);
}
}
最终保存到 EventDispatcher transferBinders Map 当中。
private Map<Integer, IBinder> transferBinders = new ConcurrentHashMap<>();
中间层到 Client 通过 AIDL 调用
private void registerAndReverseRegister(int pid, IBinder transterBinder) {
Logger.d("DispatcherService-->registerAndReverseRegister,pid=" + pid + ",processName:" + ProcessUtils.getProcessName(pid));
IRemoteTransfer remoteTransfer = IRemoteTransfer.Stub.asInterface(transterBinder);
Dispatcher.getInstance().registerRemoteTransfer(pid, transterBinder);
if (remoteTransfer != null) {
Logger.d("now register to RemoteTransfer");
try {
remoteTransfer.registerDispatcher(Dispatcher.getInstance().asBinder());
} catch (RemoteException ex) {
ex.printStackTrace();
}
} else {
Logger.d("IdspatcherRegister IBinder is null");
}
}
client 保存了dispatcherProxy 即为中间层的Binder。
@Override
public synchronized void registerDispatcher(IBinder dispatcherBinder) throws RemoteException {
Logger.d("RemoteTransfer-->registerDispatcher");
//一般从发出注册信息到这里回调就6ms左右,所以绝大部分时候走的都是这个逻辑。
dispatcherBinder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Logger.d("RemoteTransfer-->dispatcherBinder binderDied");
resetDispatcherProxy();
}
}, 0);
dispatcherProxy = IDispatcher.Stub.asInterface(dispatcherBinder);
notifyAll();
}
通过 这样一番交互,client 端 和中间层 就互相持有了对方的binder 实例 ,能够互相访问对方的远程方法。
每个提供服务的进程 都经过 上述调用过程之后, 中间层保存了 一份 每个进程的binder 引用。
- registerRemoteService
注册服务时,提供服务的进程 通过IPC 将自己注册到 中间层 .
private Map<String, BinderBean> remoteBinderCache = new ConcurrentHashMap<>();
- getRemoteService
client 获取服务时,则通过IPC 向 中间层 查询对应ServiceName 的远程Binder 引用。
@Override
public synchronized BinderBean getTargetBinder(String serviceCanonicalName) throws RemoteException {
return serviceDispatcher.getTargetBinderLocked(serviceCanonicalName);
}
2. 如何做到同步获取服务
private void initDispatchProxyLocked() {
if (null == dispatcherProxy) {
IBinder dispatcherBinder = getIBinderFromProvider();
if (null != dispatcherBinder) {
Logger.d("the binder from provider is not null");
dispatcherProxy = IDispatcher.Stub.asInterface(dispatcherBinder);
registerCurrentTransfer();
}
}
if (null == dispatcherProxy) {
sendRegisterInfo();
try {
wait(MAX_WAIT_TIME);
} catch (InterruptedException ex) {
Logger.e("Attention! Wait out of time!");
ex.printStackTrace();
}
}
}
- 正常情况 dispatcherProxy!=null
初始化时如果 双方互换 Binder 成功 那么 dispatcherProxy 引用已经存在 那么则不需要经历阻塞获取过程。 - 异常情况 dispatcherProxy 为null
服务进程被杀或者 IPC互换binder失败 等情况,
- 通过query DispatcherProvider 获取中间层的Binder实例,然后通过 AIDL IPC 调用 将Client binder 传递给 中间层保存;
- 如果仍然获取失败,尝试Intent 传递 Binder 的方式 并调用wait阻塞等待600ms ,等待中间层通过 AIDL IPC 方式 调用方法传递 dispatcherProxy后 notifyAll
3. 如何提高服务进程的优先级
为应用每个进程 配置一个 StubService,这个过程 通过gradle 脚本 编译时扫描Manifest 进程信息 并插入StubService 注册信息,最多有16个(一般情况下 应用进程不太可能有这么多) 当获取getRemoteService 去绑定 远程进程的StubService时,会尝试绑定远程进程的StuService 关键代码如下:
context.bindService(intent, connection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
其中 BIND_IMPORTANT 标志的设置 告诉系统 应提高StubService 所在进程的优先级,当client 是前台进程时是系统会将服务进程的优先级提升到前台进程优先级。
解绑时机:
- 手动调用unbind 方法
适用于 使用不在 Activty Fragment 中 获取远程服务,只有Context ; - 自动解除绑定
跟随Activty Fragment 生命周期的onDestroy,这个部分参考了 Glide 生命周期监听的实现,为Activity或者 Fragment 追加了一个 无界面的Fragment ,能够监听宿主的生命周期。
4. 如何实现事件总线
前面说道 中间层能够拿到 所有 client 的Binder 引用,所有client 也都持有了 中间层的Binder 引用,
会通过AIDL IPC 调用 中间层方法, 中间层 遍历所有的 Client Binder 列表 依次IPC 回调notify 方法。
@Override
public void publishLocked(Event event) throws RemoteException {
Logger.d("EventDispatcher-->publishLocked,event.name:" + event.getName());
RemoteException ex = null;
for (Map.Entry<Integer, IBinder> entry : transferBinders.entrySet()) {
IRemoteTransfer transfer = IRemoteTransfer.Stub.asInterface(entry.getValue());
//对于这种情况,如果有一个出现RemoteException,也不能就停下吧?
if (null != transfer) {
try {
transfer.notify(event);
} catch (RemoteException e) {
e.printStackTrace();
ex = e;
}
}
}
if (null != ex) {
throw ex;
}
}
将 event name 和 回调 映射存储,收到 中间层 IPC notify 方法时 依次遍历。
public void notifyLocked(Event event) {
Logger.d("EventTransfer-->notifyLocked,pid:" + android.os.Process.myPid() + ",event.name:" + event.getName());
List<WeakReference<EventListener>> listeners = eventListeners.get(event.getName());
if (listeners == null) {
return;
}
for (int i = listeners.size() - 1; i >= 0; --i) {
WeakReference<EventListener> listenerRef = listeners.get(i);
if (listenerRef.get() == null) {
listeners.remove(i);
} else {
listenerRef.get().onNotify(event);
}
}
}
如何做到IPC callback ?
定义了名为 BaseCallback AIDL 接口 将回调方法通过主线程 Handler切换到主线程;
为什么只能支持应用内进程间通信?
注意到 gradle 脚本 1.注入的DispatcherService DisptacherProvider 对应的 组件exported=false,表示不对外部应用暴露; 2. 代码中绑定的DispatcherService 只能绑定应用内进程的组件
如何代码中指定通信独立进程 并实现跨App进程通信
改造方法: 1.指定应用组件DispatcherService DisptacherProvider 属性 exported=true; 2.启动 DispatcherService组件时 加入setPackage 方法
|