Để so khớp các URI phân cấp với các mẫu và trích xuất các đối số, hãy sử dụng UriDeepLinkMatcher. Thư viện này dựa vào kotlinx.serialization để chuyển đổi tuần tự các đối số đã so khớp thành các lớp khoá của bạn.
Để tạo một UriDeepLinkMatcher, hãy cung cấp một mẫu DeepLinkUri và trình chuyển đổi tuần tự cho khoá tương ứng:
@Serializable data class UserProfileKey(val id: String) : NavKey val userProfilePattern = DeepLinkUri("www.example.com/users/{id}") val userProfileMatcher = UriDeepLinkMatcher(userProfilePattern, serializer<UserProfileKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val matchResult = userProfileMatcher.match(request) val key = matchResult?.key // UserProfileKey(id = "123")
Đối với các URI không phân cấp hoặc lược đồ tuỳ chỉnh (chẳng hạn như tel:), hãy xem phần Tạo các đối tượng so khớp đường liên kết sâu tuỳ chỉnh.
Các mẫu so khớp được hỗ trợ
UriDeepLinkMatcher so khớp URI dựa trên 5 thành phần: giao thức, quyền hạn, đường dẫn, truy vấn và phân đoạn. Các phần sau đây mô tả cú pháp mẫu, phần giữ chỗ đối số và quy tắc so khớp được hỗ trợ cho từng thành phần.
So khớp lược đồ
Nếu không có lược đồ nào trong mẫu URI, cả http và https đều được so khớp.
Để so khớp một lược đồ cụ thể, hãy thêm lược đồ đó vào mẫu. Tuy nhiên, giao thức http trong một mẫu sẽ khớp với cả URI yêu cầu http và https, trong khi https trong một mẫu chỉ khớp với các yêu cầu https.
| URI mẫu | URI yêu cầu | Trùng khớp |
|---|---|---|
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 |
✅ |
So khớp cơ quan cấp
UriDeepLinkMatcher thực hiện so khớp chính xác không phân biệt chữ hoa chữ thường trên cơ quan URI (máy chủ và cổng không bắt buộc). Không hỗ trợ phần giữ chỗ hoặc ký tự đại diện trong cơ quan và không có đối số nào được trích xuất:
| URI mẫu | URI yêu cầu | Trùng khớp |
|---|---|---|
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 |
❌ |
So khớp đường dẫn
Các mẫu đường dẫn sau đây được hỗ trợ:
| URI mẫu | URI yêu cầu | Trùng khớp | Đối số được trích xuất |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Không có |
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: "" (Chuỗi trống) |
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 |
✅ | Không có |
www.example.com/users |
https://www.example.com/users/ |
❌ (Dấu gạch chéo ở cuối tạo ra một phân đoạn bổ sung) | Không áp dụng |
So khớp cụm từ tìm kiếm
Thứ tự tham số truy vấn trong URI yêu cầu không cần phải khớp với thứ tự trong URI mẫu. Ngoài ra, các tham số có trong URI yêu cầu nhưng không có trong URI mẫu sẽ bị bỏ qua.
Chúng tôi hỗ trợ các mẫu tham số truy vấn sau:
| URI mẫu | URI yêu cầu | Đối số được trích xuất |
|---|---|---|
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: "" (Chuỗi trống) |
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" |
So khớp mảnh
Chúng tôi hỗ trợ các loại mẫu đoạn mã sau đây:
| URI mẫu | URI yêu cầu | Đối số được trích xuất |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Không có |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Không có |
Loại dữ liệu được hỗ trợ
UriDeepLinkMatcher hỗ trợ giải tuần tự hoá các đối số URI thành các kiểu nguyên thuỷ, enum, tập hợp và đối tượng tuỳ chỉnh. Quá trình chuyển đổi tuần tự được chia thành hai danh mục:
- Chuyển đổi tuần tự tiêu chuẩn: Sử dụng
kotlinx.serializationđể chuyển đổi tuần tự thành:- Các kiểu dữ liệu cơ bản (
Boolean,Int,Long,Float,Double,Char,Byte,Short) vàString - Liệt kê
Set,ListhoặcArraycủa các kiểu dữ liệu cơ bản, chuỗi hoặc enum- Các lớp
@Serializablelồng ghép (có các thuộc tính được làm phẳng thành các phần giữ chỗ URI riêng lẻ)
- Các kiểu dữ liệu cơ bản (
- Chuyển đổi tuần tự tuỳ chỉnh bằng
DeepLinkSerializer: Chuyển đổi giữa mộtStringvà các đối tượng tuỳ chỉnh, các loại bên ngoài (chẳng hạn nhưjava.time.LocalDate) hoặc các tập hợp được phân tách tuỳ chỉnh.
Chuyển đổi tuần tự tiêu chuẩn
UriDeepLinkMatcher hoạt động ngay khi xuất xưởng đối với các loại tiêu chuẩn và cấu trúc được đơn giản hoá mà không yêu cầu triển khai trình chuyển đổi tuần tự tuỳ chỉnh.
Kiểu dữ liệu nguyên thuỷ và chuỗi
UriDeepLinkMatcher tự động giải mã các kiểu nguyên thuỷ (Boolean, Int, Long, Float, Double, Char, Byte, Short) và String:
@Serializable data class UserProfileKey(val id: Int) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/users/{id}"), serializer<UserProfileKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val key = matcher.match(request)?.key // UserProfileKey(id = 123)
Liệt kê
Các giá trị liệt kê được so khớp có phân biệt chữ hoa chữ thường với tên phần tử liệt kê:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class ProductsKey(val sort: SortOrder) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/products?sort={sort}"), serializer<ProductsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/products?sort=DATE") val key = matcher.match(request)?.key // ProductsKey(sort = SortOrder.DATE)
Bộ sưu tập truy vấn lặp lại
Các tham số truy vấn có khoá lặp lại (chẳng hạn như ?id=10&id=20) sẽ tự động chuyển đổi tuần tự thành List<T>, Set<T> hoặc Array<T>, trong đó T là kiểu nguyên thuỷ, String hoặc enum:
@Serializable data class FilteredItemsKey(val ids: List<Int>) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/items?id={ids}"), serializer<FilteredItemsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/items?id=10&id=20") val key = matcher.match(request)?.key // FilteredItemsKey(ids = listOf(10, 20))
Các lớp @Serializable lồng nhau
Khi NavKey chứa một thuộc tính có loại là một lớp @Serializable khác, UriDeepLinkMatcher sẽ làm phẳng các thuộc tính của lớp đó để mỗi thuộc tính của lớp lồng nhau ánh xạ trực tiếp đến một tham số URI riêng lẻ có cùng tên:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class SearchFilters( val category: String, val sortBy: SortOrder = SortOrder.RELEVANCE ) @Serializable data class SearchKey( val query: String, val page: Int = 1, // Flattened into {category} and {sortBy} val filters: SearchFilters ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/search?q={query}&page={page}&category={category}&sortBy={sortBy}"), serializer<SearchKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=kotlin&category=books&sortBy=DATE") val key = matcher.match(request)?.key // SearchKey(query = "kotlin", page = 1, filters = SearchFilters(category = "books", sortBy = SortOrder.DATE))
Tuỳ chỉnh quá trình chuyển đổi tuần tự bằng DeepLinkSerializer
Để giải tuần tự hoá các đối tượng tuỳ chỉnh (chẳng hạn như Filter(key = "brand", value =
"pixel")), các loại bên ngoài (chẳng hạn như java.time.LocalDate) hoặc các chuỗi tuỳ chỉnh được phân tách (chẳng hạn như các giá trị được phân tách bằng dấu phẩy), hãy mở rộng DeepLinkSerializer<T>.
DeepLinkSerializer<T> là một KSerializer<T> trừu tượng chuyển đổi giữa String và T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Ví dụ: hãy xem xét các định nghĩa Filter và FilterSerializer được dùng trong các đoạn mã sau:
@Serializable data class Filter(val key: String, val value: String) object FilterSerializer : DeepLinkSerializer<Filter>() { override val serialName: String = "com.example.Filter" override fun deserialize(value: String): Filter { val parts = value.split(":", limit = 2) if (parts.size < 2) { throw SerializationException("Invalid filter: $value. Expected key:value.") } return Filter(key = parts[0], value = parts[1]) } override fun serialize(value: Filter): String = "${value.key}:${value.value}" }
Đối tượng tuỳ chỉnh đơn lẻ
Để giải mã một đối tượng từ một chuỗi tham số URI duy nhất (chẳng hạn như ?filter=brand:google), hãy chú giải thuộc tính bằng @Serializable(with = ...):
@Serializable data class CatalogKey( @Serializable(with = FilterSerializer::class) val filter: Filter ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/catalog?filter={filter}"), serializer<CatalogKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/catalog?filter=brand:google") val key = matcher.match(request)?.key // CatalogKey(filter = Filter("brand", "google"))
Đối tượng tuỳ chỉnh trong các tham số truy vấn lặp lại
Để giải tuần tự các tham số truy vấn lặp lại thành một tập hợp các đối tượng tuỳ chỉnh (List<T>, Set<T> hoặc Array<T>), hãy triển khai DeepLinkSerializer<T> cho loại phần tử T và chú thích đối số loại của thuộc tính bằng @Serializable(with = ...):
@Serializable data class SearchResultsKey( val query: String, val filters: List<@Serializable(with = FilterSerializer::class) Filter> = emptyList() ) : NavKey val searchResultsPattern = DeepLinkUri("www.example.com/search?q={query}&filter={filters}") val searchResultsMatcher = UriDeepLinkMatcher(searchResultsPattern, serializer<SearchResultsKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=phone&filter=brand:google&filter=color:hazel") val matchResult = searchResultsMatcher.match(request) val key = matchResult?.key // SearchResultsKey(query = "phone", filters = listOf(Filter("brand", "google"), Filter("color", "hazel")))
Tập hợp được phân tách trong các thông số đơn lẻ
Để phân tích cú pháp các giá trị được phân tách bằng dấu phẩy hoặc dấu phân cách tuỳ chỉnh (chẳng hạn như ?ids=1,2,3) thành một tập hợp, hãy triển khai DeepLinkSerializer cho toàn bộ loại tập hợp và chú thích thuộc tính bằng @Serializable(with = ...):
object IntListCsvSerializer : DeepLinkSerializer<List<Int>>() { override val serialName: String = "com.example.IntListCsv" override fun deserialize(value: String): List<Int> { if (value.isEmpty()) return emptyList() return value.split(",").map { it.trim().toInt() } } override fun serialize(value: List<Int>): String = value.joinToString(",") } @Serializable data class ItemListKey( @Serializable(with = IntListCsvSerializer::class) val ids: List<Int> ) : NavKey val itemListPattern = DeepLinkUri("www.example.com/items/{ids}") val itemListMatcher = UriDeepLinkMatcher(itemListPattern, serializer<ItemListKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/items/10,20,30") val key = itemListMatcher.match(request)?.key // ItemListKey(ids = listOf(10, 20, 30))
Kết quả xác thực và so khớp đối số
UriDeepLinkMatcher phân biệt giữa các trường hợp không khớp (trả về null để có thể thử các đối sánh khác) và các cấu hình không được hỗ trợ (gây ra một ngoại lệ).
Thông tin không khớp
Xảy ra trường hợp không khớp khi URI yêu cầu đến không đáp ứng các yêu cầu về mẫu hoặc loại:
- Thiếu các tham số bắt buộc: Các thuộc tính khoá không thể rỗng mà không có giá trị mặc định và các tham số URI tương ứng không có trong URI yêu cầu.
- Lỗi phân tích cú pháp loại: Giá trị đối số được trích xuất không thể phân tích cú pháp thành loại thuộc tính dự kiến (ví dụ:
"abc"cho thuộc tínhInt).
Khi có sự không khớp, UriDeepLinkMatcher.match sẽ trả về null, cho phép đánh giá các đối tượng so khớp tiếp theo.
Hãy xem xét một lớp khoá và đối tượng so khớp được định cấu hình bằng các giá trị mặc định, các đối tượng lồng nhau và enum:
enum class MapLayer { STANDARD, SATELLITE, TERRAIN } @Serializable data class LayerOptions( val style: String, val layer: MapLayer = MapLayer.STANDARD ) @Serializable data class MapKey( val location: String, val zoom: Int = 12, val options: LayerOptions ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/map/{location}?zoom={zoom}&style={style}&layer={layer}"), serializer<MapKey>() )
Bảng sau đây minh hoạ kết quả so khớp cho nhiều URI yêu cầu:
| URI yêu cầu | Kết quả giải mã | Kết quả trận đấu |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Thành công (Đã cung cấp tất cả các thông số) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Thành công (zoom mặc định là 12, layer đến STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Thành công (Tham số truy vấn không bắt buộc trống sử dụng 12 mặc định) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Không khớp (Thiếu tham số location bắt buộc) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Không khớp ("close" không phải là Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Không khớp ("HYBRID" không có trong enum) |
null |
Cấu hình không được hỗ trợ
Nếu lớp khoá của bạn chứa các kiểu dữ liệu không được hỗ trợ, thì UriDeepLinkMatcher sẽ gửi một ngoại lệ trong quá trình so khớp thay vì trả về null.
- Bản đồ và các tập hợp đa chiều:
UriDeepLinkMatcherchỉ hỗ trợ các tập hợp một chiều gồm các kiểu nguyên thuỷ, chuỗi, enum hoặc kiểu tuỳ chỉnh được chú thích bằngDeepLinkSerializer.Mapcác loại sẽ gửi mộtIllegalArgumentException, trong khi các tập hợp lồng nhau (chẳng hạn nhưList<List<String>>) sẽ gửi mộtSerializationException. - Bộ sưu tập đối tượng tuỳ chỉnh chưa được chú thích: Bộ sưu tập các loại tuỳ chỉnh (chẳng hạn như
List<Filter>) sẽ gửiSerializationExceptiontrừ phi loại phần tử được chú thích bằngDeepLinkSerializer. - Các lớp lồng ghép chưa được làm phẳng: Các lớp
@Serializablelồng ghép không thể được ánh xạ đến một phần giữ chỗ duy nhất (chẳng hạn như?user={user}) mà không cóDeepLinkSerializer.
// Throws IllegalArgumentException: Map decoding is not supported. @Serializable data class InvalidKey(val tags: Map<String, String>) : NavKey // Throws SerializationException: Only collections of primitives are supported. @Serializable data class InvalidKey(val filters: List<Filter>) : NavKey
So sánh UriMatchResult
Các phiên bản UriMatchResult được xếp hạng theo các tiêu chí sau đây theo thứ tự:
- Loại MatchResult:
UriMatchResultcó thứ hạng cao hơn các loạiMatchResultkhác. - Đường dẫn chính xác: Các kết quả khớp theo nghĩa đen sẽ được xếp hạng cao hơn các kết quả khớp theo trình giữ chỗ hoặc ký tự đại diện.
- Số lượng đối số đường dẫn: Số lượng đối số đường dẫn càng nhiều thì thứ hạng càng cao.
- Sự hiện diện của các đối số: Các đối số khớp với đối số sẽ xếp hạng cao hơn những đối số không khớp.
- Tổng số đối số: Tổng số đối số (đường dẫn, truy vấn, phân đoạn) là tiêu chí cuối cùng để phân định thứ hạng.
Tuỳ chỉnh UriDeepLinkMatcher
UriDeepLinkMatcher là một lớp open mà bạn có thể tạo lớp con để tuỳ chỉnh hành vi so khớp URI và trích xuất đối số:
matchRequest: Điểm truy cập so khớp cấp cao nhất choDeepLinkRequestđến. Ghi đè phương thức này để kiểm tra các phần bổ sung của yêu cầu hoặc áp dụng các điều kiện tiên quyết tuỳ chỉnh trước khi so khớp URI.matchUri: So khớpDeepLinkUrivới mẫu đã định cấu hình. Ghi đè phương thức này để chặn và chuẩn hoá các URI đến (ví dụ: viết lại các miền con động hoặc định dạng đường dẫn cũ) trước khi gọisuper.matchUri.matchArguments: Chuyển đổi tuần tự các đối số đường dẫn, truy vấn và mảnh đã trích xuất thành một thực thể khoá điều hướng bằng cách sử dụngserializerđã cung cấp. Ghi đè phương thức này để chèn các giá trị động hoặc biến đổi đối số trước khi khởi tạo khoá.
Ví dụ sau đây minh hoạ cách tạo lớp con UriDeepLinkMatcher để chuẩn hoá tiền tố đường dẫn URL cũ trước khi so khớp:
class LegacyPrefixUriDeepLinkMatcher<T : Any>( uriPattern: DeepLinkUri, serializer: KSerializer<T> ) : UriDeepLinkMatcher<T>(uriPattern, serializer) { override fun matchUri(uri: DeepLinkUri): UriMatchResult<T>? { val path = uri.path val normalizedUri = if (path != null && path.startsWith("/legacy/")) { DeepLinkUri(uri.toString().replaceFirst("/legacy", "")) } else { uri } return super.matchUri(normalizedUri) } }