Thuộc tính android:autoVerify
android:autoVerify="true" là một thuộc tính nhỏ trên thẻ <intent-filter>, nhưng là thứ quyết định một link https sẽ mở app hay mở trình duyệt. Bài này đi sâu vào thuộc tính đó; bối cảnh tổng quan về deep link xem ở bài Universal Links và App Links.
Ví dụ: cùng một intent-filter, có và không có autoVerify
Phần tiêu đề “Ví dụ: cùng một intent-filter, có và không có autoVerify”App MyShop khai báo nhận link https://shop.example.com/products/...:
<intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="shop.example.com" /> <data android:pathPrefix="/products/" /></intent-filter>Website đã đặt https://shop.example.com/.well-known/assetlinks.json đúng. Người dùng bấm link https://shop.example.com/products/123 trong ứng dụng tin nhắn:
Không có autoVerify | Có autoVerify="true", xác minh thành công | |
|---|---|---|
| Android 12 trở lên (app target 31+) | Mở trình duyệt (link https chưa xác minh mặc định mở trình duyệt) | Mở thẳng app |
| Android 6 đến 11 | Hiện hộp thoại chọn ứng dụng (trình duyệt hoặc MyShop) | Mở thẳng app |
Không có autoVerify, intent-filter trên chỉ là một deep link thường. Có autoVerify và xác minh thành công, nó trở thành Android App Link: app được hệ thống công nhận là trình xử lý mặc định cho domain đó.
Điểm đáng chú ý: trên Android 12+, thiếu autoVerify hay xác minh thất bại đều không báo lỗi gì, link chỉ lặng lẽ mở trình duyệt. Vì vậy khi link “không mở app”, việc đầu tiên là kiểm tra trạng thái xác minh (xem phần Kiểm tra trạng thái xác minh).
autoVerify làm gì?
Phần tiêu đề “autoVerify làm gì?”Thuộc tính này báo cho hệ thống: “hãy xác minh rằng tôi thật sự được domain này cho phép mở link”. Khi đó Android:
- Lấy danh sách các host trong intent-filter đủ điều kiện (xem mục dưới).
- Với mỗi host, tải file
https://<host>/.well-known/assetlinks.json. - Kiểm tra file có khai báo đúng
package_namecủa app và fingerprint SHA-256 của chứng chỉ ký app đang cài hay không. - Ghi lại kết quả cho từng host. Host xác minh thành công thì link thuộc host đó mở thẳng app.
Bước xác minh chỉ xét scheme và host. Phần path (pathPrefix, pathPattern…) dùng để quyết định link nào được app nhận, không tham gia vào việc xác minh. Ngoại lệ: từ Android 15, assetlinks.json có thể chứa luật path động (Dynamic App Links), khi đó path cũng được áp dụng theo file.
Intent-filter nào được xét?
Phần tiêu đề “Intent-filter nào được xét?”Hệ thống chỉ xét các intent-filter có đủ:
- Action
android.intent.action.VIEW - Category
android.intent.category.BROWSABLEvàandroid.intent.category.DEFAULT - Scheme
httphoặchttps
Thiếu BROWSABLE (link từ trình duyệt và ứng dụng khác sẽ không tới được app) hoặc thiếu DEFAULT là lỗi khá phổ biến khi viết tay intent-filter.
Tài liệu Android khuyên đặt autoVerify="true" cho mọi intent-filter muốn được xác minh, thay vì chỉ một filter.
Khi nào Android xác minh?
Phần tiêu đề “Khi nào Android xác minh?”| Phiên bản | Thời điểm xác minh |
|---|---|
| Android 14 trở xuống | Chỉ khi cài đặt hoặc cập nhật app. Sửa assetlinks.json sau đó thì máy đã cài không biết, phải chờ bản cập nhật tiếp theo (hoặc cài lại) |
| Android 15 trở lên | Khi cài/cập nhật, và định kỳ chạy lại trong nền. Thay đổi trên file có thể mất tới 7 ngày mới tới hết các máy |
Xác minh chạy bất đồng bộ sau khi cài, thường cần chờ khoảng 20 giây. Bấm link ngay sau khi cài xong mà thấy mở trình duyệt chưa chắc đã là cấu hình sai.
Hệ quả thực tế: nếu assetlinks.json bị lỗi đúng lúc người dùng cập nhật app (server bảo trì, file bị xoá nhầm khi deploy website), các máy Android 14 trở xuống sẽ mang trạng thái xác minh thất bại cho tới lần cập nhật sau. File này nên được coi như một phần của hạ tầng app: có giám sát, không để đội web sửa tuỳ ý.
Khác biệt giữa Android 11 trở xuống và Android 12 trở lên
Phần tiêu đề “Khác biệt giữa Android 11 trở xuống và Android 12 trở lên”Nhiều host: tất cả hay từng cái
Phần tiêu đề “Nhiều host: tất cả hay từng cái”<intent-filter android:autoVerify="true"> ... <data android:scheme="https" /> <data android:host="shop.example.com" /> <data android:host="m.example.com" /></intent-filter>Giả sử shop.example.com có file đúng, còn m.example.com không có file:
- Android 11 trở xuống: xác minh thất bại cho cả app. Chỉ cần một host hỏng là không host nào được công nhận, kể cả các host ở intent-filter khác.
- Android 12 trở lên: xác minh từng host riêng.
shop.example.comvẫn mở app, chỉm.example.commở trình duyệt.
Khi app vẫn còn người dùng Android 11 trở xuống, nên chỉ khai báo những host mà bạn kiểm soát được file assetlinks.json.
Link chưa xác minh
Phần tiêu đề “Link chưa xác minh”- Android 11 trở xuống: link khớp intent-filter nhưng chưa xác minh sẽ hiện hộp thoại chọn ứng dụng.
- Android 12 trở lên: link https chưa xác minh mặc định mở trình duyệt. Người dùng vẫn có thể tự bật cho app trong Settings → Apps → MyShop → Open by default → Add link.
Các hành vi Android 12 ở trên áp dụng cho app có targetSdk từ 31 trở lên. App target thấp hơn vẫn dùng cơ chế cũ, trừ khi bật compat change (xem lệnh ở phần kiểm tra bên dưới). Hiện Google Play đã yêu cầu target SDK cao hơn 31 từ lâu nên với app trên Play thì gần như luôn là hành vi mới.
Wildcard host
Phần tiêu đề “Wildcard host”<data android:host="*.example.com" />Khớp mọi subdomain như shop.example.com, m.example.com. Với wildcard, file assetlinks.json phải đặt ở domain gốc: https://example.com/.well-known/assetlinks.json.
Ngoài trường hợp wildcard, mỗi subdomain là một host riêng biệt: example.com, www.example.com và shop.example.com khai báo riêng thì mỗi cái cần một file riêng.
Hai cái bẫy khi viết intent-filter
Phần tiêu đề “Hai cái bẫy khi viết intent-filter”Các thẻ <data> trong cùng filter được gộp lại
Phần tiêu đề “Các thẻ <data> trong cùng filter được gộp lại”Mọi thẻ <data> trong cùng một intent-filter được gộp thành tổ hợp của tất cả scheme, host, path. Ví dụ:
<intent-filter android:autoVerify="true"> ... <data android:scheme="https" android:host="shop.example.com" android:pathPrefix="/products/" /> <data android:scheme="https" android:host="blog.example.com" android:pathPrefix="/posts/" /></intent-filter>Trông như hai luật riêng, nhưng thực tế filter sẽ nhận cả shop.example.com/posts/... và blog.example.com/products/.... Muốn tổ hợp chính xác thì tách thành hai intent-filter.
Không trộn custom scheme vào filter cần xác minh
Phần tiêu đề “Không trộn custom scheme vào filter cần xác minh”<!-- SAI: custom scheme trong filter có autoVerify --><intent-filter android:autoVerify="true"> ... <data android:scheme="https" /> <data android:scheme="myshop" /> <data android:host="shop.example.com" /></intent-filter>Tài liệu Android ghi rõ: không đưa scheme nào khác ngoài http/https vào filter cần xác minh, vì sẽ làm xác minh thất bại. Custom scheme (myshop://) đặt ở một intent-filter riêng, không có autoVerify.
Về http: mẫu trong tài liệu Android khai báo cả http và https trong filter để link http:// cũ cũng mở được app. Trong app nên xử lý link http như https.
Kiểm tra trạng thái xác minh
Phần tiêu đề “Kiểm tra trạng thái xác minh”Bằng adb
Phần tiêu đề “Bằng adb”# Xem trạng thái xác minh từng hostadb shell pm get-app-links com.example.myshopKết quả mẫu:
com.example.myshop: ID: 01234567-89ab-cdef-0123-456789abcdef Signatures: [***] Domain verification state: shop.example.com: verified m.example.com: 1024Ý nghĩa các trạng thái:
| Trạng thái | Ý nghĩa |
|---|---|
verified | Xác minh thành công. Chỉ trạng thái này mới là đạt |
none | Chưa có kết quả. Chờ thêm vài phút rồi yêu cầu xác minh lại |
approved | Được ép duyệt, thường bằng lệnh shell |
denied | Bị ép từ chối, thường bằng lệnh shell |
migrated | Kết quả giữ lại từ cơ chế xác minh cũ |
restored | Được duyệt sau khi khôi phục dữ liệu người dùng |
legacy_failure | Bị cơ chế xác minh cũ từ chối, không rõ lý do cụ thể |
system_configured | Được duyệt tự động bởi cấu hình của thiết bị |
1024 trở lên | Mã lỗi riêng của trình xác minh trên thiết bị. Kiểm tra mạng, file trên server, rồi xác minh lại |
Quy trình xác minh lại sau khi sửa assetlinks.json (Android 12+):
# 1. Đưa trạng thái link của app về ban đầuadb shell pm set-app-links --package com.example.myshop 0 all
# 2. Yêu cầu xác minh lại (máy phải có Internet), chờ vài phútadb shell pm verify-app-links --re-verify com.example.myshop
# 3. Xem kết quảadb shell pm get-app-links com.example.myshopNếu app target dưới Android 12, cần bật cơ chế xác minh mới trước bước 1:
adb shell am compat enable 175408749 com.example.myshopXem lựa chọn của người dùng (các domain người dùng tự bật/tắt trong Settings):
adb shell pm get-app-links --user cur com.example.myshopCách chắc chắn nhất để thử lại từ đầu vẫn là gỡ app, cài lại, chờ 20 giây rồi bấm link.
Trong code
Phần tiêu đề “Trong code”Từ Android 12 (API 31), app có thể tự kiểm tra domain nào đã được xác minh bằng DomainVerificationManager:
val manager = context.getSystemService(DomainVerificationManager::class.java)val userState = manager.getDomainVerificationUserState(context.packageName)
// Domain đã xác minh qua assetlinks.jsonval verifiedDomains = userState?.hostToStateMap ?.filterValues { it == DomainVerificationUserState.DOMAIN_STATE_VERIFIED }
// Domain người dùng tự chọn cho app mởval selectedDomains = userState?.hostToStateMap ?.filterValues { it == DomainVerificationUserState.DOMAIN_STATE_SELECTED }
// Domain chưa được duyệtval unapprovedDomains = userState?.hostToStateMap ?.filterValues { it == DomainVerificationUserState.DOMAIN_STATE_NONE }Nếu có domain chưa được duyệt, có thể mở màn hình Open by default để người dùng tự bật:
val intent = Intent( Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:${context.packageName}"),)context.startActivity(intent)Nên giải thích cho người dùng vì sao trước khi đưa họ tới màn hình này. Ngoài ra, có thể gửi các số liệu này về analytics để phát hiện sớm khi xác minh trên máy người dùng thật bị lỗi hàng loạt.
Một số lưu ý khác
Phần tiêu đề “Một số lưu ý khác”Mỗi package name, mỗi chứng chỉ ký cần có trong assetlinks.json
Phần tiêu đề “Mỗi package name, mỗi chứng chỉ ký cần có trong assetlinks.json”Xác minh so cả package_name lẫn fingerprint của chứng chỉ ký bản đang cài. Các trường hợp hay bị quên:
- Flavor/build type đổi applicationId, ví dụ bản dev là
com.example.myshop.dev: cần thêm một mục riêng cho package này trongassetlinks.json(thường là file của domain staging). - Bản debug ký bằng debug key, bản build local ký bằng upload key, bản tải từ Google Play ký bằng app signing key: ba fingerprint khác nhau. Mảng
sha256_cert_fingerprintschấp nhận nhiều giá trị. Xem thêm bài Google Play App Signing.
Nhiều activity hoặc nhiều app cùng nhận một link
Phần tiêu đề “Nhiều activity hoặc nhiều app cùng nhận một link”- Nhiều activity trong cùng app có intent-filter khớp cùng một App Link: không đảm bảo activity nào sẽ nhận. Nên chỉ để một activity (thường là activity chính) nhận link rồi tự điều hướng bên trong.
- Hai app cùng xác minh được cùng host và path (ví dụ bản lite và bản đầy đủ): chỉ app cài gần nhất nhận link.
Flutter
Phần tiêu đề “Flutter”Với Flutter, intent-filter đặt trong android/app/src/main/AndroidManifest.xml, trong thẻ <activity> của MainActivity. Mọi điều trong bài này áp dụng y nguyên, vì xác minh là việc của hệ điều hành, không liên quan tới framework.