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 $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种方式。
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
universal Link:
- iOS9 后推出的一项功能,通用链接,对于前端即访问一个https的url,如果这个url带有你提交给开发平台的配置文件中匹配规则的内容,iOS系统会去尝试打开你的app,如果打不开,系统就会在浏览器中转向你要访问的链接。
universal Link 工作方式如下:
- 访问web link
- iOS访问 https://xxxxxxx/apple-app-sit... 并解析,获取文件中的信息(App的Team ID和Bundle ID)
- 通过Bundle ID 检查本地是否存在对应app,和检查PATH信息等,如果有app打开app,如果没有则跳转对应web link(可通过代码实现跳去app Stroe)
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或者是去下载页
使用 & 需要注意的内容
URI Scheme:
- 使用: 这种方式是当期使用最广泛,也是最简单的,但是需要 APP 支持 URI Scheme 。
- 需要注意的内容 & 遇到的问题:
其实使用URI Scheme 部分前端没有太多可以排查的问题,会遇到的问题主要是两个部分。1. 在android的兼容性处理(国内的浏览器无力吐槽ing),2. 当没有安装app的情况,URI Scheme 会有各种报错,也需要处理…
universal Link & app Links
- 使用:对于有app的用户,只是打开一个连接,但是需要注意的是需要考虑到没有APP的用户。(个人的解决方案:针对域名来判断,当域名为特定的universal Link 的域名,则跳转去下载页面)
需要注意的内容 & 遇到的问题:
- apple-app-site-association 和 assetlinks.json 的配置
- 需要保证使用的链接跨域(universal Link)
- 直接将universal Link 贴入浏览器的url中不会生效
- 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(前端)
scheme
- iOS 和 android 是否已经支持 此scheme
- js处理兼容代码
universal Link (apple-app-site-association 官方文档)
- HTTPS的域名
- iOS9 以上
- universal Link 是否跨域
- universal Link的落地页是否是下载页面
apple-app-site-association 配置在 host的根目录和.well-known下
- apple-app-site-association会在第一次打开app或者更新app时候会去拉去,所以确认是否更新了apple-app-site-association后没有更新过app
- 检查apple-app-site-association paths 大小写敏感 支持通配符
- 该设备的用户选择了直接打开app还是打开网站,如果选择打开网站,需要通过smart banner 重新启用
- 跳转处理是否是在用户事件中触发,而不是进入页面后直接触发
app links (android app links官方文档)
- HTTPS的域名
- 跳转后的落地页是否是下载页面
assetlinks.json 配置在 host的.well-known下
- 官方生成/检测: android app links检测
facebook (facebook app link官方文档)
- 将需要的meta头信息填充完毕
- 检测链接 分享调试器 - Facebook for Developers , 确认分享链接中获取到了所需要的meta头
- 分享过的链接会有缓存,在检测中清楚缓存
- 如果web和wap链接一致,确认在web中也添加了相同的meta头,facebook会默认从web中获取
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 的唤醒功能。