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 对象,系统能够更高效、更稳定地完成跨进程调用。

相关推荐
tangweiguo030519872 小时前
Android Kotlin ViewModel 错误处理:最佳 Toast 提示方案详解
android·kotlin
火柴就是我3 小时前
android 基于 PhotoEditor 这个库 开发类似于dlabel的功能_2
android
每次的天空3 小时前
Android学习总结之Java篇(一)
android·java·学习
8931519604 小时前
Android开发Glide做毛玻璃效果
android·glide·android开发·android教程·glide做毛玻璃效果
人生游戏牛马NPC1号6 小时前
学习Android(五)玩安卓项目实战
android·kotlin
前行的小黑炭7 小时前
Android Lifecycle代码分析:为什么使用;注解的方式为什么过期?源码分析;状态与事件
android
和煦的春风7 小时前
案例分析 | SurfaceFlinger 大片Runnable引起的卡顿
android·linux
浩宇软件开发8 小时前
Android开发,实现一个简约又好看的登录页
android·java·android studio·android开发
未扬帆的小船8 小时前
在gpt的帮助下安装chales的证书,用于https在root情况下抓包
android·charles