APP唤起那点破事

JavaScript 是运行在一个单独的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由于这些 Context 与原生运行环境的天然隔离,我们可以将这种情况与 RPC(Remote Procedure Call,远程过程调用)通信进行类比,将 Native 与 JavaScript 的每次互相调用看做一次 RPC 调用。

JavaScript 调用 Native

JavaScript 调用 Native 的方式,主要有两种:window注入 API拦截 URL SCHEME

注入API

注入 API 方式的主要原理是,通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。
对于 iOS 的 UIWebView,实例如下:

1
2
3
4
5
JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

context[@"postBridgeMessage"] = ^(NSArray<NSArray *> *calls) {
// Native 逻辑
};

前端调用方式:

1
window.postBridgeMessage(message);

拦截 URL SCHEME, 支持 iOS6

先解释一下 URL SCHEME:URL SCHEME是一种类似于url的链接,是为了方便app直接互相调用设计的,形式和普通的 url 近似,主要区别是 protocol 和 host 一般是自定义的。

  • 拦截 URL SCHEME 的主要流程是:Web 端通过某种方式(例如 iframe.src)发送 URL Scheme 请求,之后 Native 拦截到请求并根据 URL SCHEME(包括所带的参数)进行相关操作。
  • 使用 iframe.src 发送 URL SCHEME 会有 url 长度的隐患。
  • 创建请求,需要一定的耗时,比注入 API 的方式调用同样的功能,耗时会较长。

Native 调用 JavaScript

相比于 JavaScript 调用 Native, Native 调用 JavaScript 较为简单,毕竟不管是 iOS 的 UIWebView 还是 WKWebView,还是 Android 的 WebView 组件,都以子组件的形式存在于 View/Activity 中,直接调用相应的 API 即可。

Native 调用 JavaScript,其实就是执行拼接 JavaScript 字符串,从外部调用 JavaScript 中的方法,因此 JavaScript 的方法必须在全局的 window 上。(闭包里的方法,JavaScript 自己都调用不了,更不用想让 Native 去调用了)

小白必看,JSBridge 初探

唤起方案

Scheme

iOS和Android都支持,只需APP开发时注册Scheme,当用户点击到此类链接时,会自动唤醒APP,借助于URL Router机制,则还可以跳转至指定页面。

一般会有弹窗提醒,safari中如未安装app会提示链接无效。

一般通过超时判断是否唤起App,然后走下载新增流程。

iOS9开始支持,基于HTTPS的唯一URL防止其他App注册scheme对链接进行拦截,通过apple-app-site-association文件配置应用信息及可处理的universal link。

安装或更新应用时,向应用内Associated Domains指定的网站请求apple-app-site-association文件,注册给系统,当设备访问链接时,如果某个应用可以处理该链接,则打开该应用,如果打不开,系统就会在浏览器中转向你要访问的链接。

支持情况:占比最大的safari支持,uc及qq浏览器不支持,微信从7.0.5开始支持,其它…

开发:App部分开发咨询iOS同学或自行查询。

Android6.0开始支持,通过assetlinks.json文件配置应用信息。

用户没有设置链接处理者,并且有多个支持处理此链接的app时,系统会显示选择对话框让用户选择最合适的app(例如微信中打开H5页面后,右上角选择浏览器中打开,会弹出列表框让用户选择浏览器)。用户设置了此链接处理者,系统把网页的uri传递给此app。

支持情况:Chrome、三星、宙斯等浏览器支持,其它…

开发:App部分开发咨询Android同学或自行查询。

口令

针对交互流程中断情况(如用户未安装、跳转弹窗时用户未允许、或微信等受限制导致用户未直接唤起App),一般会通过口令方案,自动在剪贴板中存储内容或让用户手动复制内容,当用户主动打开app或安装没打野时通过识别剪贴板内容打开的页面跳转到对应页面或执行对应操作

引导

一般app(微信、微博、百度)和浏览器(UC、QQ)都会尽可能限制用户跳出减少流失,所以对scheme和link做了刻意限制或不进行支持。

