“任意门”:一行配置实现页面跳转重定向。
背景 & 痛点 & 价值
动态路由组件,处理的是 App 中最最常见的一种行为的问题,那就是:跳转。
随着 App 技术栈的扩展,从原本最最简单的原生到原生的跳转,扩展到目前同一个 App 中包含原生页面、H5 页面、Weex 页面、Flutter 页面之间的跳转。
随之而来的问题就是:随着 App 的版本迭代,很多原本原生实现的页面,需要通过新的 H5 或者 Weex 页面进行升级/降级。而这些原本都是硬编码的跳转逻辑,可能需要随着版本不停改动。总结下来,现有的,各个技术栈隔离的页面跳转逻辑面临的直接问题有:
跳转逻辑跟着版本走,无法统一进行改动
跨技术栈跳转的实现成本比较高,必须在桥接模块中进行特殊适配
在一些 H5 需要使用专门 WebView 页面打开的场景下,很难去适配,也必须通过各个 Web 跳转的拦截做特殊处理
为了解决以上硬编码以及灵活性差的问题,我们决定梳理现有的各技术栈跳转逻辑,将这些跳转整合,能够满足动态性、可配置的需求。
得益于项目中原有的路由跳转组件,各种页面之间的页面都可以通过 URL 的方式进行路由,于是我们基于 URL 跳转,开发了一套动态路由组件,它完成的工作有 :
承担 App 内所有跳转逻辑
通过配置中心组件,支持获取/配置路由替换规则
匹配所有的路由跳转规则,命中规则的,替换成新的目标路由地址
将实际跳转目标地址传递给路由组件执行实际的跳转行为
一、实现方案
1.1 路由拦截+替换
微商城客户端目前已经有一套稳固的组件化实现方案,组件之间的页面跳转通过路由的方式进行解耦,这是一种比较常见的方式。
在微商城项目中,负责实现的路由组件为 ZanURLRouter
,它的职责很简单:
启动时注册路由和页面
找寻正确的页面进行跳转
在不影响外部接口的前提下,我们在目标路由解析这一步,引入了动态路由
对于移动端的路由重定向,实际上就是将一个路由转换为另一个路由,如:
youzan://orderlist?type=1&status=2
转换为:
wsc://orderlist/v2?type=1&status=2
1.2 跳转规则配置
路由的拦截和替换中的一个关键节点就是“配置”,我们需要一个路由规则列表来记录和下发匹配规则。为了方便下发路由规则表,我们将这份配置表存放在有赞移动配置中心,根据客户端的版本进行区分,动态地下发给不同版本的客户端。
一条路由规则,分为一个 Key 和对应的 Value,Key 为匹配方式,使用正则表达式进行匹配,Value 为替换方式,使用 JSON 格式定义。
实际代码实现中,我们将“路由规则”和“路由替换行为”分别抽象成实体类和接口方法。
1.2.1 抽象实体类
关于替换路由跳转的规则,我们可以这样配置:
即:一条匹配规则 + 一条替换模板。我们将之抽象为一个实体类, Rule
:
1.2.2 抽象接口
有了规则配置之后,就需要对动态路由的行为进行抽象,核心就是初始化规则、匹配规则和替换路由三个方法:
动态路由器会在应用启动阶段拉取正确的规则表,解析并记录下来:
在 ZanURLRouter
解析目标路由的时候,对每一个规则进行匹配测试,命中则应用匹配的规则,返回替换后的路由,再继续接下来的工作。
1.3 路由替换
实体类、接口类都抽象完成之后,就是动态路由的核心实现了,这里依赖到一个的核心工具就是:正则表达式。这里用到正则的场景有两个:
正则验证是否命中规则
正则替换url文本
在 Android 和 iOS 开发中,字符串正则相关的 API 都是自带的,开箱即用:
1.4 疑难问题:参数处理
大部分情况下,跳转本身都是带参数的,那么动态替换跳转的 URL 之后,参数的获取就成了一个问题,尤其是原生和其他页面页面的跳转。
我们主要以 Android 为例,Android 原生跳转都是通过一个关键类:Intent 来实现参数的存取。这里需要注意的是,由于 Intent 传值存在多种复杂的数据接口,包括 Parcelable 这种复杂参数的场景,由于降级之后都是以 URL 的形式传值,所以我们目前约定动态路由的参数只支持基本数据类型,复杂参数类型的需要接入方来做兼容。
参数处理我们分两个典型的场景来讨论:
原生跳转 H5 参数传递
H5 跳转原生的参数传递
1.4.1 原生跳转 H5
这里的方式主要是将 Intent 中的基本数值类型参数取出来,拼接成带参数的 URL 来实现将 Intent 里面的参数传递给 H5,主要实现代码如下:
1.4.2 H5 跳转原生
同理的,H5 跳转原生做的就是将 URL 中携带的参数塞到 Intent 中来进行。
这里比较关键的一个问题是:Intent 的取值都是带类型的,而 URL 的参数都是字符串。我们目前解决方案也很简单,就是封装 Intent 的取值方法,由于目前有赞 Android 主要使用 Kotlin 来开发,可以使用 Kotlin 的扩展函数特性来实现(Java 可以使用工具类的方式):
1.5 碰到的坑:UrlEncode
在匹配和替换 URL 规则的场景中,我们经常会碰到这么一种情况,URL 是被 UrlEncode 过的。由于字符串的正则匹配和正则替换是不会判断字符串是否被 UrlEncode 过,所以这里的逻辑需要由路由组件来实现。
UrlEncode 字符串的正则匹配逻辑实现比较简单,即直接将字符串 Decode 之后进行匹配。
比较复杂的是 UrlEncode 字符串的正则替换,有些情况下,路由中的 url 是必须进行 UrlEncode 的,如果直接 Decode 进行替换,那么可能会导致实际跳转的目标 URL 被错误地截断,导致无法跳转,所以这里的替换必须保留 UrlEncode 的字符。
我们的解决思路是:记录 URLEncode 前后被 encode 字符的下标,然后再手动实现 replace 方法去挨个替换字符串中的字符,核心代码如下:
二、实际应用案例
2.1 应用中心
微商城 App 应用中心,应该是应用动态路由的最佳场景,应用中心存在大量跳转的场景。
先来说下使用动态路由的背景,应用中心中应用列表都是由服务端统一下发的,后端为每个应用配置的跳转地址是统一的,而 Android 和 iOS 本地路由配置的 URL 是不一致的,如果直接下发配置的话,会存在有一端无法跳转的问题。以店铺管理应用跳转为例:
iOS中店铺管理的路由 URL:wsc://shop/management
Android 中的路由URL:wsc://team/management
服务端下发的URL:wsc://team/management
那么解决同一套配置跳转不同 URL 的这个问题,就交给动态路由来完成了,我只需要在 iOS 的动态路由添加一个规则,将 wsc://shop/management 动态替换成 wsc://team/management 就可以搞定!
2.2 订单项目
在微商城客户端的订单模块重构项目中,考虑到订单是使用频次很高的核心场景之一,且代码历史较久,所以新的模块上线后与旧订单列表模块共存,直到灰度完全结束。
由于微商城已经是组件化拆分,业务组件之间的跳转使用路由完成,我们在设计灰度方案时,利用动态路由来实时进行目标路由的映射:
具体可见 《 微商城订单模块重构实践》一文。
三、总结
“上线只是开始”,随着业务迭代,历史业务也越来越多,为了保证不同平台版本的用户能够平滑过渡到新的功能上去,动态路由组件扮演了一个客户端的 URL 重定向服务的角色,避免因服务下线、功能更新、平台差异、项目重构等原因导致的功能不可用。
动态路由组件,核心就是非常简单的正则匹配和正则替换,而这个非常简单和核心代码逻辑,实现了业务场景下非常重要的路由重定向。这整套解决方案,也是有赞移动端在应用组件化、动态化的一个重要组成部分,我们也希望这个技术方案能够抛砖引玉,启发更多优秀的移动端动态化解决思路。
本文转载自公众号有赞 coder(ID:youzan_coder)。
原文链接:
评论