如何唤醒APP?

移动互联网时代,“用户增长”成为每个公司关注的重点话题。为了将更多用户引导到客户端内,产品经理会习惯性地在网页的各个地方巧妙隐藏唤醒App的“机关”。

常见的出现场景

浏览器 —唤醒—> App
  用户在浏览器中浏览网页时,当检测到该网页来自于某个App时,此时可以引导用户呼起或者下载App
微信、QQ —唤醒—> App
  用户将App中自己喜欢的内容分享到微信、QQ,在站外打开网页时,可以正常浏览,也可以引导用户呼起或者下载App

接下来,让我们深入研究下唤醒App的几种解决方案?

唤醒App的几种解决方案

1、 URL Scheme 方式

  • 条件
    • APP需要注册自己的URL Scheme,用来唯一标识一个App。
    • Scheme格式:<scheme域名>://<path>?<params>=<value>
  • 代码

1) iframe方式

var _iframe = document.createElement('iframe');
_iframe.src = scheme;
_iframe.style.display = 'none';
 document.body.appendChild(_iframe);
 window.setTimeout(function(){
    document.body.removeChild(_iframe);
    if((+new Date()) - openTime > 2500) {
        window.location.href = url;
   }
}, 2000);

2)a链接方式
<a href="<scheme域名>://<path>?<params>=<value>">打开APP</a>
3)location.href 直接跳转
window.location.href = "<scheme域名>://<path>?<params>=<value>"

  • 优缺点

    • 优点:iOS、Android均支持,开发简单;web-native协议制定简单。
    • 缺点:

    由于要考虑用户没有安装App的情况,所以当用户没有安装时,通过延迟会跳转到AppStore。iOS9+当跳转App时,会弹出一个弹框,让用户选择是否跳转,此时还在当前页,setTimeout中的代码会继续执行,导致用户还没来得及选择,就已经跳到AppStore。
    若用户未安装App,Android上scheme打开失败,没有任何提示,延迟之后,跳下载页。但是iOS9+会先弹出个万恶的跳转失败的弹窗,延迟之后,再跳下载页。

    iOS9+呼端确认弹窗

image.png

iOS9+万恶的跳转失败的弹窗

image.png
  • 支持情况

    URL Scheme方式一直被广泛使用,但是有些App并不认可,比如:微信、手机百度;站在这些App的角度上考虑,他们并不希望用户为了看更多分享内容,跳出自己的App,因此他们就在客户端内拦截了scheme方式呼端,导致URL Scheme方式在微信、手机百度中彻底失效!!!当然微信是存在一个白名单的,对于白名单中的分享链接是不会屏蔽scheme调用的。
    安卓App厂商差异很大,情况比较多样化(比如:Android Chrome版本25+通过iframe方式呼端失败 )

  • 兼容性

Android系统:Chrome for Android无法通过iframe方式来调用scheme,而通过a链接的方式可以成功调用,而针对Chrome内核的浏览器如360浏览器,对于iframe和a链接的方式都能支持,所以对Chrome内核的浏览器采用a链接的方式来调用scheme;对于其他浏览器,如UC,QQ浏览器则采用iframe方式调用scheme。
iOS系统:Safari浏览器不支持 iframe可直接做页面跳转;对于UC、Chrome、QQ只能通过a链接方式调用scheme。
上述提到的屏蔽scheme方式的App:呼端失败跳下载页。

2、 Android Intent 方式

  • 条件

    A little known feature in Android lets you launch apps directly from a web page via an Android Intent. One scenario is launching an app when the user lands on a page, which you can achieve by embedding an iframe in the page with a custom URI-scheme set as the src, as follows: <iframe src="paulsawesomeapp://page1"> </iframe>. This works in the Chrome for Android browser, version 18 and earlier. It also works in the Android browser, of course.

  • 构造intent字符串
intent:
HOST/URI-path
#Intent; 
  package=[string];                 //  android app包名
  action=[string];      
  category=[string];    
  component=[string];               
  scheme=xxxx;                  // 协议头
  S.browser_fallback_url=[url]          // 可选,scheme启动客户端失败时的跳转页,一般为下载页,需编码
end; 
  • 代码