对应方案就是页面中引导用户出受限环境去浏览器,常见微信中让用户『右上角->浏览器中打开』,(iOS微信已从2019.7.16发布的7.0.5版本支持ulink)

应用商店

在受限环境中,还有一种方法是通过scheme唤起应用商店,然后通过应用商店打开App。

特别是在iOS中常用(干不过大腿),

好处是H5无法准确判断用户是否已安装App,而应用商店可以根据安装状态显示为打开或安装,另一点是用户一般对应用商店信任度更高(直接下载pkg包也会被提示安全风险)

下载页

实现自动唤起下载等功能,也做为新增的兜底页做为未唤起情况使用。

唤起 App 应该是很常见的问题了。我们在开发 H5 的时候,有一些链路上的功能在 H5 不支持,只能去 App 才能完成。比如,下单、支付等功能。那么在更多的场景能够唤起 App 就显得很重要了。

几步走

  1. 判断浏览器,动态加载对应浏览器的下载逻辑
  2. 通过 universal linkURL Schemea 标签iframe 几种方式找出最适合这个浏览器的唤起方式。
  3. 如果下载了 App,就会走打开逻辑,如果没有下载则走下载逻辑。
  4. 如果已知不能唤起的浏览器引导其它浏览器打开

流程:

各个唤起方法对比

没有哪种方式是完美的,每种唤起方式都有它的优势跟劣势,只有将所有的唤起方法在不同浏览器上尝试过才能择优使用。

通用链接(universal link)

通用链接 universal linkIOS 9.0 版本以上才可以使用的新的特性。此特性类似于深层链接,并能够方便地通过打开一个 https 链接来直接启动您的客户端应用(手机有安装 App)。对比起以往所使用的 URL Scheme, 这种新特性在实现 web-app 的无缝链接时能够提供极佳的用户体验。

体验一下通用链接

我们可以来体验一下,左边的是通用链接 universal link方式,右边是 URL Scheme 方式。

左边可以直接将 App 打开,而右边需要确认是否打开才可以。

解决的bug:在 ios 12.3 版本之后,Safari 会有一个 bug,就是 URL Schemedownload App 先后一起使用的话,会同时唤起 App 然后再进入 App store,体验极差。使用 universal link 可以完美解决这个问题。

通用链接配置

必须在根目录下或者 域名/.well-known 下面。注意 域名不要使用业务域名添加配置文件,必须要跨域使用,否则会会不能唤起App,直接打开页面。

  • 访问链接 我们假设有三个 App,分别是 appaappbappc,我们通过访问 https://jump.test.com/appa/index.html 就可以唤起 appa 了。
  • 配置下载页 如果没有安装 appa 的话就会跳转到 一个 404 页面,因为我们这个页面 https://jump.test.com/appa/index.html 其实并不存在,所以我们需要将这个页面在 nginx 代理到一个下载页面。
  • 疑问 那么你可能有一个疑问,我如果唤起来 app 之后这个页面会不会跳转到下载页呢?答案是不会的,如果唤起了 app 就不会继续跳转页面了,只有在唤不起来的时候才会进入下载页。

apple-app-site-association 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"applinks": {
"apps": [],
"details": [
{
"appID": "ABCD.com.aaa.appa",
"paths": ["appa/*"]
},
{
"appID": "ABCD.com.aaa.appb",
"paths": ["appb/*"]
},
{
"appID": "ABCD.com.aaa.appc",
"paths": ["appc/*"]
},
{
"appID": "ABCD.com.aaa.*",
"paths": ["*"]
}
]
}
}

通用链接的优缺点:

  • 优点:提供极佳的用户体验,使用起来也非常方便,解决了同时唤起 appapp store 的问题
  • 缺点:只有在 ios 9 版本之上才能使用,个别浏览器不支持有兼容性问题

URL Scheme 方式

用法

根据协议来判断是哪个 app 唤起。

