DRouter代码走读

DRouter的文档

ruby 复制代码
https://juejin.cn/post/6975818153381068831
https://github.com/didi/DRouter/wiki
https://github.com/didi/DRouter/wiki/1.-Router
https://github.com/didi/DRouter/wiki/2.-Service
https://github.com/didi/DRouter/wiki/3.-Remote
https://github.com/didi/DRouter/wiki/4.-Page
https://github.com/didi/DRouter/wiki/5.-Plugin

0、所有模块

php 复制代码
Drouter lib模块  include ':drouter-api', ':drouter-api-page', ':drouter-api-process', ':drouter-api-stub'
Drouter 插件模块 include ':drouter-plugin', ':drouter-plugin-proxy'
应用lib模块:include ':demo-base', ':demo-process'
应用模块:include ':demo'

1、应用模块

2、应用lib模块

3、Drouter lib模块

  • :drouter-api 该模块是核心api,demo中有几乎完整的api调用与回调处理。

    ruby 复制代码
    初始化,对路由表信息(插件收集cache文件生成XXXLoader类)进行分类加载缓存(RouterStore)。
    本地拦截与路由:InterceptorLoader有优先级的拦截之后,通过RouterLoader,,activity启动;处理结果回执:Activity、Fragment、View,handler。
    远程服务:通过ServiceLoader,ServiceAgent调用RemoteBridge(:drouter-api-process)通过动态代理,将远程服务信息转换成StreamCmd命令信息。
  • :drouter-api-process 该模块是远程调用相关代码,是:drouter-api远程调用的实现代码。简化了远程调用的实现,业务只需要关注服务接口定义信息,以及服务接口功能实现,不必关心跨进程rpc的模板代码编写。

    markdown 复制代码
    1、接口定义风格的rpc封装:
        通过动态代理将远程服务信息统一转换成StreamCmd命令信息。
        通过ContentProvider启动动态注册的或者恢复绑定远程进程(或者app启动就启动了),获取并缓存服务绑定代理。
        通过服务绑定IHostService传输StreamCmd命令信息进行处理。
        CmdDispatcher中根据传递的服务信息以及ServiceLoader找到IRouterProxy进行反射调用具体的服务实现。
    2、跨进程内存共享服务(建立在接口定义风格rpc上的一个特殊服务):
        ISharedMemory:作为内存服务路由接口定义,同时也包含服务调用api方法。
        MemoryServer:服务端,管理客户端连接,分配共享内存,写如共享内存。
        MemoryClient:客户端,连接服务端,读取共享内存。
       (demo有进程间共享案例,文章最后有解读)
  • :drouter-api-stub 该模块中类没有实现业务,属于占位代码,保证javac阶段编译成功,在Transform阶段被自定义编译插件生成的字节码替换掉。三种业务loader信息表,以及一些关联的生成代码代理创建对象类(DRouter\demo\build\intermediates\transforms\DRouter\debug\43\com\didi\drouter\loader\)目录下边。

  • :drouter-api-page 该模块是Fragment展示隐藏的,动态注册,解耦合(路由方式管理),北向结合demo的fragment包看,南向路由获取Fragment。

4、Drouter插件模块

drouter-plugin-proxy

Transform插件,下载drouter-plugin,使用URLClassLoader加载插件,并执行插件的transform方法,完成之后恢复原来的ClassLoader,避免影响其他插件,加载失败回退恢复编译环境。

drouter-plugin

也是一个Transform插件,支持增量编译,将增量编译文件写入cache文件进行维护,也支持全量编译。AbsRouterCollect RouterCollect:提供cahche中Router条目收集,生成RouterLoader类 ServiceCollect:提供cache中Service条目收集,生成ServiceLoader类 InterceptorCollect:提供cache中Interceptor条目收集,生成InterceptorLoader类。

插件编译相关

lua 复制代码
Transform是在class字节码编译完成以后,dex文件编译之前
java与kotlin混合模块,代码会被分开编译
Java字节码编译输出位置:DRouter\demo\build\intermediates\javac\debug\classes
Kotlin字节码编译输出位置:DRouter\demo\build\tmp\kotlin-classes\debug
java字节码与kotlin字节码有很大差异
cache文件记录与xxxLoader记录对应关系 复制代码
@Interceptor 4次    对应InterceptorLoader的6行中的4行,还有两行是由于name别名产生记录
@Router     20次    对应RouterLoader的20行
@Service    11次    对应ServiceLoader的14行中的11行,多出3条:
                1、ServiceAny implements IServiceTest, IServiceTest2 。 ServiceAny 有@Service注解,同时双接口继承,而外拆分出来两个记录。
                2、ICallService没有实现类,没有@Service注解。类似匿名内部类。
                3、Object这行是插件中凭空加的,为了处理方法调用中动态类型参数。
