乐趣区

关于android:没错TheRouter-是我写的

没错,货拉拉开源的路由库 —— TheRouter 是我写的

大概在 17 年底到 18 年初的时候,我常常会讲一些过后做模块化开发的心得和踩坑历程。比方这几篇都是那时候写的:《Android 模块化平台设计》、《优雅移除模块间耦合》、《企业级 Android 模块化平台设计倡议》。

但起初我缓缓不讲这些了,因为我发现做模块化,尽管咱们能总结进去一套较为通用的解决方案,但很难通过几次短短的技术分享就跟他人讲清楚。并且很容易让人产生误解:咱们是小公司,不须要做模块化。再加上因为过后是基于公司已有的根底建设,和制度的一些限度,并不能对外开源一套较为欠缺的模块化计划,开源一套残缺的模块化计划 这个种子就始终埋下了。

说回 TheRouter

这个名字,其实相熟我的都晓得,之前写过一个开源类 MVP 框架,叫 TheMVP,基本上成为了一种将Activity 看做 P 层架构的行业标准。起初被支付宝应用了,也在 设置 - 对于 - 版权信息 外面能查到,直到前几天我去反编译的时候,都还看到 BaseActivity 用的是我的代码。

The 代表了一种唯一性,示意有这个就够了。

TheRouter也是一样,我置信用过 TheRouter 当前你才会真正意识到,当初的企业级 Android 模块化应该怎么玩。

为什么要应用 TheRouter

路由是现如今 Android 开发中必不可少的性能,尤其是企业级 APP,能够用于将 Intent 页面跳转的强依赖关系解耦,同时缩小跨团队开发的相互依赖问题。

对于大型 APP 开发,根本都会选用模块化 (或组件化) 形式开发,对于模块间解耦要求更高。TheRouter 是一整套齐全面向模块化开发的解决方案,不仅能反对惯例的模块依赖解耦、页面跳转,同时提供了模块化过程中常见问题的解决办法。例如:完满解决了模块化开发后组件内无奈获取 Application 生命周期与业务流程,造成每次初始化与关联依赖调用都须要跨模块批改代码的问题。

不过为什么要用,说到底,还是用 ARouter 用的太头疼了。

  • 一个是死板,所有路由都是写死的,凡是想灵便一点,把线上 Crash 的页面降级成 H5 长期解决,都得改一大堆代码还很多限制性。
  • 另一个就是效率,不论是编译时长还是启动耗时,这俩问题都始终不解决。某个厂的开源我的项目都这样,作者们该降职的降职,该转岗的转岗,剩下的躺平不论,毕竟修修补补这事不占 KPI,没法述职啊。没方法,本人来吧,谁让咱们还有启动耗时指标的。
  • 再就是遇到的一个坑,在用 tinker 下发补丁的时候,发现同一个分支打进去的包,ARouterButterknife 的产物包代码都不一样,间接增大了补丁体积。
  • 当然,还有很多差别,看这个表格吧。
性能 TheRouter ARouter WMRouter
Fragment 路由 ✔️ ✔️ ✔️
反对依赖注入 ✔️ ✔️ ✔️
加载路由表 无运行时扫描
无反射
运行时扫描 dex
反射实例类
性能损耗大
运行时读文件
反射实例类
性能损耗中
注解正则表达式 ✔️ ✖️ ✔️
Activity 指定拦截器 ✔️(四大拦截器可依据业务定制) ✖️ ✔️
导出路由文档 ✔️(路由文档反对增加正文形容) ✔️ ✖️
动静注册路由信息 ✔️ ✔️ ✖️
APT 反对增量编译 ✔️ ✔️(开启文档生成则无奈增量编译) ✖️
plugin 反对增量编译 ✔️ ✖️ ✖️
多 Path 对应同一页面(低成本实现双端 path 对立) ✔️ ✖️ ✖️
远端路由表下发 ✔️ ✖️ ✖️
反对单模块独立初始化 ✔️ ✖️ ✖️
反对应用路由关上第三方库页面 ✔️ ✖️ ✖️
反对应用路由关上第三方库页面 ✔️ ✖️ ✖️
对热修复反对(例如 tinker) ✔️(未扭转的代码屡次构建无变动) ✖️(屡次构建 apt 产物会发生变化,生成无意义补丁) ✖️(屡次构建 apt 产物会发生变化,生成无意义补丁)

动静页面路由能力

