这篇文章是我在 2022【GIAC 全球互联网架构大会】共享时所讲内容的文字版别,修正删减了演讲时的冗余言语,现开放给咱们阅读,期望能给买不到票参与共享的 开源实验室 读者带来协助。

咱们好,今日跟咱们共享的是一个开源路由TheRouter的规划。
代码地址: github.com/HuolalaTech…

先来看一下目录 咱们从三点,来讲述今日的主题:

  • 别离是模块化的开端,怎样经过路由去完结一个模块化。
  • 然后再依据方针,去规划一个动态化的路由处理咱们的问题,以及在咱们的项目中,是怎样实践的。
  • 最终,今年的大环境咱们应该都知道,考虑一下怎样在资源有限的状况下,推动工程的重构。

动态路由TheRouter的设计与实践

这里有三张我手机上APP的截图,别离是:货拉拉、今日头条、美团

他们根本上能够代表了现在市面上大部分APP的一个形状,在这四五年里,互联网公司大幅增加,而APP的事务功用也不断增多。

动态路由TheRouter的设计与实践

从技能视点再看一下:

这是我列出来的一个APP的通用架构,这张图根本能够掩盖现现在百分之八九十的 APP 架构。

  1. 首要最上层是各个事务层 比方说是像货拉拉的搬迁、拉货、运大件这种。
  2. 接下来是各个事务模块 比方常见的像用户账户体系、然后或许有一些直播、音视频、支付这样的场景模块。
  3. 再往下便是一些功用性的组件:他们或许跟详细的事务功用相关,比方推送、IM、广告控件、这样的一系列功用组件。
  4. 最底层便是根底设施了: 就像数据上报 反常统计等等一系列的必要根底才干。

然后在侧面还有一些贯穿整个APP的才干 像CICD 国际化 端智能 热修正等等。

从这张图咱们也能看出现现在的APP是越来越复杂 功用也越来越多

动态路由TheRouter的设计与实践

关于功用越来越多,越来越复杂的APP架构,咱们最直接能想到的便是经过模块化,将不同的功用、不同的事务做独立拆分,分而治之,下降整个系统的复杂度。毕竟越简略,逻辑越少的代码块,BUG就越少。

所以大型 APP 的开发,根本都会选用模块化开发,一同关于模块间解耦要求更高。
而说到模块化,咱们一定需求一个路由去承载不同模块之间的通信。路由是现现在 Android 开发中必不可少的功用,尤其是企业级APP,能够用于将原生页面跳转的强依靠解耦,一同减少跨团队开发的互相依靠问题。

比方UI层级的跳转、功用模块的联动调用,这是做模块化绕不开的两点。

完结这两点最常用的办法也便是:别离将咱们当时的一个UI页面与一个uri相关,用Uri代替咱们的页面,

这个姿态在跳转的时分就不需求强依靠UI页面去做匹配,而只需求经过一段字符串去匹配就行

那另一种便是经过接口下沉,将模块依靠改为协议依靠,这样 咱们在不同的模块之间调度的时分 只需求依靠一个最根底的协议或许说是接口 去完结就能够了

动态路由TheRouter的设计与实践

做完模块化今后,一个APP的复杂度已经被下降许多了。
可是有一个最大的问题,咱们经过模块化是没办法处理的。

也便是APP依靠用户去主动的更新晋级,用户不更新,那便是永远在用旧版别, 当年,也是为了处理这个问题,催生出了许多黑科技,比方Android的插件化、热修正这种黑科技,最终这些科技最终也被验证是点歪了的技能树。

今日我跟咱们讲讲另一种处理办法:

动态路由TheRouter的设计与实践

回到咱们今日的主题:动态化路由

前些天咱们开源了一套,在安卓上面的动态化路由叫 TheRouter 他是一整套咱们完结APP动态化的规划计划。包括模块化、包括远端路由下发、包括前面刚才我列出来的几个依靠用户晋级而形成的一些问题,咱们都是经过他来处理的。

之所以叫TheRouter 由于 The 代表了一种仅有性,咱们在规划的时分就参阅了悉数现有的开源计划,吸取了许多优秀完结,一同补齐了各个计划的缺陷。咱们以为做移动端的模块化,只需求看这一个就够了。

动态路由TheRouter的设计与实践

首要咱们来看一下职业内路由的规划计划,不管是页面跳转,仍是跨模块调用,根本上都是

  1. 开发阶段,对要运用路由的落地页或被调用办法添加注解标识。
  2. 在编译期解析注解,生成一系列中间代码,等候调用。
  3. 运用发动后调用中间代码完结路由的预备动作。大部分路由会额定经过 Gradle Transform,在编译期做一次聚合,以提升运行时预备路由表的功率。
  4. 发起路由跳转时,本质上便是一次路由表遍历,经过uri获取到对应的落地页或办法目标,进行调用。

跨模块调用也是相似,在开发时做标记,编译时生成中间代码,运行时经过中间代码调用跨模块办法。

TheRouter 的全体完结逻辑也是依照这个思路去做的,不过咱们关于各个细节的处理,有更好的处理办法。

这是另一个视点,跟职业路由的一些对比数据。

动态路由TheRouter的设计与实践

咱们能够首要重视这几个点:

  • 第一个点: TheRouter是完全无运行时扫描,没有任何反射代码的结构。
    当然由于引证了Gsonjson解析,他里面应该是用了反射的,但这不在咱们评论的范围内,假如你乐意咱们答应自定义json解析结构,你能够换成其他的解析。

  • 第二点是TheRouter对增量编译支撑十分好,APTplugin都能做到增量编译。
    一同咱们内部也有一套依据最新KSP的注解处理代码,KSP是kotlin专门用于处理注解做的一套完结,咱们之前用的都是kapt,可是kapt只能处理Kotlin类的注解,假如是KotlinJava混合的工程,他还没办法处理,所以在他内部还包了一层Javaapt,碰到他解析不了的文件,就调用apt去解析,所以他的处理速度是十分慢的。
    而KSP是依据语法树剖析去做的,咱们知道,一切的代码在编译之前,都会先经过语法树剖析,他便是在这一步顺带把剖析出来的词法返回,让咱们做一些自己的定制逻辑。所以KSP其实不仅仅能够做注解处理,还能够做一些定制的语法剖析规矩,相似lint那种。

  • 第三点:TheRouter应该是现现在一切路由里,仅有一个支撑AGP8的。Gradle从7.X开端,内置了编译进程处理的相关办法,所以AGP直接在8.0删除了相同功用的办法,这就形成许多依据TransformAPI的库,在AGP8都没办法运用了。

  • 最终一点也是咱们之前碰到的坑,在用tinker这类热修正结构的时分,由于路由编译的产品代码是无序的,所以每次编译都有或许产生改变,就形成咱们的补丁包十分大。TheRouter对这一点也做了特别支撑,只需你没有新增或改动路由相关的代码,编译产品代码就不会有任何变化。

动态路由TheRouter的设计与实践

接下来需求咱们一同考虑一下,一个路由 他真正需求具备的中心才干是哪些。我前面PPT列了一下,参阅现在业内的一些通用的路由处理计划 它真正中心需求处理的问题就两个点:

  • 一个是解耦UI跳转
  • 一个是下降系统依靠

咱们把这两个方针别离拆开。
在跳转方面,除了业界常用的经过路由字符串映射页面UI之外,咱们还加入了动态参数注入。
也便是一个UI页面需求的默许参数能够经过路由表提前声明好,而路由表能够是远端下发的,那这些默许参数也能够是远端下发的,这就做到了线上默许字段的及时更新。

另一部分,下降依靠,除了常用的SPI接口下沉,将模块功用依靠改为接口协议依靠之外,咱们还供给了事务节点的hook,一切模块能够反向订阅所需的事务节点,并在事务产生时做自己的逻辑处理。

这一个才干最常用的当地,比方咱们在做隐私合规的时分,要求用户赞同隐私协议今后,才干做一些敏感API的调用。在曾经的开发,这些调用都得要放到隐私弹窗地址的模块内,当用户点赞同按钮今后,再调用其他模块初始化办法。这种逻辑对模块化是十分难受的,由于增大了跨模块的交流,假如团队特别大,不同团队担任不同模块的时分,这种交流就很累了,假定初始化办法需求增加一个参数,还得额定处理。哪些才干是要一发动就调用的,哪些API是有必要用户赞同今后才干调用的,都得交流清楚。

而咱们做了事务节点订阅今后,就把这种依靠某个事务节点的功用,做成了订阅发布模式,你只需求声明初始化办法依靠用户赞同隐私协议就行了,在用户赞同今后就会主动调用初始化办法。

别的,咱们还答应客户端创立一套依据规矩引擎的触发与呼应,能够大局动态智能处理用户操作。假定客户端此时碰到什么意外状况,比方一个女性用户,在夜里十一二点打车,路上又在某些偏远点产生反常逗留,客户端能够主动做一些咱们预置的事情,比方主动报警、语音或许视频主动联络咱们的客服。比方像今年iPhone14的新功用,有个事故检测,假如车翻了或许撞车了,主动帮你打救援电话。而咱们这一系列规矩,都能够是动态呼应的。