----------------
            35次==cache 35行class)印证ClassClassify分类记录
@Assign     14次,没有参与cache

数据源是app模块的class或者其它模块的jar包(runtime_library_classes_jar)
path                 app模块
demo                 30行
jar:file:path        lib模块(runtime_library_classes_jar)
demo-base            1行
drouter-api-process  4行

java代码期间可以引用编译 在 DRouter 的编译过程中,stub 模块中的类会在 Transform 阶段被替换。具体过程如下:

  1. 编译时替换

    • stub 模块中的 RouterLoader 类只是一个占位符
    • 在编译过程中,DRouter 的 Transform 会扫描项目中的路由注解
    • 根据扫描结果生成实际的 RouterLoader 实现类
    • 替换掉 stub 模块中的原始类
  2. 替换证据

    • 从你提供的反编译代码可以看出,最终的 RouterLoader 类包含了大量具体的路由信息
    • 这些信息是在编译时根据项目中的路由注解动态生成的
    • stub 模块中的 throw new RuntimeException() 被替换成了实际的路由加载逻辑
  3. 替换机制

    • DRouter 使用 Gradle Transform API 实现类替换
    • 在 .class 文件生成后,Transform 阶段进行类替换
    • 替换后的类会被写入 build/intermediates 目录
  4. 设计目的

    • 这种设计可以避免在开发阶段引入完整的 DRouter 实现
    • 减少编译依赖,提高编译速度
    • 实现按需加载,只生成实际使用的路由信息

所以,在编写代码时虽然引用了 stub 模块中的类,但在最终编译产物中,这些类已经被替换为实际生成的实现类。这是 DRouter 实现编译时路由表生成的重要机制。

共享内存使用案例

在当前项目中,共享内存的案例主要集中在 RemoteMemoryActivityActivityRemoteTest 中。以下是所有共享内存案例的详细分析,包括谁读谁取:


1. "host1" 共享内存

  • 生产者ActivityRemoteTest(主进程)

    • 负责创建共享内存并写入数据。
    • 通过 MemoryServer.create("host1", ...) 创建共享内存。
    • 通过 notifyClient 方法通知消费者进程数据已更新。
  • 消费者RemoteMemoryActivity(远程进程)

    • 负责从共享内存中读取数据。
    • 通过 MemoryClient("com.didi.drouter.remote.demo.host", "host1", ...) 连接到共享内存。
    • onNotify 方法中读取数据并处理。

2. "host2" 共享内存

  • 生产者ActivityRemoteTest(主进程)

    • 负责创建共享内存并写入数据。
    • 通过 MemoryServer.create("host2", ...) 创建共享内存。
    • 通过 notifyClient 方法通知消费者进程数据已更新。
  • 消费者RemoteMemoryActivity(远程进程)

    • 负责从共享内存中读取数据。
    • 通过 MemoryClient("com.didi.drouter.remote.demo.host", "host2", ...) 连接到共享内存。
    • onNotify 方法中读取数据并处理。

3. 总结

  • "host1"

    • 生产者:ActivityRemoteTest(主进程)
    • 消费者:RemoteMemoryActivity(远程进程)
  • "host2"

    • 生产者:ActivityRemoteTest(主进程)
    • 消费者:RemoteMemoryActivity(远程进程)

接口风格的RPC

RemoteProvider 启动就提供了远程服务端call函数,以及IHostService.Stub。本地调用获取服务接口描述服务时,在RemoteBridge动态代理拦截调用者接口信息转换成cmd后,本地进程通过ContentResolver().call方法跨进程获取binder代理,然后本地进程通过binder代理call方法跨进程传递cmd数据。然后IHostService.Stub是服务端接收cmd数据处理的地方,接收到cmd数据,然后再服务端进程根据cmd数据以及路由loader找到cmd信息中指定的服务,通过反射进行调用,这样就完成了接口风格定义的服务RPC调用。以下是整个 RPC 调用过程的详细梳理:

设计思想的核心:

1、接口既是服务路由定义,也是服务api函数定义。

2、接口拦截统一处理,跨进程传送信息后,@1找路由服务实现,@1进行反射调用。


