Android上的Deep-Link技术调研

Posted by Dorck on September 16, 2023

本文是两年前输出的文章,可能与现在的成熟方案存在部分细节上“代沟”,请诸位选择性阅读,适当参考即可。

一、需求背景

现在有很多 APP 支持从浏览器或其他地方唤起自家应用,并跳转至特定页面去提高用户黏性。在提高运营手段的背景下,应用也需要迫切提高自身的曝光入口以及活动场景。如果用户可以通过点击短信中的一个活动链接能够直接跳转到目标 APP 的特定页面,那么带来的好处也是不言而喻的。

二、方案调研

iOS 系统提供的 universial link 来帮助应用实现这个功能,而至于 Android 可能就比较繁琐了,Android 6.0 之后开始才提供了 App Links 来帮助开发者从外部唤起自家应用,但是 6.0 之前一般只能通过 URI Scheme 方式来唤起应用。不同的方案各自都存在对应的兼容性问题,以下是各方案的对比情况:

技术 Universal Link Android App Link URI Scheme Chrome Intent
平台要求 >= iOS 9 >= Android 6 Chrome 1 < 25, iOS Chrome 1 >= 25
未安装表现 打开 Web 页面 打开 Web 页面 发生错误 可以打开 Web 页面
能否不发生跳转 不能 不能
能否去下载页面 不能
iframe 触发 不支持 不支持 Chrome 1 <= 18, iOS < 9 不支持
链接格式 2 正常的 URL 正常的 URL 自定义协议的 URL intent 协议的 URL
  1. 本文只针对移动端浏览器,其中 Chrome 表示 Chrome for Android,以及 Android Browser 的对应版本。
  2. 链接的作用方式有 3 种:用户点击这样的 <a> 标签;脚本中进行页面重定向;设置 iframesrc
  3. 每种实现方式都有其适用的平台和浏览器,要兼容多数浏览器需要根据 User Agent 应用不同的策略。 这些实现方式的行为也不一致,可能需要配合产品需求才能确定结合哪几种实现方式。

下面来看下各方案的具体实现过程。

三、URI Scheme 方案

URI Scheme 这种方式比较古老,目前也广泛用于从外部应用打开APP,这种协议由开发者自己定义,有比较丰富的定制性,但唤起时会有一个系统级别弹窗来让用户去选择通过匹配到的 APP 或者浏览器打开。URI 协议格式如下:

[scheme]://[host]/[path]?[params]

例如:http://www.baidu.com/data?page=home&uid=ut9092

上面的 URI 可以在 Android 应用中通过 intent-filter 去识别和处理:

1
2
3
4
5
6
7
8
9
<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:host="www.baidu.com"
        android:pathPattern="data"
        android:scheme="http"/>
</intent-filter>

下面,我们来通过 URI Scheme 来实现一个简单的小 demo,要求能够通过系统的记事本应用中的链接进入到 APP 的目标页面。首先,我们先来约束一下协议格式内容,如下所示:

1
http://com.example.deeplink?path=demo_page&text=im_from_system_notebook

可以看到,我们定义的 scheme 为 http,而 host 对应的 com.example.deeplink,这里我设置的是应用的包名,一般不会更改。至于 pathtext 就很显然是作为参数内容了,这里分别代表着页面路径参数和对应的页面内容参数,方便我们定制不同的页面逻辑,大家可以根据情况制定自己的规则。好的,接下来开始我们的第二步 — 在 demo 项目中额外创建一个页面(EvokeDemoActivity)并添加相应的 intent 匹配逻辑:

1
2
3
4
5
6
7
8
9
10
 <activity android:name=".deeplink.EvokeDemoActivity">
            <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"
                    android:host="com.example.deeplink"/>
            </intent-filter>
        </activity>

接下来,只需要在目标唤醒页面获取对应的参数内容即可:

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
/**
 * Deep link 唤起的目标页面
 */
public class EvokeDemoActivity extends AppCompatActivity {

    public static final String EVOKE_DEMO_PAGE = "demo_page";
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_evoke_demo);
        textView = findViewById(R.id.textView);
        obtainDeepLink();
    }

    private void obtainDeepLink() {
        Intent intent = getIntent();
        if (intent != null && intent.getData() != null && intent.getData().getQueryParameter("path").equals(EVOKE_DEMO_PAGE)) {
            Log.e("DemoPage", "received deep link intent: " + intent.getData().toString());
            String paramContent = intent.getData().getQueryParameter("text");
            if (!TextUtils.isEmpty(paramContent)) {
                textView.setText(paramContent);
            }

        }
    }
}

