乐趣区

H5-唤醒-APP-解决方案

参考:https://github.com/bsxz0604/R…

核心代码

// dom
<a id="openApp" href="javascript:;" title=""> 立即打开 </a>

// js
;(function (window) {var ue = {};

    // 客户端检测
    ue.browserInfo = function () {
        var json = {userAgent: navigator.userAgent.toLowerCase(),
            isAndroid: Boolean(navigator.userAgent.match(/android/ig)),
            isIos: Boolean(navigator.userAgent.match(/iphone|ipod|ipad/ig)),
            isWeChat: Boolean(navigator.userAgent.match(/MicroMessenger/ig)),
            isQQ: Boolean(navigator.userAgent.match(/ QQ/ig)),
            isQQBrowser: Boolean(navigator.userAgent.match(/MQQBrowser/ig))
        }

        return json;
    }

    // 唤醒 APP
    ue.openApp = function () {var $downloadSelectBtn = $('#downloadSelect');
        var $btnOpenIos = $('#downloadIos');
        var browser = ue.browserInfo();
        var scheme = '';
        var iosVersion = browser.userAgent.match(/os\s*(\d+)/);
        var universalLink = 'a';

        iosVersion = iosVersion ? (iosVersion[1] || 0) : 0;

        if (browser.isAndroid) { // 安卓
            scheme = 'b';
        } else if (browser.isIos) { // ios
            scheme = 'c';
        }

        $btnOpenIos.on('click', function () {
            // 客户端检测微信
            if (browser.isWeChat) {$('.pop-openbrowser').show();} else {if (browser.isAndroid) {var ifr = document.createElement('iframe');
                    ifr.src = scheme;
                    ifr.style.display = 'none';
                    document.body.appendChild(ifr);

                    setTimeout(function () {document.body.removeChild(ifr);
                    }, 2000);
                } else {
                    // ios9+
                    if (iosVersion >= 9) {document.location.href = universalLink;} else {setTimeout(function () { // 必须要使用 settimeout
                            var a = document.createElement("a"); // 创建 a 元素
                            a.setAttribute("href", scheme), a.style.display = "none", document.body.appendChild(a);
                            var t = document.createEvent("HTMLEvents"); // 返回新创建的 Event 对象,具有指定的类型。t.initEvent("click", !1, !1) // 初始化新事件对象的属性
                            a.dispatchEvent(t) // 绑定事件
                        }, 0)
                    }
                }

                var checkOpen = function (cb) {var _clickTime = +(new Date());

                    function check(elsTime) {if (elsTime > 3000 || document.hidden || document.webkitHidden) {cb(1);
                        } else {cb(0);
                        }
                    }
                    // 启动间隔 20ms 运行的定时器,并检测累计消耗时间是否超过 3000ms,超过则结束
                    var _count = 0,
                        intHandle;
                    intHandle = setInterval(function () {
                        _count++;
                        var elsTime = +(new Date()) - _clickTime;
                        if (_count >= 100 || elsTime > 3000) {clearInterval(intHandle);
                            check(elsTime);
                        }
                    }, 20);
                }
                checkOpen(function (opened) {
                    // APP 没有打开成功  并且开启自动跳转到下载页
                    if (opened === 0) {location.href = universalLink}
                });
            }
            ue.trackEventDownload();});
    };

    ue.init = function () {ue.openApp();
    };
})(window);
$(function () {ue.init();
})

业务逻辑

概念叙述

调起 APP 在不同平台用不同的方式,主要就分 3 个

* URI Scheme
* universal Link
* Android App Links

