@SR1s
2017-09-09T22:34:23.000000Z
字数 13494
阅读 4656
AppLinks
以下内容分两部分。
第一部分译自官网文档,主要为App Links的接入指南;第二部分为笔者的接入实践,总结了接入过程遇到的问题,以及对App Links的使用上的思考。
以上内容,仅供个人学习、记录、参考使用,如有纰漏,还请留言指正。
Android App Links是一种特殊的Deep Links,它使Android系统能够直接通过网站地址打开应用程序对应的内容页面,而不需要用户选择使用哪个应用来处理网站地址。
要添加Android App Links到应用中,需要在应用里定义通过Http(s)地址打开应用的intent filter,并验证你确实拥有该应用和该网站。如果系统成功验证到你拥有该网站,那么系统会直接把URL对应的intent路由到你的应用。
为了验证你对应用和网站的所有权,以下两个步骤是必须的:
1. 在AndroidManifest里要求系统自动进行App Links的所有权验证。这个配置会告诉Android系统去验证你的应用是否属于在intent filter内指定的URL域名。
2. 在以下链接地址里,放置一个数字资产链接的Json文件,声明你的网址和应用之间的关系:
https://domain.name/.well-known/assetlinks.json
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及以上 |
为了让系统为你的应用进行链接验证,需要在AndroidManifest中,在任意一个网站链接intent filter里(包含了android.intent.action.VIEW
的Intent Action和android.intent.category.BROWSABLE
的Intent Category),设置android:autoVerify="true"
。如下面的片段所示:
<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有关的每一个域名的验证。验证过程设计以下步骤:
1. 系统会检查所有包含以下特征的intent filter:Action为 android.intent.action.VIEW
、Category为android.intent.category.BROWSABLE
和android.intent.category.DEFAULT
、Data scheme为http
或https
2. 对于在上述intent filter里找到的每一个唯一的域名,Android系统会到对应的域名下查找数字资产文件,地址是:https://域名/.well-known/assetlinks.json
只有当系统为AndroidManifest里找到的每一个域名找到对应的数字资产文件,系统才会把你的应用设置为特定链接的默认处理器。
在intent filter中的data元素中定义的每一个域名,系统都需要能够验证它的数字资产声明文件,即每一个域名都需要放置一个对应的数字资产声明文件。如果验证过程中有任何一个域名验证失败了,那么系统不会把应用设为应用内声明的任何一种链接模式的默认处理器。系统会用原来处理Deep Links的方式处理这些链接。
举例来说,一个定义了下面的intent filter的应用,在验证过程中,由于assetlinks.json
文件在https://www.example.com/.well-known/assetlinks.json
和https://www.example.net/.well-known/assetlinks.json
下都没找到。
<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="http" android:host="www.example.com" />
<data android:scheme="https" />
</intent-filter>
</activity>
<activity android:name=”SecondActivity”>
<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" android:host="www.example.net" />
</intent-filter>
</activity>
</application>
注意,同一个intent filter内所有的<data>
元素会合并在一起,以解释他们所有组合属性的变量。例如:上述第一个intent filter仅仅定义了一个HTTPS Scheme。但这个Scheme和其他的<data>
元素结合在一起,因此这个intent filter既支持http://www.example.com
,也支持https://www.example.com
。同样的,为了定义不同的URI的Scheme和域名的组合,你需要创建不同的intent filter。
数字资产链接协议把你intent filter里的子域名看做唯一的、独立的域名。因此,如果你的intent filter列出一个域名及其多个子域名,你需要在每个子域名上发布一个有效的assetlinks.json
文件。举例来说,下面的intent filter包含了www.example.com
和mobile.example.com
作为期望处理的intent的URL的主机名。因此,有效的assetlinks.json
必须同时发布在https://www.example.com/.well-known/assetlinks.json
和https://mobile.example.com/.well-known/assetlinks.json
。
<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
)将会通过验证。
<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>
数字资产证明文件必须发布在你的网站上,以证明你的应用是和网站关联在一起的,并且用于验证应用内的URL链接模式。这个JSON文件用下面几个字段来标识关联的应用:
$ keytool -list -v -keystore my-release-key.keystore
下面这个示例assetlinks.json
授予链接打开权限给com.example
应用:
[{
"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
。
[{
"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
文件里声明关联同一个应用。下述文件展示了如何关联example.com和example.net到app1。第一个文件展示了关联app1到example.com:
https://www.example.com/.well-known/assetlinks.json
[{
"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。两者的差异只有放置的地方不同。
https://www.example.net/.well-known/assetlinks.json
[{
"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验证文件发布在下面的位置:
https://你的域名/.well-known/assetlinks.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)。一种可行的做法是配置构建类型,来为开发构建生成不同的清单。
当实现APP Links特性时,你需要测试链接功能是否可用,以保证系统能够将你的APP和网站关联起来,并如预期地处理URL请求。
要测试一个已存在的声明文件,你可以使用声明列表生成器和测试器工具
进行测试时,你需要确认你的应用里关联的需要系统进行验证的域名列表。确保列表里的域名对应的intent filter包含以下属性和元素:
- android:scheme
属性值为http
或https
- android:host
属性为一个域名URL模式
- 包含android.intent.action.VIEW
Category
- 包含android.intent.category.BROWSABLE
Category
同样的,确保列表里的每一个域名及相应的子域名下有数字资产证明链接的JSON文件存在。
对每个域名,使用数字资产链接API来确认数字资产证明文件被正确地定义和发布在互联网上:
https://digitalassetlinks.googleapis.com/v1/statements:list?
source.web.site=https://你的域名:可选的端口&
relation=delegate_permission/common.handle_all_urls
当你确认了你声明的网站列表和你的应用之间的关联,同时也确认了发布的JSON文件是有效的,将应用安装到设备上。等待约20s,以便系统发起的异步验证任务执行完毕。用下面这个命令来检查系统是否验证了你的应用并设置了正确的链接处理策略:
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "http://你的域名:可选的端口"
作为你的测试的一部分,你可以当前系统的链接处理设置。用下面的命令来获取设备上所有应用的链接处理策略:
adb shell dumpsys package domain-preferred-apps
下面这个命令也是同样的作用:
adb shell dumpsys package d
注意:为了让系统能完成验证过程,确保你在安装应用之后等待了至少20秒
命令返回了设备上每一个用户配置的列表,以如下格式的头部信息开头:
App linkages for user 0:
在头部信息后面的输出以如下的格式输出链接处理配置信息:
Package: com.android.vending
Domains: play.google.com market.android.com
Status: always : 200000002
这个列表表示了哪个应用和哪个网站之间的关联:
- Package
:标识应用的包名
- Domains
:列出应用所处理的所有网站域名列表,以空格作为分隔符
- Status
:表示应用当前的链接处理策略。通过了验证的应用,且它的Manifest文件里包含android:autoVerify="true"
,它的状态为always
。跟在状态之后的十六进制数字和系统的应用链接配置记录有关。这个数值和验证是否成功无关。
注意:If a user changes the app link settings for an app before verification is complete, you may see a false positive for a successful verification, even though verification has failed.This verification failure, however, does not matter if the user explicitly enabled the app to open supported links without asking. This is because user preferences take precedence over programmatic verification (or lack of it). As a result, the link goes directly to your app, without showing a dialog, just as if verification had succeeded.
为了App Links能验证成功,系统必须能够验证你定义在intent filter里的所有网站,当然这些定义需要符合App Links的标准。下面这是示例演示了一个定义了几个App Links的Manifest配置。
<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>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.example2.com" />
</intent-filter>
</activity>
<activity android:name=”SecondActivity”>
<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" android:host="account.example.com" />
</intent-filter>
</activity>
<activity android:name=”ThirdActivity”>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" android:host="map.example.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="market" android:host="example.com" />
</intent-filter>
</activity>
</application>
根据上述配置,系统会尝试进行验证的域名有如下几个:
www.example.com
mobile.example.com
www.example2.com
account.example.com
系统不会尝试进行验证的域名有:
map.example.com (没有设置 android.intent.category.BROWSABLE)
market://example.com (不是“http”或“https” scheme)
以上便是官网上有关AppLinks的接入指导。但在笔者接入过程中,仍然碰到了一些问题。下文将记录笔者遇到的问题和解决的方法。
AppLinks的接入验证,依照上文,可分为两步进行:
1. 验证网站上是否将assetlink.json
2. 第一步验证通过后,将应用安装到设备上,进行验证
第一步的验证可使用上文提及的声明列表生成器和测试器工具。笔者在验证过程中发现,此工具确实能有效帮助我们生成,但对于验证环节,错误信息的提示并不怎么友好。
为了获得具体的出错信息,需要我们右键打开Chrome浏览的检查功能,选中Network
标签页,然后再触发一次验证请求。此时,可以在Network
标签页上看到刚刚发出去的验证请求。查看这个请求的返回结果,在errorMessage
里可以看到完整的错误信息。
进行第二步验证,依照上文,需要在应用安装后,等待至少20秒的时间,以便系统验证完毕。但是,上文只告诉我们如何获得应用是否是特定域名的默认处理器的配置信息,系统的这个验证过程是否发起,是否执行完毕,验证结果如何,我们是无从得知的。
笔者在接入过程中,观察系统(Android 7.1.2)日志输出,发现SingleHostAsyncVerifier
这个tag会输出验证相关的log,如下:
09-05 14:10:08.122 1386-26740/? I/SingleHostAsyncVerifier: Verification result: checking for a statement with source a <
a: "https://kg.qq.com"
>
, relation delegate_permission/common.handle_all_urls, and target b <
a: "com.tencent.karaoke"
b <
a: "02:38:85:6C:4B:EF:4A:5D:E1:81:E1:CB:07:B5:EF:02:B2:EC:4E:69:AC:C3:7F:CD:FC:08:8D:40:C7:6B:9C:68"
>
>
--> true.
上述日志输出表示成功验证了这个AppLinks。可以参考本方法,观察系统log来确定是否验证通过。另一种方法是以应用包名作为过滤器来过滤日志,也能发现一些蛛丝马迹。
AppLinks是Android M之后系统支持的特性,因此也只有使用Android M及以上的系统的用户能体验到这个特性。
但要使这个特性生效,是否需要我们应用的目标SDK版本(targetSdkVersion
)也提到Android M+呢?答案是否定的。因此,即时应用还未适配Android M以上的动态权限申请,也可以接入这个特性。
另外,AppLinks本质上是一种特殊的DeepLinks,也就是说,在不支持的系统上,它会跟DeepLinks一样,作为一个可处理某一类请求的处理器,显示在系统的弹出列表里。
要验证AppLinks,可以使用上文提到的adb
命令:
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "http://你的域名:可选的端口"
来发送一个构造好的intent给系统。这种方式适合开发者进行自测。
如果要给他人体验这个效果,那么可以尝试,在记事本里输入可处理的链接,然后在预览模式下,点击该链接,触发AppLinks生效的场景。
另外,笔者尝试在Android手机上的Chrome浏览器、猎豹浏览器、UC浏览器里直接输入网址来验证,均无法触发系统的AppLinks特性。推测浏览器内发起的跳转,会被浏览器直接拦截处理了,因此无法进入系统的分发处理Intent流程,故没触发AppLinks特性。
另外,笔者在调试过程中发现,即便AppLinks特性已经生效了,但通过:
adb shell dumpsys package domain-preferred-apps
和
adb shell dumpsys package d
这两个命令得到的列表里,笔者的应用显示的仍然不是always
的状态。这在笔者调试的过程中,给笔者造成了很大的困扰,导致笔者以为AppLinks的验证过程未通过,导致这个情况。
实际验证过程推荐使用查看系统日志、手动构造发送intent的方式进行验证。
Android Studio 2.3开始,在Tools里集成了App Links Assistant,这个图形化工具能帮助我们完成上述提及的所有步骤。了解了上述细节后,可以使用这个工具,快速集成。
总的来说,App Links赋予了应用直接处理其相关网站连接的能力,使得应用能更方便地触达用户,提供优于Web的体验。同时,通过assetlink这种方式,来确保网站所有者及其对应应用的相关性,避免外部应用恶意拦截处理网站连接的情况。
但在实际使用中发现,这种方式只能用于系统处理URL的intent的情况,若URL本身没有抛给系统处理,而是直接在应用内打开了,那就无法直接拉起应用。
这也是App Links和iOS的Universal Link不一样的地方。App Links是在URL被处理前,转发给应用处理,而Universal Link则是先打开对应的网页,然后再直接跳转到应用。
由于大部分应用,如微博、微信、第三方浏览器(包括Chrome),都不会将URL抛给系统处理,因此App Links生效的情况就很有限了,比如只能从记事本应用、短信应用这些进行跳转。总体来说,实属鸡肋。