匹配 URI 深层链接

如需将分层 URI 与模式进行匹配并提取参数,请使用 UriDeepLinkMatcher。它依靠 kotlinx.serialization 将匹配的参数反序列化为您的键类。

如需创建 UriDeepLinkMatcher,请提供模式 DeepLinkUri 以及相应键的序列化器:

对于非分层 URI 或自定义架构(例如 tel:),请参阅创建自定义深层链接匹配器。

支持的匹配模式

UriDeepLinkMatcher 根据 URI 的五个组成部分(scheme、authority、path、query 和 fragment)来匹配 URI。以下部分介绍了每个组件支持的模式语法、实参占位符和匹配规则。

架构匹配

如果 URI 格式中没有架构,则 http 和 https 都会匹配。如需匹配特定架构,请将其包含在格式中。作为例外情况,模式中的 http 架构会同时匹配 http 和 https 请求 URI,而模式中的 https 仅匹配 https 请求。

模式 URI 请求 URI 匹配
www.example.com https://www.example.com ✅
www.example.com http://www.example.com ✅
http://www.example.com http://www.example.com ✅
http://www.example.com https://www.example.com ✅
https://www.example.com http://www.example.com ❌
myapp://www.example.com myapp://www.example.com ✅

授权机构匹配

UriDeepLinkMatcher 对 URI 授权机构(主机和可选端口)执行不区分大小写的完全匹配。授权机构中不支持占位符或通配符,并且不会提取任何参数:

模式 URI 请求 URI 匹配
example.com https://example.com ✅
example.com https://EXAMPLE.COM ✅
example.com https://sub.example.com ❌
example.com https://www.example.com ❌
example.com https://example.com:8080 ❌
example.com:8080 https://example.com:8080 ✅
example.com:8080 https://example.com ❌

路径匹配

支持以下路径模式:

模式 URI 请求 URI 匹配 提取的参数
www.example.com/users https://www.example.com/users ✅ 无
www.example.com/users/{id} https://www.example.com/users/123 ✅ id:"123"
www.example.com/users/{first}-{last} https://www.example.com/users/john-doe ✅ first: "john", last: "doe"
www.example.com/users/{id}/profile https://www.example.com/users//profile ✅ id:""(空字符串)
www.example.com/users/user_{id} https://www.example.com/users/user_123 ✅ id:"123"
www.example.com/users/{userId}/posts/{postId} https://www.example.com/users/123/posts/456 ✅ userId: "123", postId: "456"
www.example.com/users/.* https://www.example.com/users/john-doe ✅ 无
www.example.com/users https://www.example.com/users/ ❌(尾部斜杠会创建额外的片段) 不适用

搜索查询匹配

请求 URI 中的查询参数顺序无需与模式 URI 中的顺序一致。此外,系统会忽略请求 URI 中存在但模式 URI 中不存在的参数。

支持以下查询参数模式:

模式 URI 请求 URI 提取的参数
www.example.com/users?name={name} https://www.example.com/users?name=john name:"john"
www.example.com/users?name={name} https://www.example.com/users?name= name:""(空字符串)
www.example.com/users?{rawQuery} https://www.example.com/users?anything&else rawQuery:["anything", "else"]
www.example.com/users?type=user_{id} https://www.example.com/users?type=user_123 id:"123"
www.example.com/users?name={first}_{last} https://www.example.com/users?name=john_doe first: "john", last: "doe"
www.example.com/users?list={list} https://www.example.com/users?list=10&list=20 list:["10", "20"]
www.example.com/users?name={name}&{other} https://www.example.com/users?name=john&tab=info name: "john", other: ["tab=info"]
www.example.com/users?type=user_.* https://www.example.com/users?type=user_admin type:"admin"

片段匹配

支持以下片段模式类型:

模式 URI 请求 URI 提取的参数
www.example.com/#section1 https://www.example.com/#section1 无
www.example.com/#section_{id} https://www.example.com/#section_123 id:"123"
www.example.com/#section_.* https://www.example.com/#section_123 无

受支持的数据类型

UriDeepLinkMatcher 支持将 URI 实参反序列化为基元类型、枚举、集合和自定义对象。序列化分为两类:

  • 标准序列化:使用 kotlinx.serialization 反序列化为:
    • 基元(Boolean、Int、Long、Float、Double、Char、Byte、Short)和 String
    • 枚举
    • Set、List 或 Array(基元、字符串或枚举)
    • 嵌套的 @Serializable 类(其属性会扁平化为各个 URI 占位符)
  • 使用 DeepLinkSerializer 进行自定义序列化:在单个 String 与自定义对象、外部类型(例如 java.time.LocalDate)或自定义分隔的集合之间进行转换。

标准序列化

UriDeepLinkMatcher 适用于标准类型和扁平化结构,无需自定义序列化器实现。

基元和字符串

UriDeepLinkMatcher 会自动解码基元类型(Boolean、Int、Long、Float、Double、Char、Byte、Short)和 String:

枚举

枚举值与枚举元素名称进行匹配时区分大小写:

重复的查询集合