现在还是有很多第三方(例如魔窗)来协助你处理这个事情,通过接入他们的 SDK 和客户端代码来处理,但是万变不离其宗,所有的第三方也离不开这 3 种方式。

  1. URI Scheme:

    • URI Scheme 是 iOS,Android 平台都支持,只需要原生 APP 开发时注册 scheme,用户点击到此类链接时,会自动唤醒 APP,借助于 URL Router 机制,则还可以跳转至指定页面。
    • <scheme name> : <hierarchical part> [? <query>] [# <fragment>]

      • <scheme name>:是 scheme 的名称,代表着协议名称。
      • <hierarchical part>:它包含 authority 和 path。
      • <query>:可选项目,隔开或 & 隔开的键值对 <key>=<value>
      • <fragmentg>:可选项目包,其它额外的标识信息

例如:

git://github.com/user/project-name.git
ftp://user1:1234@地址
musically://musical?id=xxxx&key=xxxx
  1. universal Link:

    • iOS9 后推出的一项功能,通用链接,对于前端即访问一个 https 的 url, 如果这个 url 带有你提交给开发平台的配置文件中匹配规则的内容,iOS 系统会去尝试打开你的 app,如果打不开,系统就会在浏览器中转向你要访问的链接。
    • universal Link 工作方式如下:

      1. 访问 web link
      2. iOS 访问 https://xxxxxxx/apple-app-sit… 并解析,获取文件中的信息 (App 的 Team ID 和 Bundle ID)
      3. 通过 Bundle ID 检查本地是否存在对应 app,和检查 PATH 信息等,如果有 app 打开 app,如果没有则跳转对应 web link(可通过代码实现跳去 app Stroe)
  2. Android App Links:

    • 在 2015 年的 Google I/ O 大会上,Android M 宣布了一个新特性:App Links 让用户在点击一个普通 web 链接的时候可以打开指定 APP 的指定页面,前提是这个 APP 已经安装并且经过了验证,否则会显示一个打开确认选项的弹出框,只支持 Android M 以上系统。
    • 简单的说就是建立 APP 和某个链接的关联,避免系统在处理该类型链接时弹出选择框。弹框最常见的就是浏览器打开时的选择框。弹出选择框是应用注册了相应 scheme,applinks 的作用是避免在打开自己域名的链接时弹出选择(前提是注册了相应 scheme),可以实现直接打开自己关联的 app。

对比 / 优劣

1. URI Scheme

URI Scheme 的兼容性是最高,在使用的过程中,会发现有很多限制:

  • 当要被唤起的 app 没有安装时,这个链接就会出错。在国内非常杂乱的浏览器中,会出错的现象会很多种类型。
  • 当注册有多个 scheme 相同的时候,目前没有办法区分。
  • 不支持从其他 app 中的 UIWebView 中跳转到目标 app

也就因为会有这些原因,apple 和 android 都出现了自己第二套解决方案。

2. universal Link

从链接上看来,是一个 web link,所以也就解决了当没有 app 时,跳转也不会出现报错,所以相对 Scheme 优势就提现出来了。

  • 当已经安装 app,不需要加载任何 web 页面,app 就会立即启动;app 没有安装,就会跳去对应的 web link。
  • universal Link 是从服务器上查询是哪个 app 需要被打开,所以不会存在冲突问题
  • universal Link 支持从其他 app 中的 UIWebView 中跳转到目标 app
  • 隐私性,提供 universal Link 给别的 app 进行 app 间的交流,然而对方并不能够用这个方法去检测你的 app 是否被安装。

当然 universal Link 也不是十全十美的, 缺陷也是存在的:

  • 会记住用户的选择:在用户点击了 Universal link 之后,iOS 会去检测用户最近一次是选择了直接打开 app 还是打开网站。一旦用户点击了这个选项,他就会通过 safiri 打开你的网站。并且在之后的操作中,默认一直延续这个选择,除非用户从你的 webpage 上通过点击 Smart App Banner 上的 OPEN 按钮来打开。

3. app link 和 universal Link

差异不大。也是为了更好的提供调起 app 出现的 google 的方案。优点与 universal Link 差不多,缺点主要如下:

* 国内的支持相对较差,在有的浏览器或者手机 ROM 中并不能链接至 APP,而是在浏览器中打开了对应的链接。* 在询问是否用 APP 打开对应的链接时,如果选择了“取消”并且“记住选择”被勾上,那么下次你再次想链接至 APP 时就不会有任何反应

无论哪一种方式目前都没有解决几个问题:

  • 如果设备上没有安装这个 app 的时候,安装完毕后,无法保留住此时用户停留的上下文。
  • 因为 web 没有办法监听到 APP 是否安装,所以都需要通过一些手段来兼容调起 app 或者是去下载页

使用 & 需要注意的内容

  1. URI Scheme:

    • 使用: 这种方式是当期使用最广泛,也是最简单的,但是需要 APP 支持 URI Scheme。
    • 需要注意的内容 & 遇到的问题:
 其实使用 URI Scheme 部分前端没有太多可以排查的问题,会遇到的问题主要是两个部分。1. 在 android 的兼容性处理(国内的浏览器无力吐槽 ing),2. 当没有安装 app 的情况,URI Scheme 会有各种报错,也需要处理…
  1. universal Link & app Links

    • 使用:对于有 app 的用户,只是打开一个连接,但是需要注意的是需要考虑到没有 APP 的用户。(个人的解决方案:针对域名来判断,当域名为特定的 universal Link 的域名,则跳转去下载页面)
    • 需要注意的内容 & 遇到的问题:

      1. apple-app-site-association 和 assetlinks.json 的配置
      2. 需要保证使用的链接跨域 (universal Link)
      3. 直接将 universal Link 贴入浏览器的 url 中不会生效
      4. window.onload 或者用户没有任何事件触发的情况下,universal Link 也不会生效

两大平台的特殊处理 (facebook & twitter)

facebook 和 twitter 作为国外的两大信息聚合平台,对于在他们 app 中调起 app 也有自己的一套方式。
根据要求通过添加 META 头来处理打开 APP
facebook:

<meta property="fb:app_id" content="xxxxxx" />
<meta property="og:type" content="xxxx"/>
<meta property="og:title" content="xxx" />
<meta property="al:ios:url" content="{{uri scheme}}" />
<meta property="al:android:url" content="{{uri scheme}}" />
<meta property="al:ios:app_store_id" content="{{app_store_id}}" />
<meta property="al:ios:app_name" content="{{xxx}}" />
<meta property="al:android:app_name" content="{{xxx}}" />
<meta property="al:android:package" content="{{android:package}}" />

twitter:

<meta name="twitter:card" content="app" />
<meta name="twitter:site" content="xxxxx" />
<meta name="twitter:title" content="xxxxx" />
<meta name="twitter:description" content="xxxxxxx" />
<meta name="twitter:image" content="xxxx" />
<meta name="twitter:app:name:iphone" content="xxx">
<meta name="twitter:app:id:iphone" content="xxx">
<meta name="twitter:app:url:iphone" content="{{Scheme}}">
<meta name="twitter:app:name:ipad" content="xxx">
<meta name="twitter:app:id:ipad" content="xxx">
<meta name="twitter:app:url:ipad" content="{{Scheme}}">
<meta name="twitter:app:name:googleplay" content="xxx">
<meta name="twitter:app:id:googleplay" content="xxx">
<meta name="twitter:app:url:googleplay" content="{{Scheme}}">

第三方服务

从以上内容可以总结出:要做一个兼容性很好的方案,就需要考虑各种情况,在不同的情况适配不同的方案,比方说用户是在手机浏览器打开还是微信中打开,或者是在 pc 中打开,universal link 是否被关闭等,这就使代码实现变得复杂,且容易出错,且还有安卓平台机型众多、浏览器众多等导致的兼容问题。

如果觉得实现难度或者成本太高,你可以考虑使用第三方提供的服务,例如魔窗的 `mLink。具体请查看官网,在此不赘述。

使用 checkList(前端)

  1. scheme

    • iOS 和 android 是否已经支持 此 scheme
    • js 处理兼容代码
  2. universal Link (apple-app-site-association 官方文档)

    • HTTPS 的域名
    • iOS9 以上
    • universal Link 是否跨域
    • universal Link 的落地页是否是下载页面
    • apple-app-site-association 配置在 host 的根目录和.well-known 下

      • 官方检测: apple-app-site-association 检测
    • apple-app-site-association 会在第一次打开 app 或者更新 app 时候会去拉去, 所以确认是否更新了 apple-app-site-association 后没有更新过 app
    • 检查 apple-app-site-association paths 大小写敏感 支持通配符
    • 该设备的用户选择了直接打开 app 还是打开网站,如果选择打开网站,需要通过 smart banner 重新启用
    • 跳转处理是否是在用户事件中触发,而不是进入页面后直接触发
  3. app links (android app links 官方文档)

    • HTTPS 的域名
    • 跳转后的落地页是否是下载页面
    • assetlinks.json 配置在 host 的.well-known 下

      • 官方生成 / 检测: android app links 检测
  4. facebook (facebook app link 官方文档)

    • 将需要的 meta 头信息填充完毕
    • 检测链接 分享调试器 – Facebook for Developers , 确认分享链接中获取到了所需要的 meta 头
    • 分享过的链接会有缓存,在检测中清楚缓存
    • 如果 web 和 wap 链接一致,确认在 web 中也添加了相同的 meta 头,facebook 会默认从 web 中获取
  5. twitter (Twitter app card 官方文档)

    • 将需要的 meta 头信息填充完毕
    • 检测链接 Twitter app card 检测
    • 如果 web 和 wap 链接一致,确认在 web 中也添加了相同的 meta 头,facebook 会默认从 web 中获取

可能出现的 bug

1. iOS 11.2 universal link 失效的 bug:

https://blog.branch.io/notice…

失效现象:
无法直接唤醒 app,而是直接跳转到 appStore,用户再点击【打开】。

解决方案:
iOS 开发可以配置通用链接让其【强制打开】,但是可能出现该错误提示框,这样给用户的体验反而更不好。

2. QQ 浏览器、微信已屏蔽 scheme & universal link

腾讯系封杀了除本厂及白名单 app 之外的所有 app 的唤醒功能。

参考资料链接

  1. webview 如何屏蔽 universal Link
  2. apple-app-site-association 官方文档
  3. apple-app-site-association 检测
  4. android app links 官方文档
  5. android app links 检测
  6. facebook app link 官方文档
  7. 分享调试器 – Facebook for Developers
  8. Twitter app card 官方文档
  9. Twitter app card 检测
退出移动版