移动端:Native调起方案调研——schema和universalLink

2,305 阅读6分钟

为什么不直接在浏览器中打开h5,要跳转到app中?

相较于浏览器,app通过jsbridge可以提供给h5更多的服务

比如设备的标识did,避免重复登录等功能。

举个例子,瑞幸的app有一个新人首杯咖啡免费的活动。当同一台手机,你换一个号码去注册登录,这个活动就用不了了。这就是用了设备id来避免无限白嫖的行为。如果只是在浏览器中,是无法获取一台设备id来标识用户。

URL Schema(ios+安卓)

形式:scheme://host/pathname?query

scheme:是app的标示,一个APP可以设置一个或多个打开自己的scheme,比如今日头条是snssdk143(安卓)/snssdk147(ios)
host:是app位置,比如home(主页)/detail(详情页)/webview(webview页面)
pathname:是在host中进一步细分
query:可以携带透穿的参数标示来源信息

在浏览器调起app时,将host置为webviewquery置为页面的url,可以在指定的app中打开指定的h5。

universal link(ios9以上)

Universal Link(通用链接):看起来就是一条普通的https链接

在ios设备,如果手机中安装了对应的APP,可以直接调起
也可以在该链接中放置对应的H5页面,起引导下载的作用

Schema VS universal Link

universal link在调起app失败时有更好的表现。
使用schema时,如果用户没有安装对应的app,安卓设备会没有反应,ios设备会有一个系统的错误弹窗。但使用universalLink,在调起失败时,可以自动跳转到app store,引导用户下载。

实现

方法:将window.location.href置为schema/uviversal link

由于现在ios已经到14了,所以基本忽略ios9以下的情况。我们认为在安卓上有schema这一种方案,ios上有schema+universal link两种调起方案。

发起调起

  1. 在安卓中,不直接将当前修改当前窗口的href,而是创建一个iframe节点,将这个节点的href设置为相对应的link,并且将这个节点的可见性设为隐藏,这样不会影响当前页面的可见改变,又可以尝试去调起app(如果直接修改当前窗口的href,无论这次调起是否成功,当前页面都已经跳转了)

  2. 在ios中,不支持创建iframe节点。

    const ifr = document.createElement('iframe');
    ifr.src = '调起页的schema';
    ifr.style.display = 'none';
    document.body.appendChild(ifr);
    setTimeout(function(){
        document.body.removeChild(ifr);
    }, 3000);

调起检测

浏览器没有办法知道本地是否安装了某个app,但是可以感知app是否成功调起。

  1. 一是通过浏览器的属性visibilityState,当前页面被隐藏时为hidden,可见时为visible。

window.setTimeout(function(){
    document.body.removeChild(ifr);
    if(document.visibilityState === 'visible'){
        window.location.href = '下载链接'
    }
},2000)
  1. 是通过“浏览器被切入后台,计时器会延长”这个特性来推断唤醒了app。
const openTime = +new Date()
window.setTimeout(function(){
    document.body.removeChild(ifr);
    if( (+new Date()) - openTime < 2500 ){//但调起成功浏览器会延迟,时间差大于2.5s,如果没有大于2.5s,则认为没有成功,进入失败处理,这里是跳转到下载链接
        window.location = '下载链接';
    }
},2000)
  • 为什么是2500ms,不设置成2001ms?
    设置一个2s的定时器不代表这个方法一定会在2s后执行,而是指的2s后把触发这个函数的事件挂到事件队列的尾部,而它真正被执行,需要当前执行栈清空+它之前的事件队列对应的函数完成。所以即使定时器没有被延迟,也可能时差计算大于2000ms,所以放宽到2500ms

但是经过测试,第二个方法不稳定,在电脑上试验偶然会有毫秒级的延迟,在手机上几乎没有。不可用。所以选了第一个方案

默认调起失败处理

  1. 在发起调起app操作后设置一个时间间隔(比如三秒),如果当前页面的visibilityState为hidden,说明调起成功,否则为失败,认为没有安装对应app,跳转到下载链接。

  2. 但是,在不同的浏览器中,页面的可见性不完全可靠。比如,在qq的webview中,2s内成功调起了app,但是之前页面的visibilityState出现过十几秒才变成hidden。

  3. 并且,在调起app时,系统会有一个弹窗,“是否打开xxx”,往往用户来不及点击是定时器已经被触发

    也就是说,如果使用“3s内浏览器可见性没有改变就去下载页”的方案,会出现“用户安装了对应的app,并且调起成功,但还是跳到了下载页的情况”

自定义失败处理

由于上面的原因,可以自定义一个调起失败的处理。
比如给一个弹窗,询问用户是否要跳转到对应的下载页,用户确认之后再去下载。

宿主环境差异

当前的宿主环境自然不希望用户跳转出去,所以一些用户量较大的软件对这种跳转链接做了禁封
对份额前三的浏览器(uc浏览器、qq浏览器、百度)以及qq、微信、微博的三家的webview,在ios和安卓上分别做了测试,发现:

  1. 在安卓中,微信/微博无法使用schema唤醒app
  2. 在ios中,微信/qq/qq 浏览器/百度无法使用schema唤醒app;微信/微博/uc浏览器无法使用universal link唤醒app。
  • 如何区分宿主环境?
    通过navigator.userAgent可以获得用户的操作系统已经浏览器类型。
    比如ios上,qq浏览器的ua:Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 MQQBrowser/10.5.1 Mobile/15E148 Safari/604.1 QBWebViewUA/2 QBWebViewType/1 WKType/1

跳转成功率检测

在跳转之前向服务端进行打点,并通过url透穿一个一个唯一的标识。在跳转到对应的页面后接受这个标识,再进行一次打点。

可以监测到每一次跳转是否成功。