其实单纯的页面路由,没什么好说的,基本上所有人都是这么做的。APT 编译期生成一个形容类,gradle插件聚合所有的形容类,利用启动的时候再加载形容类,就这么一个流程。TheRouter 文档外面写的十分具体了,这里次要讲讲路由在古代 APP 中要怎么用。

TheRouter 从设计阶段,思考的就是 APP 动态化能力。所以既能反对第三方 SDK 的路由跳转,也能反对插件化的开发状态,又能解决 H5HybridFlutter 混合的这种我的项目,反正路由表都是能够轻易增加。

那,真正用途最多的是通过动静下发,晋升客户端容灾能力。
比方在线上,某些页面或者外围下单交易流程因为客户端开发忽略,造成无奈应用的状况,能够通过路由将对应页面降级为 H5 或者小程序,保障线上 APP 仍然是可用的状态。

有两种举荐的近程下发形式可供使用方抉择:

  1. 将打包零碎与配置零碎买通,每次新版本 APP 打包后主动将 assets/ 目录中的配置文件上传到配置零碎,下发给对应版本 APP。长处在于全自动不会出错。
  2. 配置零碎无奈买通,线上手动下发须要批改的路由项,因为 TheRouter 会主动用最新下发的路由项笼罩包内的路由项。长处在于准确,且流量资源占用小。

注:一旦你设置了自定义的InitTask,原框架内路由表初始化工作将不再执行,你须要本人解决找不到路由表时的兜底逻辑,一种倡议的解决形式见如下代码。

// 此代码 必须 在 Application.super.onCreate() 之前调用
RouteMap.setInitTask(new RouterMapInitTask() {
    /** 
     * 此办法执行在异步
     */
    @Override
    public void asyncInitRouteMap() {
        // 此处为纯业务逻辑,每家公司远端配置计划可能都不一样
        // 不倡议每次都申请网络,否则申请网络的过程中,路由表是空的,可能造成 APP 无奈跳转页面
        // 最好是优先加载本地,而后开异步线程加载远端配置
        String json = Connfig.doHttp("routeMap");
        // 倡议加一个判断,如果远端配置拉取失败,应用包内配置做兜底计划,否则可能造成路由表异样
        if (!TextUtils.isEmpty(json)) {List<RouteItem> list = new Gson().fromJson(json, new TypeToken<List<RouteItem>>() {}.getType());
            // 倡议远端下发路由表差别局部,用远端包笼罩本地更正当
            RouteMap.addRouteMap(list);
        } else {
            // 在异步执行 TheRouter 外部兜底路由表
            initRouteMap()}
    }
});

另一种状况,如果某些页面传参过程中,漏传了一些固定参数,也能够通过动静下发路由表的形式,对不同的页面,做动静的默认参数注入,这样就能达到不发版也能间接修复某些参数引起的小问题。

TheRouter中下发路由表的格局:

[
  {
    "path": "https://kymjs.com/therouter/test",
    "className": "com.therouter.app.autoinit.TestActivity",
    "action": "","description":"",
    "params": {"key":"value"}
  },
  ......
]

单模块主动初始化能力

其实,做模块化最麻烦的两个点,第一个是依赖解耦,第二个应该就是独立模块的初始化问题了。再加上当初对于隐衷合规问题越查越严,各种权限都必须在隐衷弹窗受权当前能力应用,使得模块独立更难,动不动就得改到 Application 壳工程。

TheRouter 的单模块主动初始化能力就是为了解决这样的状况,能够只在以后模块申明初始化办法后,将会在业务场景时主动被调用。

相似于 Gradle 的 Task,你也能够申明本人的初始化 Task,而后申明的时候提供好须要依赖的其余 Task,这样只有依赖的那个 Task 没有初始化,你的工作就不会被初始化。直到依赖的那个 Task 初始化实现,你的工作才会被主动调用。

/**
 * 将会在异步执行
 */
@FlowTask(taskName = "mmkv_init", dependsOn = TheRouterFlowTask.APP_ONCREATE, async = true)
public static void test2(Context context) {System.out.println("异步 =========Application onCreate 后执行");
}

@FlowTask 注解参数阐明:

  • taskName:以后初始化工作的工作名,必须全局惟一,倡议格局为:moduleName_taskName
  • dependsOn:参考 Gradle Task,工作与工作之间可能会有依赖关系。如果当前任务须要依赖其余工作先初始化,则在这里申明依赖的工作名。能够同时依赖多个工作,用英文逗号分隔,空格可选,会被过滤:dependsOn = “mmkv, config, login”,默认为空,利用启动就被调用
  • async:是否要在异步执行此工作,默认 false。

