面试系列——爱奇艺Andromeda 跨进程通信组件分析

Posted BridgeGeorge

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试系列——爱奇艺Andromeda 跨进程通信组件分析相关的知识,希望对你有一定的参考价值。

优势

  1. 不需要手动绑定service
  2. 获取远程服务是同步的
  3. 自动保持服务进程优先级
  4. 跨进程事件总线支持
  5. 支持IPC的Callback

不足

  1. 默认只支持 同App 内的不同进程之间进行通信,无法支持 跨App进行通信;
  2. 无法在代码层面指定通信进程,只能在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();
            
        
    
  1. 正常情况 dispatcherProxy!=null
    初始化时如果 双方互换 Binder 成功 那么 dispatcherProxy 引用已经存在 那么则不需要经历阻塞获取过程。
  2. 异常情况 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 是前台进程时是系统会将服务进程的优先级提升到前台进程优先级。

解绑时机:

  1. 手动调用unbind 方法
    适用于 使用不在 Activty Fragment 中 获取远程服务,只有Context ;
  2. 自动解除绑定
    跟随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 跨进程通信组件分析的主要内容,如果未能解决你的问题,请参考以下文章

面试系列——系统资源调度平台设计和实现总结

基于JSON RPC的一种Android跨进程调用解决方案了解一下?

面试系列——系统资源调度平台设计和实现总结

刚刚,爱奇艺发布重磅开源项目!

爱奇艺网络协程编写高并发应用实践

Android面试Android跨进程通信之Binder