面试系列——爱奇艺Andromeda 跨进程通信组件分析
Posted BridgeGeorge
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试系列——爱奇艺Andromeda 跨进程通信组件分析相关的知识,希望对你有一定的参考价值。
优势
- 不需要手动绑定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 引用,
- publish 过程
会通过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;
- subscribe 过程
将 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 方法
以上是关于面试系列——爱奇艺Andromeda 跨进程通信组件分析的主要内容,如果未能解决你的问题,请参考以下文章