具有重复键(例如 ?id=10&id=20)的查询参数会自动反序列化为 List<T>、Set<T> 或 Array<T>,其中 T 是基元类型、String 或枚举:

嵌套的 @Serializable 类

当 NavKey 包含类型为另一个 @Serializable 类的属性时,UriDeepLinkMatcher 会展平其属性,以便嵌套类的每个属性直接映射到同名的单个 URI 参数:

如需反序列化自定义对象(例如 Filter(key = "brand", value = "pixel"))、外部类型(例如 java.time.LocalDate)或自定义分隔字符串(例如逗号分隔值),请扩展 DeepLinkSerializer<T>。

DeepLinkSerializer<T> 是一个抽象 KSerializer<T>,可在 String 和 T 之间进行转换:

abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
    abstract val serialName: String
    abstract fun deserialize(value: String): T
    abstract fun serialize(value: T): String
}

例如,请考虑以下代码段中使用的 Filter 和 FilterSerializer 定义:

单个自定义对象

如需从单个 URI 参数字符串(例如 ?filter=brand:google)中解码对象,请使用 @Serializable(with = ...) 为属性添加注解:

重复查询参数中的自定义对象

如需将重复的查询参数反序列化为自定义对象(List<T>、Set<T> 或 Array<T>)的集合,请为元素类型 T 实现 DeepLinkSerializer<T>,并使用 @Serializable(with = ...) 为属性的类型实参添加注解:

单个参数中的分隔集合

如需将以逗号分隔或自定义分隔的值(例如 ?ids=1,2,3)解析为集合,请为整个集合类型实现 DeepLinkSerializer,并使用 @Serializable(with = ...) 为属性添加注解:

参数验证和匹配结果

UriDeepLinkMatcher 会区分不匹配(返回 null,以便可以尝试其他匹配器)和不受支持的配置(抛出异常)。

不一致

当传入请求 URI 不满足模式或类型要求时,会发生不匹配:

  • 缺少必需参数:不可为 null 的键属性没有默认值,且其对应的 URI 参数在请求 URI 中缺失。
  • 类型解析失败:提取的实参值无法解析为预期属性类型(例如,Int 属性的 "abc")。

如果发生不匹配,UriDeepLinkMatcher.match 会返回 null,以便评估后续匹配器。

假设有一个键类和匹配器,它们配置了默认值、嵌套对象和枚举:

下表展示了各种请求 URI 的匹配结果:

请求 URI 解码结果 比赛结果
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE 成功(提供了所有参数) UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE)))
https://www.example.com/map/paris?style=dark 成功(zoom 默认为 12,layer 默认为 STANDARD) UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map/paris?zoom=&style=dark 成功(空的可选查询参数使用默认 12) UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD)))
https://www.example.com/map?style=dark 不一致(缺少必需的 location 参数) null
https://www.example.com/map/paris?zoom=close&style=dark 不匹配("close" 不是 Int) null
https://www.example.com/map/paris?style=dark&layer=HYBRID 不匹配("HYBRID" 不在枚举中) null

不受支持的配置

如果您的键类包含不受支持的数据类型,UriDeepLinkMatcher 会在匹配期间抛出异常,而不是返回 null。

  • 映射和多维集合:UriDeepLinkMatcher 仅支持使用 DeepLinkSerializer 注释的基元、字符串、枚举或自定义类型的一维集合。Map 类型会抛出 IllegalArgumentException,而嵌套集合(例如 List<List<String>>)会抛出 SerializationException。
  • 未加注解的自定义对象集合:自定义类型(例如 List<Filter>)的集合会抛出 SerializationException,除非元素类型带有 DeepLinkSerializer 注解。
  • 未展平的嵌套类:如果没有 DeepLinkSerializer,嵌套的 @Serializable 类无法映射到单个占位符(例如 ?user={user})。

UriMatchResult对比

UriMatchResult 实例按以下条件依次排序:

  1. MatchResult 类型:UriMatchResult 的排名高于其他 MatchResult 类型。
  2. 确切路径:字面量路径匹配的排名高于占位符或通配符匹配。
  3. 路径参数数量:路径参数较多的匹配项排名较高。
  4. 存在实参:捕获实参的匹配项排名高于未捕获实参的匹配项。
  5. 总参数数量:总参数数量(路径、查询、片段)是最终的决胜因素。

自定义UriDeepLinkMatcher

UriDeepLinkMatcher 是一个 open 类,您可以对其进行子类化,以自定义 URI 匹配和参数提取行为:

  • matchRequest:传入 DeepLinkRequest 的顶级匹配入口点。替换此方法可在 URI 匹配之前检查请求 extra 或应用自定义前提条件。
  • matchUri:将 DeepLinkUri 与配置的模式进行匹配。替换此方法可在调用 super.matchUri 之前拦截并规范化传入的 URI(例如,重写动态子网域或旧版路径格式)。
  • matchArguments:使用提供的 serializer 将提取的路径、查询和 fragment 实参映射反序列化为导航键实例。替换此方法可在密钥实例化之前注入动态价值或转换参数。

以下示例演示了如何对 UriDeepLinkMatcher 进行子类化,以便在匹配之前对旧版网址路径前缀进行标准化: