Android进阶之路 - 从 URL Scheme 到 Deep Link 与 App Link
看很多人分享了 Deep Link 、App Links 的官方文档,发现挺多都看不了,大家有兴趣的话也可以直接前往 官方首页,国内适用 查询相关文档
Deep Link、Deep Links、App Link、App Links 有没有什么区别?其实区别不大,只是一个单复数概念,因为它们的配置项可兼容场景较多,所以自行根据场景定义就好
关联篇
- Android进阶之路 - 从 URL Scheme 到 Deep Link 与 App Link
- Android实战进阶 - 通过Scheme和App Link响应外部调用
快速度过认生期
- Scheme 基础
- Deep Link 、App Link
- 基础共性
- Deep Links
- App Link
- 三方唤起App方式
- 浏览器调用
- 原生调用
在官方文档中看到这样一张图,大家可以简单看一下它们之间的关系
你遇到了什么业务需求(应用场景)才来了解 Deep Link 、App Link
? 如果想要支持以下类似场景,那么就需要我们App予以支持
通过AI直接搜的应用场景,我就不画蛇添足了
- 在抖音、微博或朋友圈看到了一个广告,点击后不是跳转到浏览器,而是直接打开对应App的商品详情页、活动页或视频页
- 从App A(如邮箱客户端)点击一个特定链接,直接跳转到App B(如你的应用)的某个页面。或者在你公司旗下的多个App之间相互跳转
- 向沉默用户推送一条PUSH消息,内容是其收藏商品降价了。用户点击通知栏消息,直接跳转到App内的商品页
- 线下海报、商品包装上印有一个二维码。用户用手机相机或扫码工具扫描后,直接打开App对应页面(如领取优惠券、查看产品信息)
社交媒体营销与广告投放 - 给用户发送一条营销短信,里面包含一个链接。用户点击后,如果安装了App则直接打开,否则跳转到应用市场或备用网页
- 用户在Google搜索结果、Gmail邮件或Chrome浏览器中点击一个属于你App的链接(如https://www.myapp.com/product/123)。系统会直接打开你的App,而不会弹出选择器询问用户(App Link 特性)
- 你拥有一个网站 www.myapp.com 和一个对应的Android App。你希望确保所有指向你网站的链接,在用户安装了App的情况下,都由你的App来处理,而不是浏览器。(App Link 特性)
- 用户经常通过搜索引擎访问你的移动版网站。当用户安装了App后,所有来自搜索、社交分享的你网站的链接,都会直接在你的App中打开,从而将Web流量转化为App的活跃度(App Link 特性)
Scheme 基础
不论是
Deep Link
还是App Link
都涉及到了schame
基础,所以整体都梳理一次,统一下我自己的答疑解惑
我们以 https://blog.csdn.net/qq_20451879
来分析下Scheme组成
- Scheme(协议):常见的有 http、https协议;(此处: https 协议)
- Host(主机、域名):三方的域名基本不同,一般属于类似网络身份证;(此处:blog.csdn.net)
- Path(路径):在很多场景中我们需要定义到具体页面(目标页面),这里一般都是通过path来进行定位的;(此处:qq_20451879)
- Query(参数):传递数据的键值对(可以参考
https://blog.csdn.net/qq_20451879/article/details/151584530?spm=1001.2014.3001.5501
,其中?spm=1001.2014.3001.5501
就是所携带的参数) - Port(端口):默认端口可不做特殊声明,一般不用关注
<activityandroid:name=".SchemeActivity"android:theme="@style/AppTheme"><!--要想在别的App上能成功调起App,必须添加intent过滤器--><intent-filter><!--协议部分,随便设置--><dataandroid:scheme="https" android:host="blog.csdn.net" android:path="/qq_20451879" /><!--通用设置,主要是支持游览器、新开界面、以及一些默认配置--><category android:name="android.intent.category.DEFAULT"/><action android:name="android.intent.action.VIEW"/><category android:name="android.intent.category.BROWSABLE"/></intent-filter></activity>
Deep Link 、App Link
Deep Link
、App Link
更适用于 Android 端,之前看别人Blog下的评论,如果公司要走android、ios、web
等多个平台,大多会使用三方框架,其中提到了 Firebase Dynamic Link 框架,有兴趣的可以去了解
关于怎么理解俩者之间的关系,很多人有不同的看法,在我看来你可以把它们看做独立的个体,也可以把它们看成历史的迭代
基础共性
之所以将 Deep Link
、App Link
放在一起讲解,个人认为主要是他们就像是新老版本的迭代,有点像 ListView vs RecyclView
历史关系,又有点像父子之间的继承关系,又有点像策略模式的实现方式?
其实在实战开发中,我们通常都会同步配置 Deep Link
、App Link
!而之所以这样做的原因是当 App Link
验证失败的情况下,Deep Link
可进行兜底处理
Deep Link
调起流程
App Link
调起流程
首先通过实现 Deep Link
或 App Link
都可以响应外部调用,不过具体选用哪一个?还有要根据场景来看,我们先从不同维度看一下它们的区别
区别 | Deep Link | App Link |
---|---|---|
适用版本 | Android 全系列版本 | Android 6.0之后 |
适用协议 | 支持自定义scheme | 仅支持 http、https协议 |
安全验证 | 不做验证 | 需要客户端生存安全稳健存储于,验证所有权 |
用户体验 | 通常会弹框,让用户选择打开方式 | 默认直接启动我们目标App(体验更好) |
关于 AndoirdMainfest
中的 action、category
配置项,建议统一加入
<action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" />
那么为什么Android 6.0 特意推出了 App Link
?继续使用 Deep Link
不好吗?意义是什么? 我们继续换个角度来研究下
App Link 优点 | Deep Link 不足 | |
---|---|---|
用户体验 | 支持App直接打开 | 中间多一步选择操作,体验不足 |
安全验证 | 加入了验证机制,更安全 | 不具备该项保护 |
我多次提到了用户体验,直接看图就可以明白 Deep Link
、App Link
俩者调起区别(因为没有写完整demo,篇中直接拿了几张 别人的效果图用一下…)
Deep Link 调起结果
App Link 调起结果
课外学堂
不知道你会不会有以下这种配置方式的想法?通常情况下浏览器都会监听全部的http和https协议的链接,但是App这样做的意义好像并不大
<intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><dataandroid:host="*"android:scheme="http" /><dataandroid:host="*"android:scheme="https" />
</intent-filter>
关于源码部分,我们就先不追了,有兴趣的可以直接去看 Android DEPPLINK、APPLink原理简析
Deep Links
关于Deep Links的官方文档,没找到太多,大家可以先看这个 Create Deep Links to App Content
DeepLink
是 Android系统最基础、最普遍、最广泛的外部唤起App的方式,不受系统版本限制,支持当用户点击一个链接时,系统默认按以下顺序打开:
- 如果你设置了默认打开应用,则优先使用该应用打开
- 如果只有一个应用能打开,则直接用该应用打开
- 如果有多个应用能打开,才会弹出应用选择框
Deep Link 支持自定义scheme
协议,类似以下配置
<!-- DeepLink适用所有版本,支持自定义协议 --><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><!-- 自定义协议 --><dataandroid:host="l.nian.com"android:scheme="ly" /><dataandroid:host="l.nian.com"android:scheme="custom" /><!--常见协议 --><dataandroid:host="l.nian.com"android:scheme="http" /><dataandroid:host="l.nian.com"android:scheme="https" /></intent-filter>
App Link
Android App Links 是指可将用户直接带往 Android 应用内特定内容的 HTTP 网址。帮助您发现最常用的应用内容,并让用户更轻松地在已安装的应用中查找和分享内容。
Tip
:关于 App Link
的部分解析,摘自官方文档;如有疑问,可直接前往 添加 Android App Links 、验证 Android 应用链接
Android 应用链接是一种特殊类型的深层链接,可让您的网站网址直接在您的 Android 应用中打开相应内容,无需用户选择应用。
Android 应用链接使用 Digital Asset Links API
来建立信任关系,表明您的应用已获得网站的批准,可以自动打开相应网域的链接。如果系统成功验证您是网址所有者,则会自动将这些网址 intent 路由到您的应用。
个人经验,以下几点 AndroidMainfest
中 Link 类 配置需要注意
- 设置
exported="true
" ,需要支持被外部调用 - App Links 设置
android:autoVerify="true"
,安全校验 - App Links
仅支持 http、https
协议
若要添加对 Android App Links
的支持,请执行以下操作:
- :在清单中创建 intent 过滤器
关于这一点,其实俩种实现方式,当然最后结果是一样的
第一种:通过 Android Studio > Tools > App Links Assistant
生成 相应的 intent-filter
信息(关于这种方式,如果以后有机会的话,我会记一下,因为目前项目成型,缺少整体实战,所以相应延后…)
生成的类似结果(如果设置了 host
之类,应该会更详细一些,这里抄自)
<!-- Make sure you explicitly set android:autoVerify to "true". -->
<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" /><!-- If a user clicks on a shared link that uses the "http" scheme, yourapp should be able to delegate that traffic to "https". --><!-- Do not include other schemes. --><data android:scheme="http" /><data android:scheme="https" /><!-- Include one or more domains that should be verified. --><data android:host="..." />
</intent-filter>
第二种:直接开卷抄一下,改一改,用起来(比较建议)
- :将代码添加到应用的 activity 中以处理传入链接
最终结果(实战篇),类似如下
<activityandroid:name=".SchemeActivity"android:exported="true" android:theme="@style/SplashTheme"><!-- DeepLink适用所有版本,支持自定义协议 --><intent-filter><dataandroid:host="l.nian.com"android:scheme="ly" /><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 (autoVerify必加,安全验证)适用于6.0之后--><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" /><!-- App Links 支持仅http、https协议,一般都会做兼容处理--><dataandroid:host="l.nian.com"android:scheme="http" /><dataandroid:host="l.nian.com"android:scheme="https" /></intent-filter></activity>
在址映射正常运行后,请添加逻辑以处理您创建的 intent(这其实我们接收到三方调用后的处理逻辑了)
- 点击
App Links Assistant
中的Select Activity
- 从列表中选择一个 activity,然后点击
Insert Code
根据上方操作生成的伪代码
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)...// ATTENTION: This was auto-generated to handle app links.val appLinkIntent: Intent = intentval appLinkAction: String? = appLinkIntent.actionval appLinkData: Uri? = appLinkIntent.data...
}
稍微正常点的伪代码,需要自己根据业务做逻辑处理
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.actionval appLinkData: Uri? = intent.dataif (Intent.ACTION_VIEW == appLinkAction) {appLinkData?.lastPathSegment?.also { recipeId ->Uri.parse("content://com.recipe_app/recipe/").buildUpon().appendPath(recipeId).build().also { appData ->showRecipe(appData)}}}
}
- :使用 Digital Asset Links 将应用与网站相关联
在此之前的操作,都可以算是基础配置,重要的还是要 将应用与网站相关联
在为您的应用设置网址支持后,App Links Assistant
会生成一个 Digital Asset Links
文件,您可以使用该文件将网站与应用相关联。
提示 - 国内可忽略:除了使用
Digital Asset Links
文件,您还可以在Search Console
中将您的网站与应用相关联(GSC,谷歌搜索控制台)
必须在网站上发布 Digital Asset Links
JSON 文件,以指示与网站相关联的 Android 应用并验证应用的网址 intent ,发布 JSON 验证文件有以下几点需要注意
- 使用内容类型
application/json
发布assetlinks.json
文件。 - 无论应用的 intent 过滤器是否将数据架构声明为 HTTPS,都必须可通过 HTTPS 连接访问
assetlinks.json
文件。 assetlinks.json
文件必须可直接访问,没有任何重定向(无 301 或 302 重定向)。- 如果您的应用链接支持多个主机网域,您必须在每个网域上分别发布
assetlinks.json
文件。请参阅支持多个主机的应用链接。 - 请勿发布清单文件中的开发/测试网址无法供公众访问的应用(例如,任何只可通过 VPN 访问的应用)。作为这种情况下的应急措施,您可以配置 build 变体以针对开发 build 生成不同的清单文件
assetlinks.json 示例
[{"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
文件内声明与多个应用的关联性。以下文件清单展示了一个声明示例文件,该文件分别声明了与两个应用的关联,并且位于 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"]}
}]
不同的应用可以处理同一网络主机下的不同资源对应的链接。例如,应用 1 可能声明了一个对应于 https://example.com/articles
的 intent 过滤器,而应用 2 可能声明了一个对应于 https://example.com/videos 的 intent
过滤器。
- 将多个网站与同一应用相关联
多个网站可在各自的assetlinks.json
文件中声明与同一应用的关联性。以下文件列表示例展示了如何声明 example.com 和 example.net 与 app1 的关联性。第一个列表展示了 example.com 与 app1 的关联性:
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"]}
}]
下一个列表展示了 example.net 与 app1 的关联性。唯一的区别是托管这些文件的位置不同(.com 和 .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"]}
}]
Android App Links
验证
当应用的至少一个 intent 过滤器中存在 android:autoVerify="true"
时,在搭载 Android 6.0(API 级别 23)或更高版本的设备上安装应用会导致系统自动验证与应用 intent 过滤器中的网址相关联的主机
。在 Android 12 及更高版本中,您还可以手动调用验证流程来测试验证逻辑(手动验证不如直接用Android Studio提供的工具,有机会再讲,主要看自动验证)
系统的自动验证涉及以下方面:
- 系统会检查所有包含以下任意项的 intent 过滤器
- 操作:android.intent.action.VIEW
- 类别:android.intent.category.BROWSABLE 和 android.intent.category.DEFAULT
- 数据架构:http 或 https
- 对于在上述 intent 过滤器中找到的每个唯一主机名,Android 会在
https://hostname/.well-known/assetlinks.json
查询Digital Asset Links
文件的相应网站。
三方唤起App方式
这里指的主要是在不同场景下,三方
如何调用我们写的 Deep Link
、App Link
浏览器调用
如果采用的是 Deep Link
的自定义scheme,可能会导致部分浏览器不支持,同时存在信息泄漏安全风险
<a href="https://blog.csdn.net/qq_20451879">Blog主页</a>
原生调用
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("https://blog.csdn.net/qq_20451879"));startActivity(intent);
在实际使用中,或许你会用到以下几个小方法
- 关于 App 获取三方传值的方式都基本类似,可以先参考以下伪代码
val data:Uri? = intent.data // https://yuwen.ggl.cn/app?a=123&b='ggl'
val scheme:String? = data?.scheme // https
val host:String? = data?.host // yuwen.ggl.cn
val path:String? = data?.path // /app
val valueA:String? = data?.getQueryParameter("a") // 123
val valueB:String? = data?.getQueryParameter("b") // 'ggl'
- 获取Scheme跳转的参数(Java版本)
Uri uri = getIntent().getData();
if (uri != null) {// 完整的url信息String url = uri.toString();Log.e(TAG, "url: " + uri);// scheme部分String scheme = uri.getScheme();Log.e(TAG, "scheme: " + scheme);// host部分String host = uri.getHost();Log.e(TAG, "host: " + host);//port部分int port = uri.getPort();Log.e(TAG, "host: " + port);// 访问路劲String path = uri.getPath();Log.e(TAG, "path: " + path);List<String> pathSegments = uri.getPathSegments();// Query部分String query = uri.getQuery();Log.e(TAG, "query: " + query);//获取指定参数值String goodsId = uri.getQueryParameter("goodsId");Log.e(TAG, "goodsId: " + goodsId);
}
- 如何判断一个Scheme是否有效
PackageManager packageManager = getPackageManager();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://blog.csdn.net/qq_20451879"));
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isValid = !activities.isEmpty();
if (isValid) {startActivity(intent);
}