0. RemoteBridge 动态代理拦截调用

  • 动态代理

    • RemoteBridge 通过 Proxy.newProxyInstance 创建动态代理对象,拦截调用者的接口方法。

    • 代码

      less 复制代码
      \path\to\RemoteBridge.java
      @Override @SuppressWarnings("unchecked")
      public <T> T getService(@NonNull Strategy strategy, Lifecycle lifecycle,
                              Class<T> serviceClass, String alias, Object feature, @Nullable Object... constructor) {
          this.strategy = strategy;
          this.lifecycle = lifecycle;
          return (T) Proxy.newProxyInstance(getClass().getClassLoader(),
                  new Class[] {serviceClass}, new Handler(serviceClass, alias, feature, constructor));
      }
  • 拦截调用

    • Handlerinvoke 方法中,将调用者的接口信息转换为 StreamCmd 对象。

    • 代码

      ini 复制代码
      \path\to\RemoteBridge.java
      @Override
      public Object invoke(Object proxy, Method method, @Nullable Object[] args) {
          final StreamCmd command = new StreamCmd();
          command.bridge = RemoteBridge.this;
          command.serviceClass = serviceClass;
          command.alias = alias;
          command.feature = feature;
          command.constructorArgs = constructorArgs;
          command.methodName = method.getName();
          command.methodArgs = args;
          StreamCallback.preprocess(args, strategy.authority);
          StreamResult result = execute(command);
          if (result != null && StreamResult.SUCCESS.equals(result.state)) {
              return result.result;
          } else {
              Class<?> returnType = method.getReturnType();   // void、int...
              if (returnType.isPrimitive()) {
                  if (returnType == boolean.class) {
                      return false;
                  } else if (returnType == char.class) {
                      return '0';
                  } else {
                      return 0;
                  }
              } else {
                  return null;
              }
          }
      }

1. call 方法跨进程获取 Binder 代理

  • 客户端调用

    • 客户端通过 ContentResolver().call 调用 RemoteProvidercall 方法,传递方法名、参数和 Bundle

    • 代码

      java 复制代码
      java
      Apply
      Bundle bundle = getContentResolver().call(
          Uri.parse("content://com.didi.drouter.remote"), // RemoteProvider 的 authority
          "methodName", // 方法名
          "arg", // 参数
          null // extras
      );
  • 获取 Binder

    • 客户端从返回的 Bundle 中获取服务端的 Binder 对象。

    • 代码

      ini 复制代码
      java
      Apply
      IBinder binder = bundle.getParcelable(FIELD_REMOTE_BINDER);
      IHostService hostService = IHostService.Stub.asInterface(binder);

2. Binder 代理 call 方法跨进程传递 Cmd

  • RPC 调用

    • 客户端通过 Binder 对象调用远程方法,传递 StreamCmd 数据。

    • 代码

      ini 复制代码
      java
      Apply
      StreamResult result = hostService.call(command);

3. 服务端处理

  • 接收 Cmd 数据

    • 服务端的 IHostService.Stub 实现类接收客户端传递的 StreamCmd 数据。

    • 代码

      java 复制代码
      \path\to\RemoteProvider.java
      Apply
      private static final IHostService.Stub stub = new IHostService.Stub() {
          @Override
          public StreamResult call(StreamCmd command) {
              try {
                  ProcChecker.checkApplication();
                  return new CmdDispatcher().call(command);
              } catch (RuntimeException e) {
                  RouterLogger.getCoreLogger().e("[Server] exception: %s", e);
                  throw e;  // will not crash
              }
          }
      };
  • 路由加载服务

    • 服务端根据 StreamCmd 数据中的服务类名、方法名等信息,通过 ServiceLoader 加载对应的服务实例。

    • 代码

      ini 复制代码
      \path\to\CmdDispatcher.java
      Apply
      ServiceLoader<?> loader = DRouter.build(command.serviceClass)
              .setAlias(command.alias)
              .setFeature(command.feature);
      Object instance = loader.getService(command.constructorArgs);
  • 反射调用服务

    • 服务端通过反射调用服务实例的指定方法,并返回执行结果。

    • 代码

      ini 复制代码
      \path\to\CmdDispatcher.java
      Apply
      result.result = ReflectUtil.invokeMethod(instance, command.methodName, command.methodArgs);

4. 总结

  • call 方法跨进程获取 Binder 代理 :客户端通过 ContentResolver().call 获取服务端的 Binder 对象。

  • Binder 代理 call 方法跨进程传递 Cmd :客户端通过 Binder 对象调用远程方法,传递 StreamCmd 数据。

  • 服务端处理 :服务端接收 StreamCmd 数据,通过路由加载服务实例,并通过反射调用指定方法。

RPC服务的缓存

RemoteProvider 中,Binder 服务的缓存和恢复是通过 sHostServiceMapsProcessMap 实现的。以下是详细的分析:


