比對 URI 深層連結

如要比對階層式 URI 與模式並擷取引數,請使用 UriDeepLinkMatcher。它會依賴 kotlinx.serialization 將相符的引數反序列化為索引鍵類別。

如要建立 UriDeepLinkMatcher,請提供模式 DeepLinkUri 和對應鍵的序列化器:

如為非階層式 URI 或自訂配置 (例如 tel:),請參閱「建立自訂深層連結比對器」。

支援的比對模式

UriDeepLinkMatcher 會根據 URI 的五個元件 (配置、授權、路徑、查詢和片段) 比對 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 不符合模式或類型規定,就會發生不相符的情況:

  • 缺少必要參數:不可為空值的鍵屬性沒有預設值,且要求 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 比對前檢查要求額外資訊,或套用自訂先決條件。
  • matchUri:根據設定的模式比對 DeepLinkUri。覆寫此方法,即可在呼叫 super.matchUri 之前,攔截並正規化傳入的 URI (例如重寫動態子網域或舊版路徑格式)。
  • matchArguments:使用提供的 serializer,將擷取的路徑、查詢和片段引數對應反序列化為導覽鍵例項。覆寫這個方法,即可在金鑰例項化前插入動態值或轉換引數。

以下範例說明如何建立 UriDeepLinkMatcher 的子類別,在比對前正規化舊版網址路徑前置字元: