IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 面试系列——爱奇艺Andromeda 跨进程通信组件分析 -> 正文阅读

[移动开发]面试系列——爱奇艺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 方法

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2022-03-10 22:41:17  更:2022-03-10 22:45:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 18:50:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码