接着运行一下 demo,然后在系统记事本中输入测试链接,如: [http://com.example.deeplink?path=demo_page&text=im_from_system_notebook](http://com.example.deeplink?path=demo_page&text=im_from_system_notebook) 保存后点击查看,可以看到如下效果:

Kapture 2021-02-22 at 14.45.04.gif

可以发现,我们在记事本里点击链接后弹出了系统弹窗让我们选择打开链接的应用,需要选择我们自己的 APP 来打开(示例这边是 Android samples 应用),然后成功进入了目标 app 的制定页面并拿到了携带的参数内容。

与 iOS 中的 universial links 方案类似,Android 在 6.0 推出了 App links,用标准的 Web 页面 URL,同时绑定对应的 App。可以支持从白名单配置中的链接直接唤起 App,而不需要额外弹窗提示,对于用户来说会降低困扰,提升用户体验度。 首先,我们需要生成一份配置文件放到服务端,点击 Android studio 的 Tools > App Links Assistant,进入配置页面后第一步点击 Open URL Mapping Editor,添加一个 URL: add_url 然后在 Android manifest 文件中修改一下配置:

1
2
3
4
5
6
7
8
9
10
<activity android:name=".deeplink.EvokeDemoActivity">
            <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="com.example.deeplink"/>
            </intent-filter>
        </activity>

可以看到,这里我们只是添加了一个 autoVerify 属性。接着通过点击 Open Digital Asset Links File Generator **来生成配置的 json 文件: config **具体可参考官方文档的配置过程:https://developer.android.com/studio/write/app-link-indexing?utm_source=android-studio#associatesite 最终配置文件上传到服务器并测试通过后,就可以同样打开上面的测试链接,发现直接可以唤起我们的 APP,而不需要经过弹窗拦截。 此外,我们除了可以通过Android studio提供的 tools 来验证 app link 配置的正确性以外,也可以直接通过以下 adb 命令来验证:

1
adb shell dumpsys [package_name] domain-preferred-apps

如果显示如下信息,则校验成功:

1
2
3
Package: com.xxx.example
Domains: example.applink.xxx.com
Status:  always : 200000036

另外,需要注意的是:想要该配置关联生效,APP 在安装时必须联网。

当您 App Links 所有的环节已经配置完成后,可以利用测试设备测试唤起效果,测试步骤如下: 1、创建一条用于测试的 DeepLink ; 2、在测试设备中原生场景下进行测试,如:短信、备忘录等等; (不要直接在第三方软件中进行测试,例如微信。第三方软件通常对 DeepLink 跳转存在限制) 3、点击测试 DeepLink,如果可以直接跳转到 App 中,说明可以直接唤起 App,为最理想流程。 4、如果未直接跳转到 App 中,而是跳入下载引导中间页,并且系统弹窗询问是否要在应用中打开,此时可以通过上面的 adb shell dumpsys [package_name] domain-preferred-apps 命令 中的 Status 查看状态,如果状态显示为“ask”,并且确认 App Links 集成流程正确无误,则可能是当前测试设备机型在 AppLinks 遇到校验问题,对于该情况请参考下方常见问题说明。 对于 Status 状态的说明:

Status 状态 描述
ask Applink校验失败, 每次打开连接跳转时会弹出一个对话框, 提示选择打开短链的App
always 校验成功,理想状态
never 用户选择不再打开
always-ask 可忽略,尚未发现这一个出现, 跟 never 一样需要手动干预才会出现
undefined 尚未校验完成, 请稍后再试

常见问题: 1、多数客户可能会在验证环节得到的 Status 为 ask,这是为什么呢? AppLinks 的合法性是由系统校验,不同的手机系统使用不同的校验组件,即使是一个厂商的不同型号手机都可能使用不同的校验组件。 如果系统使用 com.android.statementservice 进行 AppLinks 的校验,在网络正常的情况下基本都能顺利通过, 如果系统使用 com.google.android.gms 组件校验,在手机能够科学上网的情况,也就是能够正常访问 Google 时,校验才能通过。常见华为 mate 系列,P 系列使用的都是 gms,也就是 Status 会为 ask。 查看自己手机是使用哪种组件,在命令行中输入以下命令: adb shell dumpsys package i 综上,Applink 不能顺利通过系统检验,原因有以下可能:

  • 可能是国内网络问题,使用 gms 组件校验的手机需要联通 Google 服务
  • 可能是您产品配置问题,GIO 填写的签名和手机上运行的 APP 签名不同

六、在浏览器唤起 APP 的场景

可是,重点来了,我们在实际测试过程中发现了一堆兼容性问题,在说明这些兼容性问题前,我们先解释一个概念:intent:// 协议。 Android Chrome 25+ 后已经不支持自定义 scheme 的方式,只支持 intent:// 协议(Android Intents with Chrome),最终要的是需要用户手动进行 点击 才能跳转,举个例子:

1
2
3
4
5
<a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;S.browser_fallback_url=http%3A%2F%2Fzxing.org;end"> Take a QR code </a>
<iframe href="intent://xxx" style="display: none;">></iframe>// 失效
<iframe href="strange://xxx" style="display: none;">></iframe>// 失效

intent:// 协议格式说明:

1
2
3
4
5
6
intent:
   //scan/
   #Intent; 
      package=com.google.zxing.client.android; 
      scheme=zxing; 
   end;

目前市面上大多第三方浏览器都是基于 Chrome 开发,这就带来了兼容性问题(没有条件覆盖所有的系统浏览器,这里只是有限测试的结果): browsers

1、部分浏览器,只支持 intent:// 协议 手动 唤起,如chrome、锤子。 2、部分浏览器只支持 scheme 唤起,如 UC 浏览器。 3、大部分浏览器,同时支持 scheme 私有协议和 intent:// 协议 自动 唤起。但,都没有按标准的 intent:// 协议来实现(除了 360 浏览器,给 360 点个赞):

  • 有的浏览器在 App 没安装时并没有执行 S.browser_fallback_url,而是跳转到应用市场如猎豹浏览器 4.46.3、乐视浏览器 1.2.1.29。
  • 有的浏览器不支持 S.browser_fallback_url 如搜狗浏览器、欧朋浏览器、猎豹浏览器。
  • 有的浏览器无论应用有无安装 S.browser_fallback_url 一直都会执行如 QQ 浏览器。

所以对这部分浏览器,不能使用 intent:// 协议。 4、更奇葩者,二者都不支持,如百度浏览器。

兼容性问题解决方案

针对上述三个兼容性问题,第 4 种情况无解我们直接忽略,第 2 第 3 种情况只能用自定义 scheme 的方式。 问题出在第 1 种情况,因为只能手动唤起,我们需要对浏览器类型进行判断(浏览器没有提供是否支持 自定义 schemeintent:// 的 API 只能通过 UA 判断),结合我们有限的测试结果,如果是锤子、Chrome 原生浏览器,需在页面中内置一个“下载应用”的按钮引导用户点击。

1
2
3
4
// 如果安装了 App 就跳转,否则就访问 S.browser_fallback_url 的值 http://strange.com
<a href="intent://strange/#Intent;scheme=strange://login;package=com.strange;S.browser_fallback_url=http%3A%2F%2Fstrange.com;end">跳转到活动页/下载</a>

我们来分析一下浏览器的 UA ,举几个例子:

1
2
3
4
5
6
7
8
//小米系统浏览器
User-Agent:Mozilla/5.0 (Linux; U; Android 6.0.1; zh-cn; MI 3W Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/8.9.5
// 小米 Chrome 原生浏览器
User-Agent:Mozilla/5.0 (Linux; Android 6.0.1; MI 3W Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.108 Mobile Safari/537.36
// 锤子系统浏览器
User-Agent:Mozilla/5.0 (Linux; Android 5.1.1; YQ603 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.11 Mobile Safari/537.36
// 锤子 Chrome 原生浏览器
User-Agent:Mozilla/5.0 (Linux; Android 5.1.1; YQ603 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.89 Mobile Safari/537.36

可以看出小米浏览器是在 Chrome 原生浏览器的 UA 上增加了 XiaoMi/MiuiBrowser/8.9.5 这部分特征码。类似的,很多第三方浏览器都是在 Chrome 基础上增加自己的特征码,换句话说 Chrome 原生浏览器 UA 没有自己的特征。 而锤子系统浏览器和 Chrome 原生浏览器 UA 几乎一样,这就使得判断是否锤子系统浏览器、 Chrome 原生浏览器 变得异常困难,要想尽可能完美解决问题只能使用排除法。 排除法由于不可能排除所有非 Chrome 原生浏览器,可能会存在误伤的可能。

经浏览器中转唤起 App 总结

要实现经浏览器中转 自动 唤起 App,Android 和 iOS 都可以通过 自定义 scheme 的方式,但 Android 的情况稍显复杂,因为部分浏览器并不支持,必须换成 intent:// 协议的方式 手动 唤起。 考虑到浏览器判断的难度,结合浏览器市场占有率的情况,我们最终的方案是暂时忽略 锤子系统浏览器、 Chrome 原生浏览器 这部分不支持 自定义 scheme 自动唤起 App 的用户。

App Links兼容性问题
  • App links在国内的支持还不够,部分安卓浏览器并不支持跳转至App,而是直接在浏览器上打开对应页面(直接将Applink作为链接跳转)。
  • 系统询问是否打开对应App时,假如用户选择“取消”并且选中了“记住此操作”,那么用户以后就无法再跳转 App。
1
2
3
<a href="https://tuyasmart.applink.smart321.com/triple/fromalexa?client_id=xxx&response_type=alexa&state=1&scope=uejdj&redirect_uri=https.baidu.com">通过applink唤起</a>

1
2
3
<a href="intent://applink/deviceshare/#Intent;scheme=tuyasmart;package=com.tuya.smart;S.browser_fallback_url=https://www.zhihu.com/question/270839820;end">通过 chrome Intent 唤起</a>

1
2
3
<a href="tuyasmart://com.tuya.smart/applink/deviceshare?short_code=124141">通过scheme唤起</a>

经过自己简单部署的前端页面发现,在 App link、chrome intent 以及 scheme 三种唤起方式在不同机型和浏览器表现上存在较大差异,以下是几种机型和浏览器简单的对比(并不全):

  App link(Android 6.0) chrome intent scheme
Pixel 2 XL(chrome) 成功且无弹窗 成功且无弹窗 成功且无弹窗
一加7t(夸克) 失败 成功,唤起前会有确认弹窗 成功,唤起前会有确认弹窗
三星GT-19500(三星浏览器 6.0 以下) 失败 成功且无弹窗 成功且无弹窗
三星GT-19500(chrome浏览器 6.0 以下) 成功,但有选择弹窗 成功且无弹窗 成功且无弹窗

虽然安卓还支持 APP Links 技术,但是这个技术依赖于谷歌服务器的验证,而谷歌服务器是被墙的,所以是在国内基本不能用。所以在安卓端能用的技术只有scheme技术了)可是大部分 App 的 URL Scheme 方式都被微信QQ封锁了,不过微信QQ有维护着一个白名单,如果你的域名在白名单内,那这个域名下所有的页面发起的 URL Scheme 就都会被允许。

七、项目中应用现状

目前内置项目中已经部分模块实现了从外部网页直接唤起进入 APP 内部的功能,主要还是借助于传统 Deep link 的方式实现的:

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
    <activity
            android:name="com.example.activity.ThirdAuthActivity"
            android:exported="@ref/0x7f05000d"
            android:launchMode="2"
            android:screenOrientation="1"
            android:configChanges="0x4a0">

            <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="xyz.com"
                    android:path="/action/share" />
            </intent-filter>
        </activity>

    <activity
            android:name="com.example.activity.ThirdLinkActivity"
            android:screenOrientation="1"
            android:configChanges="0x4a0">

            <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="xyz123.com"
                    android:path="/action/share" />
            </intent-filter>
        </activity>

经过与产品确认,目前只考虑 Android 6.0及以上的情况统一使用 AppLinks 来从外部网页唤起应用。然而,针对于国内浏览器环境等综合因素及 Android6.0 以下的情况,还是使用传统 DeepLinks 方式兜底。

八、理想方案

综合起来就是:

  • 通过 App Links(iOS 则是Universal Links),可以实现点击短信链接直接唤起 App;
  • 如果系统因为各种原因不支持 App Links,备选方案是 URI Scheme,不过会出弹框让用户选择用哪个 App 打开链接;
  • 如果用户没有选择我们的 App 而是选择了浏览器打开,则通过 URI scheme 尝试唤起 App;
  • 由于技术和成本问题,我们忽略不支持 URI Scheme 的浏览器。

如下图所示:

perfect_flow 网页能否判断当前运行的浏览器环境,如果是 chrome 浏览器(或内置chrome内核),则通过 Applink 唤起应用页面,如果其他类型的浏览器能否判断是否支持 intent:// 唤起,否则都通过原生 scheme 来兜底,至于 Android 6.0 以下机型默认执行 scheme 唤起,这也是 App link 在低版本机型上的默认兜底方式。

相关参考


许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。