
fuhj
2022/12/06阅读:117主题:蓝莹
移动端DeepLink到底是怎么实现的?
一、什么是DeepLink?
在之前的文章《广告系统进化和演变解析》中,曾经写过PC互联网时代,网站和网站之间通过http链接实现网站和网站之间的跳转,搜索引擎也可靠http链接实现内容的索引,用户有特定内容诉求只需要去搜索引擎里输入关键词就能找出他们需要的网站和页面。同样在移动互联网时代每个app都变成了一个个孤岛,应用间无法有效互联,内容也无法被搜索引擎索引和查找,随着移动互联网的发展,出于引流的需要从应用A跳转到应用B变成了必须的功能,移动端的两大平台Android和iOS分别实现了DeepLink功能可以实现在各自平台内应用间实现相互的跳转。

Deeplink的定义
移动端使用统一资源定位符(URI)链接到应用中特定的位置,在应用App中打开时定位到特定的位置界面,而不是简单的打开App首页。

Deferred Deeplink(延迟深度链接)
当用户手机中尚未安装目标App的情况下,为用户打开一个web页面,并引导用户下载应用,在用户安装App后重新还原之前预览的页面。

使用场景
DeepLink使用的场景有很多,可以包括:
-
社交分享 -
广告引流 -
Web和App互通 -
裂变分享 -
短信、邮件营销
在Web和App互通的场景下,可以额很方便的从外部流量场景下,回到App内特定的场景和位置(如果给对应位置分配了DeepLink的话),如:
-
电商App:
分享H5后点击链接可以打开app内对应的商品页
-
游戏App:
分享H5后点击链接打开特定方向或者任务
-
直播类App:
分享H5后跳转到特定直播间
DeepLink可以缩短整个操作路径,可以极大减少用户操作成本,降低用户流失成率,帮助App拉新和留存。
二、实现原理
移动App在移动互联网中占主导地位,并且移动app内的资源相对比较封闭,为了实现App之间唯一资源的定位,就需要实现特定的定位符地址打开特定资源的功能。
在Android和iOS中每个想被外部应用打开的App都会有定义一个唯一的Scheme,scheme对应于web端的http和https协议。当App被安装到手机中,应用会向操作系统注册一个Scheme,就如同每个小区都需要向公安局申领门牌号一样,而Scheme对应的URL被调用的时候操作系统就会检索内部是不是有这个门牌号,如果门牌号存在,又会通过URL里的host和path定位到小区的哪栋楼的哪个单元的哪个户号(具体的格式不用care,稍后会详细介绍),最终实现从外部小区来的人能找到对应的家庭。
实现方式
常见的实现方式有以下几种:
-
URL Scheme, iOS和Android下的通用方式,打开前会询问是否打开某app; -
Chrome Intent, Chrome 25+无法通过URL scheme 唤起 App,Chrome内核中必须使用Intent方式进行呼起; -
Universal Link,适用于iOS 9及更高版本,点击http/https即跳转,操作丝滑 -
App Links,适用于Android 6及更高版本,点击http/https即跳转,操作丝滑
APP要想被其他App呼起和打开,自身需要做技术实现支持,让自己具备被打开的能力。同样App想要打开其他的App,自身也需要有技术实现支持,判断Scheme是否被注册,由哪个App注册,app是否安装,特种跳转的处理
* URL Scheme
一般来说完整的URL Scheme形如:
[scheme]://[host]:[port]/[path]?[query]=xxxxxxx
其中path代表了想要跳转的指定页面,而query代表了想要传递的参数,相关的配置在AndroidManifest.xml。
如果相同的Scheme有多个App都注册了,系统会弹出选择框让用户选择,用哪个app打开这个URL。
缺点
本身没有规范,难以获知要跳转的path或query 功能不全,app越复杂,scheme就越复杂 会被拦截,包括浏览器或应用的webview,如微信 打开app失败后,iOS会有错误弹窗,体验不好,提示网页无效 URL scheme可能重复
常见scheme
微信weixin://
(有白名单,从外部应用呼起微信特殊场景需要授权加白,原有熟知的: weixin://dl/scan 扫一扫 weixin://dl/moments 朋友圈 weixin://dl/settings 设置 *都已失效* )
当前有效的仅剩weixin://dl/business/?t= TICKET形式的deeplink,TICKET由服务端生成,每天生成 URL Scheme 和 URL Link 总数量上限为50万,开放范围仅限于非个人主体小程序开放。 实例网页如下:
https://postpay-2g5hm2oxbbb721a4-1258211818.tcloudbaseapp.com/jump-mp.html
移动端浏览器打开此网页后会尝试呼起微信小程序
电商 | 浏览器 | 其他 |
---|---|---|
淘宝:tbopen:// | Chrome:googlechrome:// | 飞书:lark:// |
支付宝:alipay:// | UC浏览器:ucbrowser:// | 微博:sinaweibo:// |
美团:imeituan:// | 手机百度:baiduboxapp:// | 唯品会:vipshop:// |
* Chrome Intent
Chrome 25+无法通过URL scheme 唤起 App,必须使用Intent
格式类似于:
intent: HOST/URI-path // Optional host #Intent; package=\[string\]; action=\[string\]; category=\[string\]; component=\[string\]; scheme=\[string\]; end;
在end前添加S.browser_fallback_url=[encoded_full_url]可以指定唤起失败的地址。一个构造好的intent形如:
intent://path#Intent;scheme=xxx;package=com. xxx;S.browser_fallback_url=https://xxx;end
* Universal Link
适用于iOS 9及更高版本,点击http/https即跳转,操作丝滑,是iOS 9推出的HTTPS链接来启动App的特性,既可以打开app,当没有安装时则打开特定网页,对比URL Scheme的优势在于:
-
无缝切换,不弹窗确认 -
兼容性好,未安装时直接打开网页 -
使用通用的http协议,不担心scheme重复 -
通过网站配置文件和app关联,保证安全
在网站下根目录或.well-known路径下需要有apple-app-association(无后缀名)JSON文件。
例如,知乎的配置文件为 https://oia.zhihu.com/apple-app-site-association
在配置时:
-
需要保证有一个HTTPS的域名,最好和web网页域名区分开,保证web网页的正常访问 -
在开发者中心 ,Identifiers下 AppIDs 找到自己的App ID,编辑打开Associated Domains服务 -
打开工程配置中的 Associated Domains ,在其中的 Domains 中填入你想支持的域名,必须以applinks:为前缀,如:applinks:domain.com -
配置apple-app-site-association文件,不带任何后缀,上传该文件到服务器的根目录或者.well-known目录下
在网站下配置好Universal Link后,用户点击网站链接后,即会直接跳转到App,而不需要经过浏览器。当然,微信等app还是会拦截Universal Link的行为,需要在微信开放平台注册自己的应用id和Universal Link
* App Links
适用于Android 6及更高版本,点击http/https即跳转,操作丝滑
App Links,类似Universal Link,是Android 6(Android M)及以上操作系统中适用的HTTPS路径链接。可以直接将用户带入到Android App内的特定页面中,实现上,网站配置文件名为assetlink.json,只能放在.well-known目录下。如https://vt.tiktok.com/.well-known/assetlinks.json。不过,国产的安卓版本以及微信等app依旧会拦截。
三、调用方式
DeepLink的意义:
-
DeepLink实现了网页直接和App直接跳转。之前手机上的每个App都相当于一个个孤岛,没有办法和广泛的网站实现直接的跳转。现在比如你在浏览微博的时候看到某个App上面有精彩的内容,你就可以直接点击链接跳转到App里面(甚至可以判断如果按照了App就进入App里面,如果不安装那么就进入应用市场的该App下载界面),这样的交互很方便,很好的将App连接到了整个网络世界,以后有个浏览器就能随意的跳转。 -
DeepLink完全可以在搜索中使用,目前的搜索都是搜到了内容还是调网页。以后如果开发者把自己的DeepLink链接提交给搜索公司,那么在搜索到对应的结果的时候就可以直接点击搜到的结果跳转到自己的App了。这个还能应用到广告上去。推广自己的App就更容易了。 -
DeepLink使得大企业的众多App之间相互拉活,相互跳转。假如某公司有个超级App,那么想推广自己的其他App就可以使用DeepLink在开启自己某个子页面的时候,把这个子页面交给其他App进行处理。这样就拉活了自己的其他App了。 -
在DeepLink的基础上,Google又新出了一个AppLinks,AppLinks就是你自己的网站和你自己的App相互关联了。
ADB调用Deeplink
当你需要测试DeepLink时,Android系统可以通过ADB进行调试,具体命令如下:
adb shell am start
-W -a android.intent.action.VIEW
-d <URI> <PACKAGE>
html调用Deeplink
如果不具备ADB调试条件也可以把deeplink放在html代码中<a>标签的href属性中,打开页面,通过点击链接尝试呼起应用 类似于如下代码,实现了deeplink呼起支付宝打开百度首页:
<a href='alipays://platformapi/startapp?appClearTop=false&appId=20000067&startMultApp=YES&url=https%3A%2F%2Fbaidu.com'>alipay-baidu</a>
特殊用法
快应用
快应用支持一下三种deeplink的格式:
http://hapjs.org/app/
https://hapjs.org/app/
hap://app/
快应用内只支持通过 hap 链接打开快应用,http 和 https 链接将被当成 web 页面打开。
package: 应用包名,必选
path: 应用内页面的 path,可选,默认为首页
key-value: 希望传给页面的参数,可选,可以有多个
四、能知道是哪个App调用我的DeepLink吗?
有些情况下流量主会把低价的App B的流量掺在高价的App A的流量中售卖给广告主(恩,对,也就是以次充好),流量主说广告主反馈流量并都是来自高价App A的,问广告主能知道自己掺量了么?是咋知道掺量了的?
先公布答案:广告主如果想知道,是能知道的(至于广告主给不给你结算那就不知道了)。
那能不能伪造?答案是可以伪造。
那伪造能不能被识破?答案也是可以的
为什么,慢慢解释给你听。
如何识别调用deeplink来源?
前面的章节我们已经说了deeplink,存在的意义在于App A开了几个抓手给其他的App X,别的App可以通过这个抓手打开应用的某个场景和内容,这样就能实现从App X点击链接跳转到App A的能力,这就给App间广告营销和流量买卖创造了可能,但同时流量劫持,流量伪造也可以通过这个口子把用户引过来,然后找广告主结算要钱,所以我们需要识别deeplink调用的来源是哪个app。
一般来讲我们可以通过反射获取通过Deeplink调用的Activity中的mReferrer字段来获取跳转来源的包名。代码如下:
/**
* 通过反射获取referrer
* @return
*/
private String reflectGetReferrer() {
try {
Field referrerField =
Activity.class.getDeclaredField("mReferrer");
referrerField.setAccessible(true);
return (String) referrerField.get(this);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "";
}
那mReferrer有没有可能被伪造呢?
在Activity中,序列化对象传输通常是应用binder来实现的,而binder的服务端是在System过程中。这里实现了反序列化,那么在远端的binder服务中肯定有序列化的过程。咱们能够在System过程中调试这个断点,应该就是序列化的过程。
deeplink的来源如何伪造?
mReferrer其实是应用context的getBasePackageName()来实现的,通过重写context的getBasePackageName()被伪造,劫持这个接口的返回值就可以伪造来源了。Deeplink的被调用方在应用mReferrer时肯定要小心。
伪造的包名如何识别?
一旦mReferrer被伪造,轻则业务逻辑出错,重则造成经济损失,针对这种情况有没有比较好的方式获取呢?
相当于mReferrer是发起Deeplink调用的App的PackageName,类似于微信调用了某个App的Deeplink这里获取到的就是com.tencent.mm,但是这个mReferrer是可以通过重写context的getBasePackageName()方法伪造的,劫持这个接口的返回值就可以伪造,避免伪造的方法呢就是ActivityRecord对象还有PID和Uid的属性,拿到这个Uid就可以调用packageManager的相关方法获取Uid对应的真实包名了,这个是没有办法伪造的。 具体获取方法如下:
private String reRealPackage() {
try {
Method getServiceMethod = ActivityManager.class.getMethod("getService");
Object sIActivityManager = getServiceMethod.invoke(null);
Method sGetLaunchedFromUidMethod = sIActivityManager.getClass().getMethod("getLaunchedFromUid", IBinder.class);
Method sGetActivityTokenMethod = Activity.class.getMethod("getActivityToken");
IBinder binder = (IBinder) sGetActivityTokenMethod.invoke(this);
int uid = (int) sGetLaunchedFromUidMethod.invoke(sIActivityManager, binder);
return getPackageManager().getPackagesForUid(uid)[0];
} catch (Exception e) {
e.printStackTrace();
}
return "null";
}
以上。
作者介绍