<!--Intent方式呼端-->
<a href="intent://<role>/<path>#Intent;scheme=<scheme>;package=com.domain;S.browser_fallback_url=[url];end">打开APP</a>
  • 支持情况
    • iOS:不支持
    • Android:Android的Intent方式比URL Scheme方式要靠谱,能通过参数设置呼端失败要跳转的地址;若手机能匹配到对应App,则成功呼到对应页面,若未安装,会跳手机自带的应用市场下载。
  • 参考文档
    Android Intents with Chrome

3、Universal Links

  • 条件

    • iOS9+系统
    • Universal Link是通过标准的http/https协议链接唤起App;若未安装App,访问此通用链接,可以自定义页面;
  • 优缺点

    • 优点

      唯一性:不像自定义的Scheme方式,该方式是通过标准的http/https协议的链接到你的App.未安装App时,自定义的Scheme方式无法打开,而http/https的Universal Link通用链接有很好的兼容性;
      安全性:当用户安装了App时,iOS会从网站上去下载说明文件(下面所说的apple-app-site-association的文件,说明这个App可以打开哪些http链接),这个说明文件是只有开发者有权限写并上传到网站的根目录,所以App与网页http链接的关联是安全的。
      可变性:当用户没有安装App时,Universal Link链接也能正常打开,也可以设置:未安装App时,可以用Safari打开该链接。
      简单性:一个URL链接,可以将网站和App做关联。
      私有性:其他App在不知道你是否安装的情况下,也可以和你的App进行相互通信(因为只是一个普通的http链接啊)。

    • 缺点:只支持 iOS9+系统;在开启了Universal Link之后,用户可以通过该方式呼端,成功呼端后右上角会显示链接地址(iOS9、 iOS10会出现面包屑导航,iOS11+没有面包屑导航(如下图)),当用户一不小心手动点击这个地址时(俗称:Universal link误关),Universal Link会失效(失效后,Universal Link呼端就永远失败了,永远呼不起来,跳下载页,所以我们要重新激活Universal Link)需要引导用户到Safari浏览器,引导用户点击Safari内置顶部的系统呼端条,重新激活Universal Link,也可以将链接复制到备忘录中,长按链接出现用App打开,也可激活Universal Link。


      image.png

      image.png

      image.png

      image.png
  • 开启Universal Links开关

    • 注册一个域名并支持https
    • 有权限上传到网站根目录.well-know(这个权限是为了上传一个Apple指定的文件apple-app-site-association)
    • 创建一个JSON文件名字为apple-app-site-association的文件(文件名必须是这个!!!)
// apple-app-site-association文件配置
{
    "applinks": {
        "apps": [],
        "details": {
            "ZVC23L5QY4.com.domain.app": {
                "paths": ["*"]
             }
        }
     }
  }

注意:
大小写敏感;严格匹配配置的path路径;apple-app-site-association文件的读取,只在第一次启动App时加载;iOS9.2开始,在相同的domain下,Universal Link不work,必须跨域;
以上只是重点部分内容,详细说明请参考:
打通 iOS 9 的通用链接(Universal Links)

了解了呼端的几种方式,分享下我们团队完整的呼端解决方案

完整的解决方案

由于我们无法判断用户是否安装App,所以以上所有的方案都只能尝试呼端。整合上述这些方案,具体思路如下:

// iOS9+在开启Universal Link开关的前提下,优先使用Universal Links方式呼端
if (!closeUnilink && isios && iosVer && iosVer >= 9) {
      // 通过Universal Links方式获取通用呼端链接
      const unilink = getUnilink(opts.scheme, opts.unilink);
      if (unilink) {
        window.location.href = unilink;
      }
    } else {
  // 不支持Intent方式,采用 URL Scheme 方式
   if (!canIntent) {
       // iOS9+ Safari 不支持iframe方式
       if (!isWv && (ua.indexOf('safari') > -1 && iosVer && iosVer >= 9)) {
          link(scheme);
        } else {
          iframe(scheme);
        }
        // 处理未安装客户端情况:延迟跳转到下载页
        setTimeout(function () {
              gotoDownload();
            },延长时间);

    } else {
       // Android支持Intent方式时: intent呼起客户端
      intent(scheme);
  }
}

