AppLinks
App Links
什么是 App Links?
App Links 是一种特殊类型的 Deep Links,可以看作是 Deep Links 的升级版本(Android 6.0 引入)。二者主要区别在于:当用户点击 App Links 时将直接跳转到应用内容,而 Deep Links 则会弹出应用选择对话框(如果用户设备上有多个应用可以处理相同的 intent)。举例来说:在电子邮件中点击银行发送的 HTTP 网址,如果是 Deep Link 系统可能会显示一个对话框,询问用户是使用浏览器还是银行自己的应用打开此链接,而 App Link 则直接跳转到应用。
Android App links 介绍
- Android App Links 在 Android6.0 及以上才能用
- 用 http/https 协议,包含
autoVerify
属性,该属性允许你把自己 APP 作为该 links 的默认处理者,点击链接就可以直达 APP,而不需要像 DeepLink 有个选择的弹窗来选择哪个应用来处理(解决 deeplink 二次模糊的弹窗)
Android App links 集成
若要添加对 Android App Links 的支持,请执行以下操作:
- manifest 文件中添加
intent-filters
,需要添加android:autoVerify="true"
,否则不会通过 host 的校验,app links 会不生效 - Launcher 的 Activity 添加处理到来的 Links
- 使用
Digital Asset Links
将应用与网站相关联,配置assetlinks.json
使用 AS 自带 App Links Assistant 工具添加 App links 支持
[Add Android App Links Android Studio Android Developers](https://developer.android.google.cn/studio/write/app-link-indexing?hl=zh-cn)
添加 Intent Filters
- ① 在 Host 字段中输入您网站的网址。
- ② 为您要映射的网址添加 path、pathPrefix或pathPattern。(例如,如果您有一个食谱分享应用,所有食谱都在同一个 activity 中,并且对应网站的食谱都在同一个
/recipe
目录中,请使用 pathPrefix 并输入 “/recipe”。这样,网址 http://www.recipe-app.com/recipe/grilled-potato-salad 就会映射到您在下一步中选择的 activity。) - ③ 选择网址应将用户转至的 Activity。
- ④ 点击 OK。此时会显示
URL Mapping Editor
(网址映射编辑器)窗口。App Links Assistant 会根据您映射到 AndroidManifest.xml 文件的网址添加 intent 过滤器,并在 Preview 字段中突出显示更改。如果您要进行任何更改,请点击 Open AndroidManifest.xml 以修改 intent 过滤器。
- 如需验证网址映射是否正常运行,请在 Check URL Mapping 字段中输入网址,然后点击 Check Mapping。如果网址映射正常运行,成功消息会显示您输入的网址映射到您选择的 activity。
注意: path、 pathPrefix、 pathPattern 之间的区别
- path 用来匹配完整的路径,这里将 path 设置为
/assistant/download.html
才能够进行匹配; - pathPrefix 用来匹配路径的开头部分,拿上面的 Uri 来说,这里将 pathPrefix 设置为
/assistant
就能进行匹配了; - pathPattern 用表达式来匹配整个路径,这里需要说下匹配符号与转义。
Intent-Filters 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<activity
android:name=".samples.basic.杂七杂八.链接打开App.urlscheme.URLSchemeActivity"
android:exported="true"
tools:ignore="AppLinkUrlError">
<!-- for deep-link -->
<!-- URI Scheme -->
<intent-filter>
<data
android:host="hacket.me"
android:scheme="hacket" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<!-- App Links -->
<intent-filter>
<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:host="hacket.me" />
</intent-filter>
<intent-filter>
<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:host="hacket.me" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="hacket.me" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="hacket.me" />
</intent-filter>
<intent-filter>
<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:host="lbo0d.test-app.link" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="lbo0d.test-app.link" />
</intent-filter>
</activity>
- path 完全匹配 /recipe,/recipe/xxx 就会匹配不了
- pathPrefix 前缀就可以匹配的上,带 path 的一般都用 pathPrefix 就行了
- 一个 Intent-Filter 标签中,所有的 data 标签的,host/scheme/path 都是互相组合的,只要配置了一个 pathPrefix,其他 host 也是能匹配的
- host 能不能写通配符,如
*
,匹配不上的,得验证一下
1
2
3
4
5
6
7
8
9
<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:host="*.onelink.xxx.com"
android:scheme="https" />
</intent-filter>
Add logic to handle the intent 添加处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class URLSchemeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
val appLinkAction = intent.action
val appLinkData: Uri? = intent.data
if (Intent.ACTION_VIEW == appLinkAction) {
appLinkData?.lastPathSegment?.also { recipeId ->
Uri.parse("content://com.recipe_app/recipe/")
.buildUpon()
.appendPath(recipeId)
.build().also { appData ->
showRecipe(appData)
}
}
}
}
添加 assetlinks.json
文件:Associate website 将应用与网站相关联
添加 assetlinks.json
文件,声明你的网址和应用之间的关系
点击 App Links Assistant 中的 Open Digital Asset Links File Generator
- 输入您的 Site domain 和 Application ID
- 如需在 Digital Asset Links 文件中添加对 一键登录 的支持,请选择 Support sharing credentials between the app and the website,输入网站的登录网址。此操作会将以下字符串添加到 Digital Asset Links 文件中,声明应用和网站共享登录凭据:
delegate_permission/common.get_login_creds
。 - 指定 签名配置 或选择 密钥库文件。
- 点击 Generate Digital Asset Links file。
- Android Studio 生成文件后,点击 Save file 进行下载。
- 将
assetlinks.json
文件上传到您的网站并允许所有人读取,网址为https://yoursite/.well-known/assetlinks.json
;yoursite 就是你在 intent-filter 配置的 host。
系统会通过加密的 HTTPS 协议验证 Digital Asset Links 文件。请确保无论应用的 intent 过滤器是否包括 https,均可通过 HTTPS 连接访问
assetlinks.json
文件。
- 点击 Link and Verify 以确认您已将正确的 Digital Asset Links 文件上传到适当的位置
intent-filter 配置
intent-filter 错误配置
同一个 intent filter 内所有的 <data>
元素会合并在一起,以解释他们所有组合属性的变量。
错误案例: 要配置 app.xxx.xxx
和 api-.xxx.xxx.com/h5/sharejump/appjump
两种域名的 Applinks
错误配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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:host="app.xxx.com"
android:scheme="https"/>
<data
android:host="app.xxx.tw"
android:scheme="https"/>
<data
android:host="app.xxx.se"
android:scheme="https"/>
<data
android:host="api-xxx.xxx.com"
android:pathPrefix="/h5/sharejump/appjump"
android:scheme="https" />
<data
android:host="api-xxx.xxx.com"
android:pathPrefix="/h5/sharejump/appjump"
android:scheme="http" />
</intent-filter>
同一个 intent-filter 中,host、scheme、path 、pathPrefix、pathPattern 如果配置了多个,他们之间会互相组合的,就会导致原本的
https://app.xxx.tw
这种Applinks
失效,必须加上 pathPrefix 才行:https://app.xxx.tw//h5/sharejump/appjump
解决: app.xxx.xxx 和 api-.xxx.xxx.com/h5/sharejump/appjump 要分开在不同的 intent-filter 中配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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:host="app.xxx.com"
android:scheme="https"/>
<data
android:host="app.xxx.tw"
android:scheme="https"/>
<data
android:host="app.xxx.se"
android:scheme="https"/>
</intent-filter>
<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="https"/>
<data android:host="api-xxx.xxx.com"/>
<data android:pathPrefix="/h5/sharejump/appjump"/>
<data android:scheme="http"/>
<data android:host="api-xxx.xxx.com"/>
<data android:pathPrefix="/h5/sharejump/appjump"/>
</intent-filter>
支持不同的子域名
数字资产链接协议把你 intent filter 里的子域名看做唯一的、独立的域名。因此,如果你的 intent filter 列出一个域名及其多个子域名,你需要在每个子域名上发布一个有效的 assetlinks.json 文件。
例如:下面的 intent filter 包含了
和 mobile.example.com 作为期望处理的 intent 的 URL 的主机名。因此,有效的 `assetlinks.json` 必须同时发布在<https://www.example.com/.well-known/assetlinks.json>和<https://mobile.example.com/.well-known/assetlinks.json>。>
1
2
3
4
5
6
7
8
9
10
11
<application>
<activity android:name=”MainActivity”>
<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="https" android:host="www.example.com" />
<data android:scheme="https" android:host="mobile.example.com" />
</intent-filter>
</activity>
</application>
或者,如果你用通配符定义了你要处理的主机名 (如 *.example.com),那么你需要把你的 assetlinks.json 文件发布在根主机名 (根域名) 下 (example.com)。
比如,一个以定义了如下 intent filter 的应用,如果 assetlinks.json 发布在https://example.com/.well- known/assetlinks.json,它的任一子域名 (如 foo.example.com) 将会通过验证。
1
2
3
4
5
6
7
8
9
10
<application>
<activity android:name=”MainActivity”>
<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="https" android:host="*.example.com" />
</intent-filter>
</activity>
</application>
assetlinks.json
文件
配置路径:https://yoursite/.well-known/assetlinks.json
单一网站和单一应用之间的关系
数字资产证明文件必须发布在你的网站上,以证明你的应用是和网站关联在一起的,并且用于验证应用内的 URL 链接模式。这个 JSON 文件用下面几个字段来标识关联的应用:
package_name
:在build.gradle
里定义的 application idsha256_cert_fingerprints
:应用签名的 SHA256 指纹信息。你可以用下面的命令,通过Java keytool
来生成指纹信息:keytool -list -v -keystore my-release-key.keystore
这个字段支持多个指纹信息,可以用来支持不同的应用版本,如开发版本和发布版本
下面这个示例 assetlinks.json 授予链接打开权限给 com.example 应用
1
2
3
4
5
6
7
8
[{ "relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]
让同一网站和不同应用关联起来
网站可以在同一个 assetlinks.json 文件里声明和不同的 app 的关系。下面这个文件列出了两个声明,这两个声明声明了网站和两个应用之间的关联,这个文件位于https://www.example.com/.well-known/assetlinks.json:>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[{ "relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.puppies.app",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.monkeys.app",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]
不同应用或许会处理同一个域名下的不同资源链接。比如:app1 声明一个 intent filter 用于处理https://example.com/articles,app2 声明一个 intent> filter 用于处理https://example.com/videos。>
注意:关联同一个域名的不同应用可能会使用相同或不同的签名文件。
关联一个应用到不同网站
不同的网站可以在它们的 assetlinks.json 文件里声明关联同一个应用。
- 第一个文件展示了关联 app1 到 example.com
1
2
3
4
5
6
7
8
[{ "relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.mycompany.app1",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]
- 关联 app1 到 example.net
1
2
3
4
5
6
7
8
[{ "relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.mycompany.app1",
"sha256_cert_fingerprints":
["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]
发布 JSON 验证文件
assetlinks.json
文件的 content-type 必须为application/json
- 不管你的应用的 intent filter 是否定义 https 作为 data 的 scheme,
assetlinks.json
必须能通过 HTTPS 链接访问 assetlinks.json
必须能不经过任何重定向被访问到 (即没有 301、302 跳转),同时可以被爬虫访问到 (你的robot.txt必须允许抓取/.well-known/assetlinks.json
)- 如果你的应用支持多种域名,你需要把 assetlinks.json 发布在这几个域名的服务器上
- 不要在你的 AndroidManifest 文件里发布无法公开访问的开发/测试 URL(比如那些只能通过 VPN 进行访问的 URL)。一种可行的做法是配置构建类型,来为开发构建生成不同的清单。
[[Android applinks assetlinks.json配置指南]]
Android App Links 验证
[验证 Android 应用链接 | Android 开发者 | Android Developers](https://developer.android.com/training/app-links/verify-android-applinks) |
AppLinks 生效条件
- AndroidM 及以上支持;不需要 APP,
targetSdkVersion=M
,只需要 AndroidM 手机即可 - AppLinks 本质上是一种特殊的 DeepLinks,也就是说,在不支持的系统上,它会跟 DeepLinks 一样,作为一个可处理某一类请求的处理器,显示在系统的弹出列表里。
App links 系统验证的过程
你的 app links 的 intent-filter 配置:
1
2
3
4
5
6
7
8
9
<activity ...>
<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" android:host="www.example.com" />
<data android:scheme="https" />
</intent-filter>
</activity>
当 android:autoVerify="true"
出现在你任意一个 intent filter 里,在 Android 6.0 及以上的系统上安装应用的时候,会触发系统对 APP 里和 URL 有关的每一个域名的验证。验证过程设计以下步骤:
- 系统会检查所有包含以下特征的 intent filter:Action 为
android.intent.action.VIEW
、Category 为android.intent.category.BROWSABLE
和android.intent.category.DEFAULT
、Data scheme 为http或https
- 对于在上述 intent filter 里找到的每一个唯一的域名,Android 系统会到对应的域名下查找数字资产文件,地址是:
https://域名/.well-known/assetlinks.json
只有当系统为 AndroidManifest 里找到的每一个域名找到对应的数字资产文件,系统才会把你的应用设置为特定链接的默认处理器。
在你安装 APP 后,会请求这个来验证,抓包看
方式一:请求 googleapis
来验证
https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourhost&relation=delegate_permission/common.handle_all_urls>
如:https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://app.xxx.com&relation=delegate_permission/common.handle_all_urls>
出现类似:
方式二:Statement List Generator and Tester 工具
Test statement
当实现 APP Links 特性时,你需要测试链接功能是否可用,以保证系统能够将你的 APP 和网站关联起来,并如预期地处理 URL 请求。
- Hosting site domain 域名,不要带 path
- App package name applicationId
- App package fingerprint (SHA256) 签名
Generate statement
Generate statement:帮我们生成 assetlinks.json
文件
方式三:直接访问配置了 assetlinks.json 的域名
如:https://app.xxx.tw/.well-known/assetlinks.json
但有的是在浏览器中访问不了的,这种就可以用前面的方式来验证
https://app.xxx.com/.well-known/assetlinks.json
如何出现这种,就是运维把证书配置错了:
方式四:安装到设备看 log
将应用安装到设备上,等待 20 秒左右,进行验证
在应用安装后,等待至少 20 秒的时间,以便系统验证完毕。但是,上文只告诉我们如何获得应用是否是特定域名的默认处理器的配置信息,系统的这个验证过程是否发起,是否执行完毕,验证结果如何,我们是无从得知的。
验证成功会有类似日志信息:
1
2
2020-09-27 11:57:48.216 10414-27720/? I/IntentFilterIntentOp: Verifying IntentFilter. verificationId:32 scheme:"https" hosts:"mashi-alternate.test-app.link mashi.test-app.link" package:"club.jinmei.mgvoice". [CONTEXT service_id=244 ]
2020-09-27 11:58:20.534 10414-27720/? I/IntentFilterIntentOp: Verification 32 complete. Success:false. Failed hosts:mashi-alternate.test-app.link,mashi.test-app.link. [CONTEXT service_id=244 ]
这种方式不一样好使
方式五:获取设备上所有应用的链接处理策略命令
输出当前手机所有 app links 的包名
1
adb shell dumpsys package domain-preferred-apps
1
adb shell dumpsys package d
方式六:Android12 及以上命令验证 (推荐)
从 Android12 开始,可以手动调用 app links 域名验证,不管你的 APP 的 targetSdkVersion
是否是 Android12 了。
- 设置支持 app links 域名校验
- 如果你的应用的
targetSdkVersion
已经是 Android12,那么自动开启验证 - 如果你的应用的 targetSdkVersion 小于 Android12,需要手动开启:
adb shell am compat enable 175408749 PACKAGE_NAME
- 如果你的应用的
- 重置设备上 App Links 的状态
adb shell pm set-app-links –package PACKAGE_NAME 0 all 如: adb shell pm set-app-links –package com.zzz 0 all
- 调用 app links 域名校验
adb shell pm verify-app-links –re-verify PACKAGE_NAME 如:adb shell pm verify-app-links –re-verify com.zzz
- 查看校验结果
adb shell pm get-app-links PACKAGE_NAME 如:adb shell pm get-app-links com.zzz
用浏览器/备忘录/Pushbullet 测试
浏览器:
- 浏览器唤起时,只有在 chrome 或者基于 chrome 的浏览器才可以,市面上大部分都不支持,例如小米自带的浏览器,qq 浏览器,百度浏览器,三星浏览器等都不支持。
- 总的来说在国内意义不大,像微信内置浏览器拦截了系统的 deeplink 和 applink,自己维护了一套 (https://wiki.open.qq.com/index.php?title=mobile/%E5%BA%94%E7%94%A8%E5%AE%9D%E5%BE%AE%E4%B8%8B%E8%BD%BD),需要申请白名单才行。
备忘录:
- 需要放到备忘录来测试,有的浏览器是不支持直接跳转的,还会弹个窗二次确认。https://xxx.tw/onelink
如果未验证成功或者集成不对,还是弹窗来选择了
Pushbullet
- 利用 pushbullet 在电脑和手机端传输链接,点击其中的 applinks 也是可以跳转的
adb+URL Intent 测试
1
2
3
4
5
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "http://你的域名:可选的端口"
# 如
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://app.xxx.com"
在 Android 手机上的 Chrome 浏览器、猎豹浏览器、UC 浏览器里直接输入网址来验证,均无法触发系统的 AppLinks 特性。推测浏览器内发起的跳转,会被浏览器直接拦截处理了,因此无法进入系统的分发处理 Intent 流程,故没触发 AppLinks 特性。
对 ink 域名不太友善
在测试中发现,国内各大厂商对 .ink 域名不太友善,很多的是被支持了 .com 域名,但是不支持 .ink 域名。
机
机型 | 版本 | 是否识别 ink | 是否识别 com |
---|---|---|---|
小米 | MI6 Android 8.0 MIUI 9.5 | 否 | 是 |
小米 | MI5 Android 7.0 MIUI 9.5 | 否 | 是 |
魅族 | PRO 7 Android 7.0 Flyme 6.1.3.1A | 否 | 是 |
三星 | S8 Android 7.0 | 是,弹框 | 是 |
华为 | HonorV10 Android 8.0 EMUI 8.0 | 是 | 是 |
oppo | R11s Android 7.1.1 ColorOS 3.2 | 是 | 是 |
oppo | A59s Android 5.1 ColorOS 3.0 | 是,不能跳转到 app | 是,不能跳转到 app |
vivo | X6Plus A Android 5.0.2 Funtouch OS_2.5 | 否 | 是 |
vivo | 767 Android 6.0 Funtouch OS_2.6 | 是,不能跳转到 app | 是,不能跳转到 app |
vivo | X9 Android 7.1.1 Funtouch OS_3.1 | 是,不能跳转到 app | 是,不能跳转到 app |
数据来源网络,未验证真实
App links 不生效问题排查思路
1、检查 intent-filter 是否配置对了?
- 是否配置了
android:autoVerify="true"
属性 - scheme/host/path 是否都配置对了,如只配置了 https,那么 http 就会不支持
- host 和 path 混合配置,如果有的只需要配置 host,有的是 host/path,这种就需要分开 2 个 intent-filter 配置
2、检查 assetlinks.json
配置是否正常(配置不对找运维)
- 直接访问看是否正常,如 assetlinks.json
- Statement List Generator and Tester 工具(需要包名、证书和域名)
- 直接访问 GoogleApi,如 digitalassetlinks.googleapis.com/ 传入自己的包名(app 首次安装时会访问该接口去校验)
3、查看 APP 是否校验成功(APP 首次安装会去校验)
- ADB 命令校验:
adb shell pm get-app-links 应用applicationId
,出现verified
就是校验成功,1024
或none
就是未成功 - 如果未校验成功,一般是网络问题,检查是否开启了抓包工具的代理,需要关闭代理,如 Charles
- 要校验成功需要科学上网
4、APP 的签名和 applinks assetlinks.json 是否一致?
- 检查 APP 的签名和 applinks assetlinks.json 是否一致?
- 特别是 aab 的包的签名(如果打的测试的 aab 包用的是测试签名就不行,会校验不通过,applinks 跳转不了)
5、直接注入 url 和点击 url 跳转
如果是直接输入 URL 访问,也是拉不起 APP 的
浏览器中 “ 输入 URL 访问 “ 和 “ 点击链接访问 “ 的区别
这两种互动方式的主要区别在于用户行为的触发方式,也会影响 applinks
的处理逻辑。具体如下:
1. 输入 URL 访问
- 行为: 用户直接在浏览器地址栏中手动输入一个
applinks
支持的链接(如https://example.com/path
),再按下回车键访问。 - 实现逻辑:
- 浏览器会将这个操作视为普通的 HTTP/HTTPS 请求,并试图加载该 URL 对应的网页。
- 如果目标 URL 支持 Universal Links(iOS)或 App Links(Android),操作系统可能会尝试将这一请求重定向到支持该链接的 App。
- 但在某些情况下,系统可能默认优先加载网页版本,而非直接跳转到 App。
- 常见结果:
- iOS:不一定触发 Universal Links,可能直接访问网页,特别是低版本操作系统或未正确配置时。
- Android:如果 App Links 正常配置,并且用户未禁用相关选项,可能会直接跳转到 App。
- 特殊行为: 某些浏览器(如 Safari)对直接输入链接的唤起行为限制较多,Universal Links 的触发条件可能不会完全生效。
2. 点击链接访问
- 行为: 用户从网页、电子邮件、短信、社交软件、文档等内容中点击一个带有
applinks
的链接。 - 实现逻辑:
- 系统会分析点击的链接所属的域名是否启用了 Universal Links 或 App Links,并检查该链接是否绑定到一个特定的应用。
- 如果配置正确,系统会判断是否直接跳转到该链接对应的 App 页面,或提示用户选择打开路径。
- 默认行为:
- 如果 App 支持
applinks
并且已安装,链接会直接打开 App 中的对应页面。 - 如果 App 未安装或链接未正确配置,会在默认浏览器中加载网页版本。
- 如果 App 支持
- 常见结果:
- iOS:系统会通过
apple-app-site-association
文件判断是否要触发 App 的 Universal Link 功能,通常点击链接会更容易触发跳转。 - Android:类似,系统会检查
assetlinks.json
文件并判断 App Links 是否已匹配。
- iOS:系统会通过
具体行为上的技术区别
行为触发方式 | 请求对象 | 是否直接跳转到 App |
---|---|---|
手动输入 URL 访问 | 直接发送 HTTP/HTTPS 请求,访问网页为主 | 通常不会直接跳转到 App,系统默认优先网页加载。 |
点击链接访问 | 浏览器会递交该 URL,由 OS 或目标 App 进行处理 | 如果支持 Universal Links 或 App Links,支持直接跳转到 App。 |
这两者的技术差异归因于以下几个关键因素:
1. 操作系统对链接的处理优先级
- iOS:
- Universal Links 的触发较为依赖点击行为。手动输入 URL 时,系统很少触发 App 的识别机制,多数情况下会直接加载网页。
- 当点击链接时,系统先检查当前链接是否被声明为 Universal Link,如果是,则优先将用户导向 App。
- Android:
- Android 的处理机制对于点击操作更友好。当点击链接时,系统会查询
assetlinks.json
的配置,并优先提示用户选择打开 App。如果是手动输入,则通常直接加载网页。
- Android 的处理机制对于点击操作更友好。当点击链接时,系统会查询
2. 浏览器的处理逻辑
不同的浏览器对直接输入 URL 和点击链接的处理方式也可能不同:
- Safari(iOS 默认浏览器):
- 对直接输入的链接,主要作为纯 HTTP 访问请求,常常绕过 App 注册的 Universal Links。
- 对点击的 Universal Links,优先判断是否触发 App 的跳转逻辑。
- Chrome:
- 更倾向于直接读取
assetlinks.json
或apple-app-site-association
文件,点击和输入触发机制基本一致,只要匹配成功就会提示跳转。 - 但在匿名用户模式下,跳转行为可能受到限制。
- 更倾向于直接读取
WebView 对 applinks
跳转的影响
- Chrome 浏览器,
Google Applinks
可以正常跳转 - 三方 APP 自定义的 WebView ,取决于三方 APP 放不放行该链接,如果放行是可以通过 applinks 唤起 APP 的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class CustomWebViewActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化 WebView
webView = findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true // 启用 JavaScript
webView.settings.domStorageEnabled = true // 启用 DOM 存储
// 自定义 WebViewClient
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val url = request.url.toString()
// 检查是否是 App Links
if (isAppLink(url)) {
// 跳转到相关 App
openAppLinkIntent(url)
return true // 已处理,不再加载 URL
}
// 默认行为:继续加载 URL
return false
}
}
// 加载初始网页
webView.loadUrl("https://example.com")
}
/**
* 判断 URL 是否为 App Links(匹配规则可以定制)
*/
private fun isAppLink(url: String): Boolean {
return url.contains("example.com/deeplink") // 检查链接是否符合 App Links 定义规则
}
/**
* 使用 Intent 打开 App Links
*/
private fun openAppLinkIntent(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent) // 调用目标 App
} catch (e: Exception) {
e.printStackTrace()
// 处理未安装目标 App 的情况(例如引导用户下载)
}
}
}
Android App Links 核心原理
应用安装的时候根据 activity
标签中的 data
的内容到这个网页下面去获取对应域名下的 assetlinks.json
进行校验,如果符合条件则把 这个 url 保存在本地,当点击 webview 或者短信里面的 url 的时候,系统会自动与本地库中的域名相匹配, 如果匹配失败则会被自动认为是 deeplink 的连接。
DeepLinks 和 AppLinks 区别?
- Deep Links 是一种允许用户进入应用某个特定 Activity 的 intent filter。点击这类链接时,系统可能会弹出一个选择列表,让用户在一堆能够处理这类链接的应用里 (包括你的) 选择一个来处理该链接。
例如:用户点击了一个地图相关的链接,系统弹出一个选择列表,让用户选择是要使用地图应用来处理,还是使用 Chrome 浏览器来处理。
- App Links 是一种基于你的网站地址且验证通过的 Deep Links。因此,点击一个这样的链接会直接打开你的应用 (如果已经安装),系统将不会弹出选择列表。当然,后续用户可以更改配好设置,来指定由哪个应用程序处理这类链接。
下面这个列表描述更多差异:
Deep Links | App Links | |
---|---|---|
Intent URL Scheme | https, http,或者自定义 | 需为 http 或 https |
Intent Action | 任意 Action | 需为 android.intent.action.VIEW |
Intent Category | 任意 Category | 需为 android.intent.category.BROWSABLE 和 android.intent.category.DEFAULT |
链接验证 | 不需要 | 需要在网站上放置一个数字资产链接,并能够通过 HTTPS 访问 |
用户体验 | 可能会弹出一个选择列表给用户选择用哪个应用处理连接 | 没有弹框,系统直接打开你的应用处理网站连接 |
兼容性 | 所有 Android 版本 | Android 6.0 及以上 |
其他 App Links 技术
Universal Link
Universal Link 是 Apple 在 iOS 9 推出的一种能够方便的通过传统 HTTPS 链接来启动 APP 的功能。可以无缝重定向到对应的 APP,且不需要通过 Safari 浏览器。如果你的应用不支持的话,则会在 Safari 中打开该链接。