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的模板代码编写。
markdown1、接口定义风格的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 阶段被替换。具体过程如下:
-
编译时替换:
- stub 模块中的
RouterLoader
类只是一个占位符 - 在编译过程中,DRouter 的 Transform 会扫描项目中的路由注解
- 根据扫描结果生成实际的
RouterLoader
实现类 - 替换掉 stub 模块中的原始类
- stub 模块中的
-
替换证据:
- 从你提供的反编译代码可以看出,最终的
RouterLoader
类包含了大量具体的路由信息 - 这些信息是在编译时根据项目中的路由注解动态生成的
- stub 模块中的
throw new RuntimeException()
被替换成了实际的路由加载逻辑
- 从你提供的反编译代码可以看出,最终的
-
替换机制:
- DRouter 使用 Gradle Transform API 实现类替换
- 在 .class 文件生成后,Transform 阶段进行类替换
- 替换后的类会被写入 build/intermediates 目录
-
设计目的:
- 这种设计可以避免在开发阶段引入完整的 DRouter 实现
- 减少编译依赖,提高编译速度
- 实现按需加载,只生成实际使用的路由信息
所以,在编写代码时虽然引用了 stub 模块中的类,但在最终编译产物中,这些类已经被替换为实际生成的实现类。这是 DRouter 实现编译时路由表生成的重要机制。
共享内存使用案例
在当前项目中,共享内存的案例主要集中在 RemoteMemoryActivity
和 ActivityRemoteTest
中。以下是所有共享内存案例的详细分析,包括谁读谁取:
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)); }
-
-
拦截调用:
-
在
Handler
的invoke
方法中,将调用者的接口信息转换为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
调用RemoteProvider
的call
方法,传递方法名、参数和Bundle
。 -
代码:
javajava Apply Bundle bundle = getContentResolver().call( Uri.parse("content://com.didi.drouter.remote"), // RemoteProvider 的 authority "methodName", // 方法名 "arg", // 参数 null // extras );
-
-
获取 Binder:
-
客户端从返回的
Bundle
中获取服务端的Binder
对象。 -
代码:
inijava Apply IBinder binder = bundle.getParcelable(FIELD_REMOTE_BINDER); IHostService hostService = IHostService.Stub.asInterface(binder);
-
2. Binder 代理 call
方法跨进程传递 Cmd
-
RPC 调用:
-
客户端通过
Binder
对象调用远程方法,传递StreamCmd
数据。 -
代码:
inijava 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 服务的缓存和恢复是通过 sHostServiceMap
和 sProcessMap
实现的。以下是详细的分析:
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. 总结
- 缓存 :通过
sHostServiceMap
和sProcessMap
缓存Binder
对象和进程名称。 - 恢复 :当服务端进程异常终止时,通过
linkToDeath
机制移除缓存的Binder
对象,并在下次调用时重新获取和缓存。
1. 提高性能
-
减少跨进程调用:
- 每次通过
ContentProvider
获取Binder
对象都需要跨进程调用,性能开销较大。缓存Binder
对象后,后续调用可以直接使用缓存,避免重复的跨进程通信。
- 每次通过
-
降低延迟:
- 缓存机制减少了获取
Binder
对象的时间,从而降低了 RPC 调用的延迟。
- 缓存机制减少了获取
2. 优化资源使用
-
避免重复创建:
- 如果没有缓存,每次调用都需要重新创建
Binder
对象,增加了系统资源的消耗。缓存机制避免了重复创建,优化了资源使用。
- 如果没有缓存,每次调用都需要重新创建
-
减少系统负载:
- 跨进程调用会增加系统负载,缓存机制减少了不必要的跨进程调用,从而降低了系统负载。
3. 提高稳定性
-
处理服务端异常:
- 当服务端进程异常终止时,
linkToDeath
机制会触发binderDied
方法,从缓存中移除失效的Binder
对象。这确保了客户端不会继续使用失效的Binder
对象,提高了系统的稳定性。
- 当服务端进程异常终止时,
-
自动恢复:
- 当缓存中的
Binder
对象失效后,客户端在下次调用时会重新获取并缓存新的Binder
对象,实现了自动恢复。
- 当缓存中的
4. 简化调用逻辑
-
统一管理:
- 缓存机制将
Binder
对象的管理集中到RemoteProvider
中,简化了客户端的调用逻辑。客户端无需关心Binder
对象的获取和缓存细节,只需通过getHostService
方法获取即可。
- 缓存机制将
-
透明化调用:
- 缓存机制对客户端透明,客户端无需感知缓存的存在,只需像调用本地方法一样调用远程方法。
5. 支持多进程场景
-
多进程共享:
sHostServiceMap
和sProcessMap
是静态变量,可以在同一进程的多个线程中共享。这确保了在多线程或多进程场景下,Binder
对象的获取和缓存是线程安全的。
-
进程隔离:
- 缓存机制通过
authority
区分不同的服务端进程,支持多进程场景下的服务调用。
- 缓存机制通过
总结
缓存机制的主要意义在于提高性能、优化资源使用、提高稳定性、简化调用逻辑以及支持多进程场景。通过缓存 Binder
对象,系统能够更高效、更稳定地完成跨进程调用。