接下来看一下路由的规划细节

动态路由TheRouter的设计与实践

TheRouter 会在编译期依据注解生成 RouteMap__最初的类,这些类中记录了当时模块的一切路由信息,也便是当时模块的路由表。

在最顶层的app模块中,经过Gradle插件,将一切aar、源码中的RouteMap__最初的类统一会集到TheRouterServiceProvideInjecter类中。

后续运用发动后,初始化路由时只需求履行TheRouterServiceProvideInjecter类的办法,就能没有任何反射的加载到悉数的路由表了。

加载今后的路由表会被保存到一个支撑正则匹配的 Map 中,这也是TheRouter答应多个path对应同一个落地页的原因。每逢产生页面跳转时,经过跳转时的path,去Map中获取到对应的落地页信息,再正常调用startActivity()即可。

动态路由TheRouter的设计与实践

关于模块化开发中跨模块的调用,咱们引荐选用 SOA(面向服务架构) 的规划方法,服务调用方与运用方完全阻隔,调用模块外的才干不需求重视才干的供给者是谁。 ServiceProvider 的中心规划思想也是这样的,目前服务间的调用协议选用接口的方法。当然,也能够兼容不经过接口下沉而是直接调用的状况。

详细到 Android 侧便是 AIDL 相似的规划,仅仅要比AIDL开发简略许多:

  • 服务供给方担任供给服务,不需求关怀调用方是谁会在何时调用自己。
  • 服务的运用方只重视服务本身,不需求关怀这个服务是谁供给的,只需求只能服务能供给哪些才干即可。

例如上面的图片:服务运用方需求运用录音的服务,服务供给方则向外供给一个录音的服务,由TheRouterServiceProvider担任撮合。

服务运用方:

无需关怀,IRecordService这个接口服务是谁供给的,他只需求知道自己需求运用这样的一个服务就行了。 注:假如没有供给服务的供给方,TheRouter.get()或许返回null

TheRouter.get(IRecordService::class.java)?.doRecord()

服务供给方:

服务供给方需求声明一个供给服务的办法,用@ServiceProvider注解标记。

  • 假如是 java,有必要是 public static 润饰
  • 假如是 kotlin,主张写成 top level 的函数
  • 办法名不限
/**
 * 办法名不约束,恣意姓名都行
 * 返回值有必要是服务接口名,假如是完结了服务的子类,需求加上returnType约束(例如下面代码)
 * 办法有必要加上 public static 润饰,否则编译期就会报错
 */
@ServiceProvider
public static IRecordService test() {
    return new IRecordService() {
        @Override
        public void doRecord() {
            String str = "履行录制逻辑";
        }
    };
}
// 也能够直接返回目标,然后标注这个办法的服名是什么
@ServiceProvider(returnType = IRecordService.class)
public static RecordServiceImpl test() {
    // xxx 
}

动态路由TheRouter的设计与实践

前面讲过,TheRouter是完全面向模块化开发供给的一套处理计划。

在模块化开发时,或许每个模块都有自己需求初始化的一些代码。曾经的做法是把这些代码都在Application里声明,可是这样或许跟着事务变化每次都需求修正Application地址模块。TheRouter 的单模块主动初始化才干便是为了处理这样的状况,能够只在当时模块声明初始化办法后,将会在事务场景时主动被调用。

每个期望被主动初始化的办法,有必要运用public static润饰,首要原因是这姿态就能经过类名直接调用了。别的许多初始化代码都需求获取Context目标,所以咱们将Context作为初始化办法的默许参数,会主动传入Application。其他的地址类名、办法名都没有约束,反正只需加上了 @FlowTask 注解,在编译期都能经过 APT 获取到。

或许隐私合规的时分,有一些功用需求赞同隐私协议才干调用。

跨模块依靠的时分,需求另一个模块初始化今后,才干调用当时模块的初始化,等等事务都能够用事务节点自主订阅的方法去解耦。

动态路由TheRouter的设计与实践

每个加了 @FlowTask 注解的办法,都会在编译期被解析,生成一个对应的 Task 目标,这个目标包含了初始化办法的相关信息,比方:是否异步履行、使命名、是否依靠其他使命先履行。

当一切aar都编译完结,生成好悉数的 Task 今后,会在主 app 中经过Gradle插件进行聚合,在这时会将一切的 Task 做一次查看,经过构建有向无环图来防止 Task 产生循环引证的状况。

每次运用发动后,会在路由初始化时,将有向图中的悉数Task,依照依靠联系按次序加载。

能够在当时模块中,恣意类中声明一个恣意办法名的办法,给办法添加上@FlowTask 的注解即可。