1. Binder 服务的缓存

  • 缓存机制

    • sHostServiceMap 用于缓存 IHostService 对象,键为 authority
    • sProcessMap 用于缓存进程名称,键为 authority
  • 缓存时机

    • 当客户端通过 getHostService 方法获取到服务端的 Binder 对象后,会将其缓存到 sHostServiceMap 中。

    • 代码

      csharp 复制代码
      \path\to\RemoteProvider.java
      Apply
      if (parcel != null) {
          hostService = IHostService.Stub.asInterface(parcel.getBinder());
          hostService.asBinder().linkToDeath(new IBinder.DeathRecipient() {
              @Override
              public void binderDied() {
                  // rebinding is not required
                  // this may slow, so remove again when execute DeadObjectException
                  sHostServiceMap.remove(authority);
                  RouterLogger.getCoreLogger().e(
                          "[Client] linkToDeath: remote "%s" is died", authority);
              }
          }, 0);
          sHostServiceMap.put(authority, hostService);
          RouterLogger.getCoreLogger().d("[Client] get server binder success from provider, authority "%s"", authority);
          return hostService;
      }

2. Binder 服务的恢复

  • 恢复机制

    • 当服务端进程异常终止时,linkToDeath 会触发 binderDied 方法,从 sHostServiceMap 中移除对应的 Binder 对象。

    • 客户端在下次调用时,会重新通过 getHostService 方法获取服务端的 Binder 对象,并重新缓存。

    • 代码

      csharp 复制代码
      \path\to\RemoteProvider.java
      Apply
      hostService.asBinder().linkToDeath(new IBinder.DeathRecipient() {
          @Override
          public void binderDied() {
              // rebinding is not required
              // this may slow, so remove again when execute DeadObjectException
              sHostServiceMap.remove(authority);
              RouterLogger.getCoreLogger().e(
                      "[Client] linkToDeath: remote "%s" is died", authority);
          }
      }, 0);

3. 总结

  • 缓存 :通过 sHostServiceMapsProcessMap 缓存 Binder 对象和进程名称。
  • 恢复 :当服务端进程异常终止时,通过 linkToDeath 机制移除缓存的 Binder 对象,并在下次调用时重新获取和缓存。

1. 提高性能

  • 减少跨进程调用

    • 每次通过 ContentProvider 获取 Binder 对象都需要跨进程调用,性能开销较大。缓存 Binder 对象后,后续调用可以直接使用缓存,避免重复的跨进程通信。
  • 降低延迟

    • 缓存机制减少了获取 Binder 对象的时间,从而降低了 RPC 调用的延迟。

2. 优化资源使用

  • 避免重复创建

    • 如果没有缓存,每次调用都需要重新创建 Binder 对象,增加了系统资源的消耗。缓存机制避免了重复创建,优化了资源使用。
  • 减少系统负载

    • 跨进程调用会增加系统负载,缓存机制减少了不必要的跨进程调用,从而降低了系统负载。

3. 提高稳定性

  • 处理服务端异常

    • 当服务端进程异常终止时,linkToDeath 机制会触发 binderDied 方法,从缓存中移除失效的 Binder 对象。这确保了客户端不会继续使用失效的 Binder 对象,提高了系统的稳定性。
  • 自动恢复

    • 当缓存中的 Binder 对象失效后,客户端在下次调用时会重新获取并缓存新的 Binder 对象,实现了自动恢复。

4. 简化调用逻辑

  • 统一管理

    • 缓存机制将 Binder 对象的管理集中到 RemoteProvider 中,简化了客户端的调用逻辑。客户端无需关心 Binder 对象的获取和缓存细节,只需通过 getHostService 方法获取即可。
  • 透明化调用

    • 缓存机制对客户端透明,客户端无需感知缓存的存在,只需像调用本地方法一样调用远程方法。

5. 支持多进程场景

  • 多进程共享

    • sHostServiceMapsProcessMap 是静态变量,可以在同一进程的多个线程中共享。这确保了在多线程或多进程场景下,Binder 对象的获取和缓存是线程安全的。
  • 进程隔离

    • 缓存机制通过 authority 区分不同的服务端进程,支持多进程场景下的服务调用。

总结

缓存机制的主要意义在于提高性能、优化资源使用、提高稳定性、简化调用逻辑以及支持多进程场景。通过缓存 Binder 对象,系统能够更高效、更稳定地完成跨进程调用。

相关推荐
还鮟1 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡3 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi003 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil4 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你5 小时前
Android View的绘制原理详解
android
移动开发者1号8 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号8 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best12 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk13 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭17 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin