Để so khớp URI phân cấp với các mẫu và trích xuất đối số, hãy sử dụng UriDeepLinkMatcher. Thư viện này dựa vào kotlinx.serialization để giải tuần tự các đối số khớp vào các lớp khoá.
Để tạo UriDeepLinkMatcher, hãy cung cấp 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 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 trình 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, đường dẫn, truy vấn và mảnh. 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, thì cả http và https đều được so khớp.
Để so khớp một lược đồ cụ thể, hãy đưa lược đồ đó vào mẫu. Ngoại lệ, lược đồ http trong một mẫu 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 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ó thẩm quyền
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 URI
authority (máy chủ và cổng không bắt buộc). Phần giữ chỗ hoặc ký tự đại diện không được hỗ trợ trong cơ quan quản lý 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 thêm một phân đoạn) | 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.
Các mẫu tham số truy vấn sau đây được hỗ trợ:
| 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 Fragment
Chúng tôi hỗ trợ các loại mẫu mảnh 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ợ việc chuyển đổi tuần tự các đối số URI thành các kiểu dữ liệu gốc, 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 loại:
- Chuyển đổi tuần tự tiêu chuẩn: Sử dụng
kotlinx.serializationđể chuyển đổi tuần tự thành:- Kiểu dữ liệu gốc (
Boolean,Int,Long,Float,Double,Char,Byte,Short) vàString - Liệt kê
Set,ListhoặcArraycủa các kiểu dữ liệu gốc, chuỗi hoặc enum- Các lớp
@Serializablelồng nhau (có thuộc tính được làm phẳng thành các phần giữ chỗ URI riêng lẻ)
- Kiểu dữ liệu gốc (
- 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 lập tức cho các loại tiêu chuẩn và cấu trúc phẳng mà không cần triển khai trình chuyển đổi tuần tự tuỳ chỉnh.
Kiểu dữ liệu gốc và chuỗi
UriDeepLinkMatcher tự động giải mã các kiểu dữ liệu gốc (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ị enum được so khớp có phân biệt chữ hoa chữ thường với tên phần tử enum:
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 giải tuần tự thành List<T>, Set<T> hoặc Array<T>, trong đó T là một kiểu dữ liệu gốc, 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 một 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))
Chuyển đổi tuần tự tuỳ chỉnh 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 được phân tách tuỳ chỉnh (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ú thích 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 tham số truy vấn lặp lại
Để giải tuần tự hoá các tham số truy vấn lặp lại thành một tập hợp đố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 tham 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 tá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 không khớp (trả về null để có thể thử các trình so khớp khác) và cấu hình không được hỗ trợ (gây ra ngoại lệ).
Không khớp
Sự không khớp xảy ra 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 tham số bắt buộc: Các thuộc tính khoá không rỗng không có giá trị mặc định mà 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 xảy ra trường hợp không khớp, UriDeepLinkMatcher.match sẽ trả về null, cho phép
đánh giá các trình so khớp tiếp theo.
Hãy xem xét một lớp khoá và trình so khớp được định cấu hình bằng các giá trị mặc định, đố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ả tham 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 là 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 giá trị mặc định 12) |
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á chứa các loại 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à tập hợp đa chiều:
UriDeepLinkMatcherchỉ hỗ trợ tập hợp một chiều gồm các dữ liệu gốc, chuỗi, enum hoặc loại tuỳ chỉnh được chú thích bằngDeepLinkSerializer. Các loạiMapsẽ 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 không 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. - Lớp lồng ghép không được làm phẳng: Không thể liên kết các lớp
@Serializablelồng ghép với một phần giữ chỗ duy nhất (chẳng hạn như?user={user}) nếu 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 thực thể UriMatchResult được xếp hạng theo thứ tự các tiêu chí sau:
- 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 đường dẫn cố định có thứ hạng cao hơn kết quả khớp phần giữ chỗ hoặc ký tự đại diện.
- Số lượng đối số đường dẫn: Kết quả khớp có nhiều đối số đường dẫn hơn sẽ có thứ hạng cao hơn.
- Sự hiện diện của đối số: Kết quả trùng khớp có chứa đối số sẽ có thứ hạng cao hơn kết quả trùng khớp không chứa đối số.
- Tổng số đối số: Tổng số đối số (đường dẫn, truy vấn, mảnh) là yếu tố quyết định cuối cù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 phù hợ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 miền con động hoặc định dạng đường dẫn cũ) trước khi gọisuper.matchUri.matchArguments: Huỷ chuyển đổi tuần tự đường dẫn, truy vấn và bản đồ đối số 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được cung cấp. Ghi đè giá trị này để chèn giá trị động hoặc biến đổi đối số trước khi tạo thực thể 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) } }