@FlowTask 注解参数说明:

  • taskName:当时初始化使命的使命名,有必要大局仅有,主张格局为:moduleName_taskName

  • dependsOn:参阅Gradle Task,使命与使命之间或许会有依靠联系。假如当时使命需求依靠其他使命先初始化,则在这里声明依靠的使命名。能够一同依靠多个使命,用英文逗号分隔,空格可选,会被过滤:dependsOn = “mmkv, config, login”,默许为空,运用发动就被调用

  • async:是否要在异步履行此使命,默许false。

最终一个,APP动态呼应的完结。

动态路由TheRouter的设计与实践

仍是回到之前的比如:假定一个女性、夜里12点、KTV上车、偏远地址泊车,那么咱们就能够依据这样的一系列先决条件,交由后端的智慧大脑剖析,然后下发给客户端一个动作:比方翻开视频或语音,让客服介入。

而把这个比如笼统一下,一切用户的操作,比方点击、曝光、页面跳转等等埋点数据,都能够作为剖析数据交给服务端剖析,然后让客户端履行:跳转页面、弹窗、优惠券、或许其他本当地法。

这样的一个流程做完了今后,只需咱们有一个可靠的行为剖析模型,咱们是大概率能够预测用户接下来的行为是要做什么的。

当然,即使咱们没有这样一个用户行为剖析的大脑,纯客户端的计划,也是能够支撑的,这便是离线端智能计划了。

动态路由TheRouter的设计与实践

动态路由TheRouter的设计与实践

最终咱们再来看一下前面说到的几个 APP 的坏处,在 TheRouter 中是怎样处理的呢?

  • 第一个:页面Crash,咱们能够经曩昔修正路由表,然后咱们把某一些页面的 Crash 给它降级,降级成 H5 或许说是小程序。当假定咱们这个页面没办法拜访的时分,咱们能够让用户先暂时地去拜访 H5 页面或许说小程序页面。同样的,假如某个页面白屏好久,咱们也能够经过降级,直接经过H5或小程序的方法兼容翻开。

  • 第二个:关于一些接口字段,老版别的兼容问题,咱们也是能够去下发默许参数的方法。假如老版别它强制要求有某一个参数,那其实咱们能够把这个参数给下发成一个默许参数。假如咱们做了千人千面的话,那每一个用户都能够到达不同参数不同展现的效果。

  • 第三个:新功用透传及时性。假定咱们当时有某一个直播的页面,新版别已经有一个能够让用户打赏或许说是让用户发礼物这样的功用了。那老版别它还没有这样的一个功用的话,咱们能够经过点击礼物图标后,修正落地页把它给他提示晋级弹窗。这样的晋级弹窗对用户是影响最小的,它只在运用到这个功用的时分才需求做某一些晋级。

  • 第四个意外事情处理:便是我前面讲到的云端大脑或端智能这样的运用场景了。

最终咱们来看今日的第三部分,今年的状况咱们都能感触,各种人员优化,咱们都很忙,那怎样将这种大的技能重构本钱降到最低呢,咱们为TheRouter开发了许多周边才干:

动态路由TheRouter的设计与实践

TheRouter供给了图形化界面的一键搬迁工具,能够一键从其他路由搬迁到TheRouter,整个搬迁进程都是依据字符串匹配完结的,不触及任何黑科技,一切的替换点也都会展现出来,十分安全。在替换完结后,主动输出改动页面与测验点,大幅减少了开发与测验的工作量。

动态路由TheRouter的设计与实践

还有一个用于主动跳转的高效IDE辅助插件,能够直接从路由的声明处查看到哪些当地跳转到本路由,再也不必怕路由字符串满天飞了。

只需求点一下左边的图标,就能主动跳转到落地页了。假定咱们有多个跳转,跳转到同一个落地页的,点击落地页左边的图标,也会展现出对应的代码,选择今后也能够主动跳转曩昔。

别的还有一个很好的特性,便是假如你写了没有落地页的跳转,会在IDE左边有个黄色的正告,提示你是不是由于手抖或其他原因,写错了path

动态路由TheRouter的设计与实践

别的TheRouter还供给了官网和微信群,官网有许多的技能文档和辅导教程,有不懂的问题还能够加入微信群寻求协助。

官网:therouter.cn

微信群:therouter.cn/wx/

总的来说,TheRouter 并不仅仅是一个细巧灵活的路由库,而是一整套完好的 Android 模块化处理计划,能够处理几乎悉数的模块化进程中会遇到的问题。 关于现有的路由结构,咱们也在最大限度支撑滑润搬迁。你也能够在Github issue中提出需求,咱们评估后会赶快支撑,也欢迎任何人供给 Pull Requests