1
location.href = "testa://test.aaa.com/home";

判断是否唤起 app

因为唤起 app 并没有回调让我们知道唤起了 app,所以我们只能通过监听页面是否隐藏来实现判断是否唤起 app 的逻辑。当然这种做法还是有很大缺陷的,比如低端机型唤起速度很慢,高端机型要快得多,所以 delay 的时间要设置一个可以接受的值,我们暂时定的是 2500。还有如果用户没有安装 app,也会有 2500 的延时才能安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const timer = setTimeout(() => {
this.__download(options);
}, options.delay);

const visibilitychange = function() {
const tag = document.hidden || document.webkitHidden;
tag && clearTimeout(timer);
};

document.addEventListener("visibilitychange", visibilitychange, false);
document.addEventListener("webkitvisibilitychange", visibilitychange, false);
window.addEventListener(
"pagehide",
function() {
clearTimeout(timer);
},
false
);

URL Scheme 的优缺点:

  • 优点 1. 兼容性好,大部分机型都能兼容 2. 使用方便
  • 缺点 1. 不能判断是否已经下载了 app,需要自己写逻辑判断 2. 用户体验不如通用链接的方式。

a 标签跟 iframe 方式

这两种方式都是通过创建元素,通过元素属性的特性来做的

a 标签

1
2
3
const a = document.createElement("a");
a.setAttribute("href", __SCHEMA_PATH);
a.click();

iframe

1
2
3
4
5
6
7
8
const ifr = document.createElement("iframe");
iframe.src = path;
ifr.src = nativeUrl;
ifr.style.cssText = "display:none;border:0;width:0;height:0;";
document.body.appendChild(ifr);
setTimeout(function() {
document.body.removeChild(ifr);
}, 1000);

优缺点

  • 优点 在个别场景中如果发现唤起 app 有问题,可以尝试这两种。
  • 缺点 iframe 基本上已经被废弃了,a 标签测试结果只在个别机型中可用。

总结

  • 微信 必须配置白名单,或者使用 universal link 也可以在 ios 唤起,表现形式有一点差别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const wxconfig = {
    debug: false,
    appId: conf.appId, // 公众号的 appid
    timestamp: conf.timestamp,
    nonceStr: conf.noncestr,
    signature: conf.signature,
    beta: true,
    jsApiList: ["launchApplication", "getInstallState"]
    };
    (window.wx && window.wx.config(wxconfig)) || (window.wxconfig = wxconfig);
  • 百度浏览器必须用 universal link 方式才能在 IOS 唤起

  • qq 在 ios 要使用 a 标签 的方式,即使通用链接的方式也不能将其唤起

    1
    2
    3
    4
    5
    6
    if (isIOS) {
    const a = document.createElement("a");
    a.setAttribute("href", __SCHEMA_PATH);
    return a.click();
    }
    location.href = __SCHEMA_PATH;
  • 在微博内测试,京东跟淘宝都可以调起,猜测是设置了白名单,目前没有找到好的办法,只能引导到其它浏览器中打开

测试结果

目前只有微博不能唤起,其它都可以正常唤起

  • 如果已经配置过Universal Links,那么在用户第一次安装app时,苹果会发送一个请求,请求你服务器上的apple-app-site-association文件。
  • 请求apple-app-site-association文件成功之后,用户就可以使用Universal Links唤醒app了。
    = Universal Links本身是一个HTTP/HTTPS链接,所以有更好的兼容性; 只支持iOS9及以上系统
  • 企鹅和微信都把url scheme 唤醒app这种方式给禁了
  • Android M可以开启Universal Links

参考文献

Deeplink实践原理分析

JSBridge的原理

前端网页如何打开一个PC本地应用

webview的简单介绍和手写一个H5套壳的webview

使用JS检测自定义协议是否存在

各种场景唤起 App 骚操作

唤醒APP的那些事

iOS9 Universal Links踩坑之旅,移动应用之deeplink唤醒app

Deep Link及相关第三方库调研

H5唤起新增APP