内置初始化节点

应用这个能力,在路由外部默认反对了两个生命周期类工作,可在应用时间接援用

  • TheRouterFlowTask.APP_ONCREATE:当 Application 的 onCreate()执行后初始化
  • TheRouterFlowTask.APP_ONSPLASH:当利用的首个 Activity.onCreate()执行后初始化

同时,应用 TheRouter 的主动初始化依赖,也无需放心循环依赖造成的问题,框架会在编译期构建有向无环图,监测循环依赖状况,如果发现会在编译期间接报错,并且还会将产生循环援用的工作显示进去,用于排错。

动态化能力

还有一个,动态化能力。这个能力其实是须要整个我的项目公司的配合,比方有一套相似智慧大脑的计划,能够基于客户端过来的一些埋点数据,智能推断出用户下一步要做的事件,而后通过长连贯间接向客户端下发指令做某些事件。

不过抛开后端的能力,独自靠客户端也是能够应用的。

Action 实质是一个全局的零碎回调,次要用于预埋的一系列操作,例如:弹窗、上传日志、清理缓存。
与 Android 零碎自带的播送告诉相似,你能够在任何中央申明动作与解决形式。并且所有 Action 都是能够被跟踪的,只有你违心,能够在日志中将所有的动作调用栈输入,以不便调试应用。

当用户执行某些操作(关上某个页面、H5 点击某个按钮、动静页面配置的点击事件)时,将会主动触发,执行预埋的 Action 逻辑。

但还是强烈推荐,将端上数据与服务端链路买通,依据客户端不同的用户行为,交由后端剖析,进而揣测出用户下一步动作,提前执行下发逻辑交给客户端执行,则是一套残缺的动态化计划。

模块化反对,Gradle 脚本一键切换源码援用

在模块化开发过程中,如果没有采纳分仓,或采纳了分仓但仍然应用 git-submodule 的形式开发,应该都会遇到一个问题。如果集成包采纳源码编译,构建工夫切实太久,大大降低开发调试效率;如果采纳 aar 依赖编译,对于底层模块批改了代码,每次都要从新构建 aar,在下层模块批改版本号当前,能力持续整包构建编译,也极大影响开发效率。
TheRouter 中提供了一个 Gradle 脚本,只须要在开发本地的 local.properties 文件中申明要参加编译的 module,其余未声明的默认应用 aar 编译,这样就能灵便切换源码与 aar,并且不会影响其他人,如下节选代码可供参考应用:

/**
 * 如果工程中有源码,则依赖源码,否则依赖 aar
 */
def moduleApi(String compileStr, Closure configureClosure) {String[] temp = compileStr.split(":")
    String group = temp[0]
    String artifactid = temp[1]
    String version = temp[2]

    Set<String> includeModule = new HashSet<>()
    rootProject.getAllprojects().each {if (it != rootProject) includeModule.add(it.name)
    }

    if (includeModule.contains(artifactid)) {println(project.name + "源码依赖:===project(\":$artifactid\")")
        projects.project.dependencies.add("api", project(':' + artifactid), configureClosure)
//        projects.project.configurations {compile.exclude group: group, module: artifactid}
    } else {println(project.name + "依赖:=======$group:$artifactid:$version")
        projects.project.dependencies.add("api", "$group:$artifactid:$version", configureClosure)
    }
}

在理论应用时,能够齐全应用 moduleApi 替换掉原有的api。当然,implementation 也能够有一个对应的 moduleImplementation,这样只须要正文或解正文setting.gradle 文件内的 include 语句就能够达到切换源码、aar的目标了。
具体的应用办法,能够看我这篇文章外面讲的【源码与 aar 互斥】的实现:https://xiaozhuanlan.com/topic/2735849061

什么年代了,还在用 ARouter?

反对从 ARouter 一键迁徙!
没错,什么年代了,还在用 ARouter?
对于这种已有的存量路由框架,当然也是提供了一键迁徙的图形化工具。
为了写这个工具我也是废了好大的劲,特意学了一遍 JavaFX 怎么用,而后打了一个 Mac 产物、一个 Windows 产物。
不禁感叹:Java 的跨平台才是真正的跨平台啊。

注:传到了 GitHub,可能有点慢,急躁期待

  • Mac OS 迁徙工具下载:https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Mac.zip
  • Windows 迁徙工具下载:https://github.com/HuolalaTech/hll-wp-therouter-android/wiki/uploads/file/TheRouterTransfer-Windows.zip
退出移动版