注意:
  前面提到的URL Scheme 方式的iframe和a链接方式,需要考虑用户未安装客户端情况:延迟跳转到下载页。这个延长时间的设置很关键!!!延长时间的设定需要考虑:如果延长时间小于App的启动时间,App还未启动,就执行setTimeout代码;如果延长时间较长,当用户未安装App时,需要等待特别久的时间才能执行setTimeout代码。
  对代码封装,根据不同业务呼端需求,可以提炼几个可配参数:呼端scheme地址、呼端失败是否跳下载页、下载页链接、呼起客户端失败超时时间、呼起回调;这样做的好处是:调用组件时,根据不同需求传递参数即可。

优化

  • 实际测试时发现:当成功呼起App时,用户再次返回到Safari浏览器的页面时已经跳转到下载页面了,此时需要对setTimeout做清除定时器处理。
  • 当本地App被唤起时,App处于设备可视窗口的最高层,此时浏览器进入后台程序页面会被隐藏掉,会触发pagehide与visibilitychange事件,此时应先清除setTimeout事件;同时,document.hide属性设置为true,所以setTimeout内不做跳转处理,防止页面跳转到下载页面。
  • 实际开发中,为了防止某些浏览器不支持这个 Page Visibility API,最好同时监听pagehide事件,这样会比较保险(相关代码如下)。
// 页面隐藏时触发
window.onpagehide = function () {
   if (timeout) {
    clearTimeout(timeout);
   }
 };
// 页面的可见状态变化时,会触发
 visibilitychange = function () {
    const tag = document.hidden || document.webkitHidden;
    if (tag && timeout) {
      clearTimeout(timeout);
    }
  }
document.addEventListener('visibilitychange', visibilitychange, false);
// 兼容多的浏览器事件
document.addEventListener('webkitvisibilitychange', visibilitychange, false);

代码中有关事件具体参考:Page Visibility API文档

测试结果

测试机型主要针对iOS和Android默认浏览器、Chrome等一些常用主流浏览器,iOS9+微信内嵌请尝试使用 Universal Link 方式呼起客户端。

demo页面

测试反馈

image.png


以上完整的呼端解决方案我们完美的使用了很久,直到2018年1月7日,微信封掉了内置浏览器里的Universal Link呼端,导致微信内引导呼端的流程彻底中断!!!

考虑到微信是站外分享很重要的渠道,我们当然不能!!!放过!!!

  • 首先:我们有一个思路:既然微信容器内不能使用Universal Link呼端,那么我们可以脱离微信容器,做一个中转页面,引导用户去浏览器呼端。
  • 其次:想想用户之前习惯性操作都是一次性直接点击呼端成功的,引导的话链路有点长,用户难免会吐槽。
    所以,针对中转页面iOS客户端新增 Universal Link 匹配规则(以中转页面链接为主),以便在微信内,「使用浏览器打开」后,被系统拦截后可以直接打开客户端,不用引导去浏览器。
    当然,对于Android用户,我们考虑到也可以引导用户去应用宝呼端。
  • 最后做了一点小优化:在浏览器的中转页面中,采用双保险呼端。当客户端配置好Universal Link链接时,Safari浏览器访问中转页面,页面顶部会出现系统的呼端条,引导用户下拉,用户可以选择使用系统呼端条打开,从而也能激活Universal Link,防止误关;采用按钮点击尝试呼端(关闭Universal Link方式)。
  • 针对Universal Link误关的现象,可以采纳这两种方法
    • 用样式的方式遮挡右上角面包屑导航。可以使得iOS9、iOS10和iOS11+达到视觉的一致性。但是样式控制不好,用户体验会很不好。
    • 获取面包屑导航的触发事件,可以手动的控制触发后跳转的地址(可以引导跳转到中转页面,引导用别的方式呼端)。这种算hack的解决方案。要考虑成本问题,是否采纳。

中转页面

image.png

iOS客户端采用Universal link进行系统拦截

image.png

双保险呼端

image.png

image.png

至此,站外呼端方案完美落幕~~~

推荐阅读更多精彩内容