原理及说明: 首先:无论 IOS 还是 Android,浏览器无法预知本地是否安装了某个 APP。 功能实现的本质是浏览器通过 URL_scheme 打开 APP。Twitter 就注册自己能被「twitter://」打开。 补充:如果是 APP 之间的互相跳转是很简单的:IOS 可以使用 UIApplication 的 canOpenUrl 方法来监测 URL_scheme 是否能打开对应的 APP。exp:如果「twitter://」检测能被打开,也就说明本地安装了 Twitter 。再用 UIApplication 的 openURL 方法,就能打开 Twitter 了。Android 中的做法类似。
实现方案:以 IOS9 为一个分水岭:
iOS7/iOS8: iOS 中默认通过 Safari 打开 URL scheme,方法一般如下两种:
1,直跳方式:点击链接、修改 window.location 等。 <a href="schemeUrl"> 唤醒你的 APP</a> 或者: window.location.href = schemeUrl;
PS:如果 APP 唤醒失败或者未安装的情况,有可能会调到 404 页面。很影响用户体验。一般情况会让他跳到其他页面或者下载 APP
2,iframe 方式:在 body 上添加 iframe,设置src属性为跳转的URL scheme。 这种方法不会引起页面可见的变化(页面变成一个新页面),不会导致浏览器的历史记录的变化。 实现思路:
<a href="APP下载地址">下载或打开APP</a>
<script> $('a').click(function() { var ifr = document.createElement('iframe'); ifr.src = '自定义 URL scheme'; ifr.style.display = 'none'; document.body.appendChild(ifr); setTimeout(function(){ document.body.removeChild(ifr); }, 3000); }); </script>实现思路:点击 a 标签,首先会尝试打开 URL scheme,如果成功,就唤起 APP;如果失败,则跳转到 href 属性,即下载页。 (该方案在很多安卓机型有问题) Android
$('a').click(function() { location.href = '自定义 URL scheme'; t = Date.now(); setTimeout(function(){ if (Date.now() - t < 1200) { location.href = 'Android 下载地址'; } }, 1000); return false; } 理想过程是这样:浏览器尝试打开 URL scheme,在 1 秒计时后,检查当前时间,如果实际时间已过 1200 毫秒,说明唤起 APP 成功(唤起 APP 会让浏览器的定时器变慢);如果没超过 1200 毫秒,很可能是没有安装应用,就跳到下载地址。 或者:
var ifr = document.createElement('iframe'); ifr.src = 'com.baidu.tieba://'; ifr.style.display = 'none'; document.body.appendChild(ifr); var openTime = +new Date(); window.setTimeout(function(){ document.body.removeChild(ifr); if( (+new Date()) - openTime > 2500 ){ window.location = 'http://exam.com/xxxx.apk'; } },2000) 这种方案不稳定:因为 Android 是基于 Linux 的分时多任务的,setTimeout 的基准偏差可能会没那么大。
但如果设置比较小的运行间隔(<30ms),在浏览器或者 webview 中,应用切换到后台,setInterval 会被很明显的延迟执行,比如设置一个运行间隔 20ms,总计运行 100 次的定时器,如果页面一直处于前台,则 100 次跑完,总耗时与 100x20=2000ms 不会有太大差异,但页面在后台运行时,此时间会明显超过 2000ms。可以利用这一点来实现是否成功打开 APP 检测及回调。
function openApp(openUrl, appUrl, action, callback) {
//检查app是否打开
function checkOpen(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);
}
1//在iframe 中打开APP 2var ifr = document.createElement('iframe'); 3ifr.src = openUrl; 4ifr.style.display = 'none'; 5if (callback) { 6 checkOpen(function(opened){ 7 callback && callback(opened); 8 }); 9} 10 11document.body.appendChild(ifr); 12setTimeout(function() { 13 document.body.removeChild(ifr); 14}, 2000);
} 可以通过 document.hidden 或 document.[webkit|moz|ms] Hidden 来判断页面是否被置入后台(即应用被唤起),或 visibilitychange 事件,但对于 Android 4.4 版本一下则不支持。
iOS9 在 iOS 9 上,iframe 方案变得不可用。 按不能使用之前 Android 的代码,因为在打开自定义 URL scheme 时,会弹出对话框,询问是否用 xx 应用来打开。往往用户还没来得及点击打开,定时器又触发了,导致跳到 App Store。 可以在尝试打开 URL scheme 后,再加一个页面跳转,这样对话框会被覆盖,再刷新页面,就能无需确认唤起 APP:
$('a').click(function() { location.href = '自定义 URL scheme'; location.href = '下载页'; location.reload(); } 下载页延时 2 秒跳转到 App Store。
APP 已安装这是没问题的,但如果 APP 未安装,跳 App Store 的请求会失败。 这时可以使用两个定时器:
$('a').click(function() { location.href = '自定义 URL scheme'; setTimeout(function() { location.href = '下载页'; }, 250); setTimeout(function() { location.reload(); }, 1000); } 不过在 iOS9 中其实是支持 universal link 的,就是一个 http 域名形式,在微信中都可以唤起 APP。如果未安装的话,可以直接引导用户去 APP store 下载。
PS:没有完美的解决方案。
微信中打开 因为微信将唤起本地 APP 的接口给禁了,所以微信中是不能直接唤起 APP 的,一般做法是提示用户在浏览器中打开,之后的流程还是我们上面讲的内容。
但是,在 iOS9 中,这个限制是可以突破的,也就是说可以直接唤起 APP。方法就是使用我们上文提到的 universal link。
在 Android 和 iOS8 及其以下系统中,我们可以利用腾讯的亲儿子:应用宝。把你的唤起地址配置成你 APP 的应用宝地址,微信中跳转到这个地址后,如果用户已经安装了 APP,则可直接唤起,如果没有安装,则可直接点击下载。 PS:一般需要一个中间页面引导用户在尾部浏览器打开。微信唤醒 APP 默认只能到达首页。
需要判断的使用场景: 1,用户是在手机浏览器打开 2,微信浏览器打开 3,PC 中打开 4,universal link 是否被关闭 。。。
其他实现方案: 魔窗的 mLink。只要你加了魔窗的 sdk,就可以通过类似 “https://s.mlinks.cc/AA01” 的链接,在任何环境下打开你的 APP,兼容超过 600 台以上安卓机型的第三方主流浏览器。不管是在手机浏览器中,还是在微信中打开,你可以指定唤起 APP 后直达 APP 中的某个页面或内容(某个促销商品等),就算用户没安装 APP,点击下载安装之后,再打开,还是跳转到指定的页面。
魔窗使用教程: 需要准备的材料: 1,微信分享 AppID 2,应用宝微下载链接 3,IOS :Bundle ID、URI Scheme、下载地址、Team ID(如果配置了 Universal Link) 4,Android: 包名、URI Scheme、下载地址
后台生成极光魔链位信息 JS 使用: 1,直接引用 mLink JS 文件。不要下载之后放入项目
<script src="https://static.mlinks.cc/scripts/dist/mlink.min.js"></script>2,在 HTML 页面中准备一个或多个用于打开 APP 的 a 元素 <a id="btnOpenApp"> 打开 APP</a>
极光魔链后台配置好 mLink 必要参数并且生成一条短链
图片.png 初始化 mLink new Mlink({ mlink:'https://a.mlinks.cc/ABCD',//短链地址 button:document.querySelector('a#btnOpenApp') }); /* ------ 或 -------- */ var link='https://a.mlinks.cc/ABCD';//短链地址 var btn_1=document.querySelector('a#btnOpenApp1'); var btn_2=document.querySelector('a#btnOpenApp2'); var btn_3=document.querySelector('a#btnOpenApp3');
var options = [ { mlink: link+'?name=1', button: btn_1 }, { mlink:link+'?name=2', button: btn_2 }, { mlink: link+'?name=3', button: btn_3 } ];
new Mlink(options); 在 H5 页面中使用默认短链接进行动态传参
图片.png options 选项 { mlink: "短链KEY", button: document.querySelector('a#btnOpenApp'), autoLaunchApp : false, autoRedirectToDownloadUrl: true, downloadWhenUniversalLinkFailed: false, inapp : false, params: {} } 具体参数含义见链接:
补充: 京东兼容性解决方案:
(function(){
// 判断浏览器
var Navigator = navigator.userAgent;
var ifChrome = Navigator.match(/Chrome/i) != null && Navigator.match(/Version/\d+.\d+(.\d+)?\sChrome//i) == null ? true : false;
var ifAndroid = (Navigator.match(/(Android);?[\s/]+([\d.]+)?/)) ? true : false;
var ifiPad = (Navigator.match(/(iPad).*OS\s([\d_]+)/)) ? true : false;
var ifiPhone = (!ifiPad && Navigator.match(/(iPhone\sOS)\s([\d_]+)/)) ? true : false;
var ifSafari = (ifiPhone || ifiPad) && Navigator.match(/Safari/);
var version = 0;
ifSafari && (version = Navigator.match(/Version/([\d.]+)/));
1 version = parseFloat(version[1], 10); 2 // 是否从微信打开 3 var ifWeixin = navigator.userAgent.indexOf("MicroMessenger") >= 0; // weixin 4 var j = false; 5 var iframe = "plugIn_downloadAppPlugIn_loadIframe"; 6 var t = false; 7 var i = 0; 8 var B = {}; 9 var b = {}; 10 var selector = null; 11 var Hquery = {}; 12 // 判断当前使用的js框架是zepto还是jquery 13 var Query = window.Zepto || window.jQuery ? true : false; 14 var g = []; 15 // 是否存在html5的localStorage 存储 16 var v = window.localStorage ? true : false; 17 var o = "mdownloadAppPlugInskip"; 18 var p = null; 19 20 function m() { // 打印时间 例如:2016-5-18 21 var M = new Date(); 22 var N = M.getFullYear(); 23 var O = M.getMonth() + 1; 24 var L = M.getDate(); 25 strDate = N + "-" + O + "-" + L; 26 return strDate 27 } 28 // 微信相关操作 29 function r() { // weixin api 30 WeixinJSBridge.invoke("getInstallState", { 31 packageName: "com.jingdong.app.mall", 32 packageUrl: "openApp.jdMobile://" 33 }, function(M) { 34 var N = M.err_msg, 35 L = 0; 36 if (N.indexOf("get_install_state:yes") > -1) { 37 j = true 38 } 39 }) 40 } 41 // 根据是否存在js框架进行dom和时间的绑定 42 function bind(dom, event, fun) { // bind event 43 if (Query) { 44 selector("#" + dom).bind(event, fun) 45 } else { 46 selector("#" + dom).addEventListener(event, fun, !1) 47 } 48 } 49 50 function z(L) { 51 var M = (L || "mGen") + (++i); 52 return M 53 } 54 // 微信操作 55 if (ifWeixin) { // if navigitor is weixin 56 if (window.WeixinJSBridge && WeixinJSBridge.invoke) { 57 r() 58 } else { 59 document.addEventListener("WeixinJSBridgeReady", r, !1) 60 } 61 } 62 63 // 如果存在js框架 64 if (Query) { 65 selector = window.$; 66 Hquery = window.$ 67 } else { 68 selector = function(obj) { 69 if (typeof obj == "object") { 70 return obj 71 } 72 return document.querySelector(obj); 73 }; 74 if (!window.$) { 75 window.$ = Hquery = selector 76 } else { 77 Hquery = window.$ 78 } 79 } 80 window.onblur = function() { 81 for (var L = 0; L < g.length; L++) { 82 clearTimeout(g[L]) 83 } 84 }; 85 // 设置cookie。 86 function e(N) { 87 var M = document.cookie.indexOf(N + "="); 88 if (M == -1) { 89 return "" 90 } 91 M = M + N.length + 1; 92 var L = document.cookie.indexOf(";", M); 93 if (L == -1) { 94 L = document.cookie.length 95 } 96 return document.cookie.substring(M, L) 97 } 98 // 设置cookie 99 function l(N, P, L, Q, O) { 100 var R = N + "=" + escape(P); 101 if (L != "") { 102 var M = new Date(); 103 M.setTime(M.getTime() + L * 24 * 3600 * 1000); 104 R += ";expires=" + M.toGMTString() 105 } 106 if (Q != "") { 107 R += ";path=" + Q 108 } 109 if (O != "") { 110 R += ";domain=" + O 111 } 112 document.cookie = R 113 } 114 115 // 打开的链接集合 116 function F(L) { 117 var url = { 118 downAppURl: "http://h5.m.jd.com/active/download/download.html?channel=jd-m", 119 downAppIos: "http://union.m.jd.com/download/go.action?to=http%3A%2F%2Fitunes.apple.com%2Fcn%2Fapp%2Fid414245413&client=apple&unionId=12532&subunionId=m-top&key=e4dd45c0f480d8a08c4621b4fff5de74", 120 downWeixin: "http://a.app.qq.com/o/simple.jsp?pkgname=com.jingdong.app.mall&g_f=991850", 121 downIpad: "https://itunes.apple.com/cn/app/jing-dong-hd/id434374726?mt=8", 122 inteneUrl: "openApp.jdMobile://360buy?type=1", 123 inteneUrlParams: null, 124 openAppBtnId: "", 125 closePanelBtnId: "", 126 closePanelId: "", 127 closeCallblack: null, 128 closeCallblackSource: null, 129 cookieFlag: null, 130 noRecord: false, 131 sourceType: "JSHOP_SOURCE_TYPE", 132 sourceValue: "JSHOP_SOURCE_VALUE", 133 openAppEventId: "MDownLoadFloat_OpenNow", 134 closePanelEventId: "MDownLoadFloat_Close" 135 }; 136 if (L) { 137 for (var M in L) { 138 if (M && L[M]) { 139 url[M] = L[M] 140 } 141 } 142 } 143 return url 144 } 145 // 敲黑板 重点内容。看京东是怎么解决兼容问题的。 146 function openApp(N, L) { // openApp 147 var R = h(N); //获取相对应的url 148 var O = null; 149 if (ifWeixin) { // 如果是微信端 150 var M = null; 151 if (j) { 152 M = R 153 } else { 154 M = N.downWeixin 155 } 156 location.href = M; // 直接使用location.href打开 157 return 158 } 159 if (ifiPad) { // 如果是ipad 160 O = N.downIpad 161 } else { 162 if (ifiPhone) { // 如果是iphone 163 O = N.downAppIos 164 } else { 165 O = N.downAppURl 166 } 167 } 168 169 if (ifChrome) { // 如果是chrome 170 if (ifAndroid) { //安卓浏览器 171 var Q = R; 172 R = y(Q); 173 // 延后50毫秒 174 setTimeout(function() { 175 window.location.href = R 176 }, 50) 177 } 178 } 179 if (ifSafari && version >= 9) { // 判断safari版本 如果大于9 180 setTimeout(function() { // 必须要使用settimeout 181 var S = document.createElement("a"); //创建a元素 182 S.setAttribute("href", R), S.style.display = "none", document.body.appendChild(S); 183 var T = document.createEvent("HTMLEvents"); // 返回新创建的 Event 对象,具有指定的类型。 184 T.initEvent("click", !1, !1)// 初始化新事件对象的属性, S.dispatchEvent(T) // 绑定事件 185 }, 0) 186 } else { 187 document.querySelector("#" + iframe).src = R // 将iframe增加src 188 } 189 var P = Date.now(); 190 setTimeout(function() { 191 if (L) { 192 var S = setTimeout(function() { 193 x(P, O) 194 }, 1500); 195 g.push(S) 196 } 197 }, 100) 198 } 199 // x方法 200 function x(N, downUrl) { 201 var L = Date.now(); 202 if (N && (L - N) < (1500 + 200)) { 203 window.location.href = downUrl 204 } 205 } 206 207 function h(N) { 208 var V = []; 209 var P = N.inteneUrlParams; 210 var T = { 211 category: "jump", 212 des: "productDetail" 213 }; 214 if (N.sourceType && N.sourceValue) { 215 T.sourceType = N.sourceType; 216 T.sourceValue = N.sourceValue; 217 if (P && !P.sourceType && !P.sourceValue) { 218 P.sourceType = N.sourceType; 219 P.sourceValue = N.sourceValue 220 } 221 } 222 if (P) { 223 for (var U in P) { 224 if (U && P[U]) { 225 V.push('"' + U + '":"' + P[U] + '"') 226 } 227 } 228 } else { 229 for (var U in T) { 230 if (U && T[U]) { 231 V.push('"' + U + '":"' + T[U] + '"') 232 } 233 } 234 } 235 try { 236 var Q = MPing.EventSeries.getSeries(); 237 if (Q) { 238 var W = JSON.parse(Q); 239 W.jdv = encodeURIComponent(e("__jdv")); 240 W.unpl = encodeURIComponent(e("unpl")); 241 W.mt_xid = encodeURIComponent(e("mt_xid")); 242 W.mt_subsite = encodeURIComponent(e("mt_subsite")) 243 } 244 var S = { 245 mt_subsite: encodeURIComponent(e("mt_subsite")), 246 __jdv: encodeURIComponent(e("__jdv")), 247 unpl: encodeURIComponent(e("unpl")), 248 __jda: encodeURIComponent(e("__jda")) 249 }; 250 Q = JSON.stringify(W); 251 V.push('"m_param":' + Q); 252 V.push('"SE":' + JSON.stringify(S)) 253 } catch (R) { 254 V.push('"m_param":null') 255 } 256 var M = "{" + V.join(",") + "}"; 257 var O = N.inteneUrl.split("?"); 258 var L = null; 259 if (O.length == 2) { 260 L = O[0] + "?" + O[1] + "¶ms=" + M 261 } else { 262 L = O[0] + "?params=" + M 263 } 264 return L 265 } 266 267 function y(L) { 268 return "intent://m.jd.com/#Intent;scheme=" + L + ";package=com.jingdong.app.mall;end" 269 } 270 271 function n(L) { 272 if (L.openAppBtnId) { 273 B[L.openAppBtnId] = L; 274 G(L.openAppBtnId, L.openAppEventId); 275 bind(L.openAppBtnId, "click", function() { 276 var P = this.getAttribute("id"); 277 var M = B[P]; 278 if (!t) { 279 var N = document.createElement("iframe"); 280 N.id = iframe; 281 document.body.appendChild(N); 282 document.getElementById(iframe).style.display = "none"; 283 document.getElementById(iframe).style.width = "0px"; 284 document.getElementById(iframe).style.height = "0px"; 285 t = true 286 } 287 var O = M.cookieFlag ? "downloadAppPlugIn_downCloseDate_" + M.cookieFlag : "downloadAppPlugIn_downCloseDate"; 288 l(O, Date.now() + "_2592000000", 60, "/", "m.jd.com"); 289 l(O, Date.now() + "_2592000000", 60, "/", "m.jd.hk"); 290 openApp(M, true) 291 }) 292 } 293 } 294 295 function D(M) { 296 if (M.closePanelBtnId && M.closePanelId) { 297 B[M.closePanelBtnId] = M; 298 G(M.closePanelBtnId, M.closePanelEventId); 299 var Q = M.cookieFlag ? "downloadAppPlugIn_downCloseDate_" + M.cookieFlag : "downloadAppPlugIn_downCloseDate"; 300 var O = e(Q); 301 var P = null; 302 if (O) { 303 P = O.split("_"); 304 if (P.length == 2) { 305 P[0] = parseInt(P[0], 10); 306 P[1] = parseInt(P[1], 10) 307 } else { 308 P = null 309 } 310 } 311 var L = Date.now(); 312 if (Html5Plus() || (!M.noRecord && P && P.length == 2 && (L - P[0]) < P[1])) { 313 document.querySelector("#" + M.closePanelId).style.display = "none"; 314 if (M.closeCallblack) { 315 var N = M.closeCallblackSource ? M.closeCallblackSource : null; 316 M.closeCallblack.call(N) 317 } 318 return 319 } else { 320 document.querySelector("#" + M.closePanelId).style.display = "block" 321 } 322 bind(M.closePanelBtnId, "click", function() { 323 var U = this.getAttribute("id"); 324 var R = B[U]; 325 var T = R.cookieFlag ? "downloadAppPlugIn_downCloseDate_" + R.cookieFlag : "downloadAppPlugIn_downCloseDate"; 326 if (!R.noRecord) { 327 l(T, Date.now() + "_259200000", 60, "/", "m.jd.com"); 328 l(T, Date.now() + "_259200000", 60, "/", "m.jd.hk") 329 } 330 document.querySelector("#" + R.closePanelId).style.display = "none"; 331 if (R.closeCallblack) { 332 var S = R.closeCallblackSource ? R.closeCallblackSource : null; 333 R.closeCallblack.call(S) 334 } 335 }) 336 } 337 } 338 339 function Html5Plus() { // htmlplus 340 if (Navigator.indexOf("Html5Plus") >= 0) { 341 return true 342 } else { 343 return false 344 } 345 } 346 347 function G(P, M) { 348 try { 349 var O = document.getElementById(P); 350 var L = O.className; 351 if (L) { 352 L = L + " J_ping" 353 } else { 354 L = "J_ping" 355 } 356 O.className = L; 357 O.setAttribute("report-eventid", M) 358 } catch (N) {} 359 } 360 361 function C(L) { 362 var M = F(L); 363 n(M); 364 D(M) 365 } 366 Hquery.downloadAppPlugIn = C; 367 Hquery.downloadAppPlugInOpenApp = function(L) { 368 var M = F(L); 369 openApp(M); 370 } 371});
参考文章: 京东在 html5 页面中打开本地 app 的解决方案 Ios/Android h5 唤起本地 APP iOS/Android 浏览器 (h5) 及微信中唤起本地 APP 极光魔链 mLink JS 集成文档 H5 链接打开原生 App 并跳转到指定界面
作者:前端小学生_f675 链接:https://www.jianshu.com/p/62adf3cac8f2 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
