当前位置: 首页 > 工具软件 > scheme-lib > 使用案例 >

callapp-lib源码解读,实现h5/webapp打开app,如果不能就直接下载。

贺乐意
2023-12-01

 前言:h5/web实现“唤起app,如果已下载就直接打开app,如果不能下载,就直接跳转下载”的功能,通过反复查资料,我知道目前有2钟实现方式:

     1. 是通过把h5cordova打包成app调用cordova的方法。

     2. 但是绝大多数做法是通过h5打开app链接地址,如果能打开则直接会跳转,如果不能,则设置一个延迟定时器setTimeout(如延迟2秒),然后直接进行下载window.location.href=downUrl。但是因为ios和android以及不同浏览器不同应用内置的浏览器打开,会出现的一些被限制兼容等问题。所以更为精细的做法是更加ios和android以及浏览器,版本,常用打开应用的内置浏览器.....等条件,一般存在三种打开app的方式:


注意:方法二的一个弊端,在打开app链接时,如果因为网络延迟响应超过2秒也会被识别为没有安装app,直接进行下载链接。或者如果用超过2秒且页面没有关闭同时满足才下载,但是在ios9以上的safari浏览器上仍然会存在的问题是,safari浏览器通过window.location.href跳转链接时,会默认有弹窗提示(链接有效时,提示是否需要打开app的弹窗。链接无效时,仍然会提示,该网址无效的弹窗)。如果链接有效,这个默认弹窗在2秒之后在操作是否需要打开app时,此时已经超过2秒,代码仍然会视为页面超过2秒仍然没有关闭,进而会直接指向下载app链接的代码。


 

           方式一:location打开,最常见的打开方式。但是一些浏览器会限制这种方式打开。

window.location.href=openUrl

            方式二:a标签点击打开

const tagA = document.createElement('a');
tagA.setAttribute('href', openUrl);
tagA.style.display = 'none';
document.body.appendChild(tagA);
tagA.click();  

            方式三:iframe 标签点击打开

const iframe = document.createElement('iframe');
iframe.frameborder = '0';
iframe.src = openUrl;
iframe.style.cssText = 'display:none;border:0;width:0;height:0;';
document.body.appendChild(iframe);
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>callapp-lib源码解析</title>
  
</head>
<body>
  <div id='btn''>点击打开/下载app</div>

  <script>
    let iosOpenUrl='';
    let androidOpenUrl='';
    let iosDownUrl='';
    let androidDownUrl="";
    let intent=null;  //安卓原生谷歌浏览器必须传递 Intent 协议地址,才能唤起 APP
    const ua = window.navigator.userAgent || '';
    const isAndroid = /android/i.test(ua);
    const isIos = /iphone|ipad|ipod/i.test(ua);
    const isWechat = /micromessenger\/([\d.]+)/i.test(ua);
    const isWeibo = /(weibo).*weibo__([\d.]+)/i.test(ua);
    const isQQ = /qq\/([\d.]+)/i.test(ua);
    const isQQBrowser = /(qqbrowser)\/([\d.]+)/i.test(ua);
    const isQzone = /qzone\/.*_qz_([\d.]+)/i.test(ua);  
    const isOriginalChrome = /chrome\/[\d.]+ Mobile Safari\/[\d.]+/i.test(ua) && isAndroid;// 安卓 chrome 浏览器,很多 app 都是在 chrome 的 ua 上进行扩展的,即安卓的应用app很多都是内置chrome浏览器
    // chrome for ios 和 safari 的区别仅仅是将 Version/<VersionNum> 替换成了 CriOS/<ChromeRevision>
    // ios 上很多 app 都包含 safari 标识,但它们都是以自己的 app 标识开头,而不是 Mozilla
    const isSafari = /safari\/([\d.]+)$/i.test(ua) && isIos && ua.indexOf('Crios') < 0 && ua.indexOf('Mozilla') === 0;


    const wechatVersion = navigator.appVersion.match(/micromessenger\/(\d+\.\d+\.\d+)/i)[1]; //获取 微信 版本号

    let iosVersion = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);  //获取 ios 大版本号
    iosVersion=parseInt(iosVersion[1], 10);

    if(isIos){  //iOS设备
      // 近期ios版本qq禁止了scheme和universalLink唤起app,安卓不受影响 - 18年12月23日
      // ios qq浏览器禁止了scheme和universalLink - 2019年5月1日
      // ios 微信自 7.0.5 版本放开了 Universal Link 的限制
      if ((isWechat && wechatVersion < '7.0.5') || isQQ || isQQBrowser) {//微信且微信的版本小于7.0.5,或者是qq打开,或者是qq浏览器打卡
        window.top.location.href = iosOpenUrl;
      } else if (iosVersion < 9) { //ios9版本以下
        const iframe = document.createElement('iframe');
        iframe.frameborder = '0';
        iframe.src = iosOpenUrl;
        iframe.style.cssText = 'display:none;border:0;width:0;height:0;';
        document.body.appendChild(iframe);
      } else {
        window.top.location.href = iosOpenUrl;
      }
    }else { //android设备
      if(isWechat){ //android的微信
        window.top.location.href = androidOpenUrl;
      }else if(isOriginalChrome){ //android的原生浏览器
        if (typeof intent !== 'undefined') {  //安卓原生谷歌浏览器必须传递 Intent 协议地址,才能唤起 APP
          window.top.location.href = androidOpenUrl;
        } else {  // scheme 在 andriod chrome 25+ 版本上必须手势触发
          const tagA = document.createElement('a');
          tagA.setAttribute('href', androidOpenUrl);
          tagA.style.display = 'none';
          document.body.appendChild(tagA);
          tagA.click();  
        }
      }else{  //android设备其他应用
        const iframe = document.createElement('iframe');
        iframe.frameborder = '0';
        iframe.src = androidOpenUrl;
        iframe.style.cssText = 'display:none;border:0;width:0;height:0;';
        document.body.appendChild(iframe);
      }
    }
    

    document.getElementById('btn').addEventListener("click",()=>{
      checkOpen(()=>{
        if(isIos){
          window.top.location.href = iosDownUrl;
        }else{
          window.top.location.href = androidDownUrl;
        }
      },2000);
    })
    function checkOpen(failCallback, timeout=2000) {
      const visibilityChangeProperty = getVisibilityChangeProperty();
      const timer = setTimeout(() => {
        const hidden = isPageHidden();  //判断页面是否隐藏(进入后台)
        if (!hidden) {  //没有进入后端,说明唤起失败,唤起失败,就执行失败的函数
            failCallback();
        }
      }, timeout);

      if (visibilityChangeProperty) {
        document.addEventListener(visibilityChangeProperty, () => {
          clearTimeout(timer);
        });

        return;
      }

      window.addEventListener('pagehide', () => { //页面关闭时 清除定时器
        clearTimeout(timer);
      });
    }
    /**
     * 获取判断页面 显示|隐藏 状态改变的属性,webkitvisibilitychange/mozvisibilitychange/msvisibilitychange/ovisibilitychange/visibilitychange文档的可见性改变时触发
     */
    function getVisibilityChangeProperty() {
      const prefix = getPagePropertyPrefix();
      if (prefix === false) return false;

      return `${prefix}visibilitychange`;
    }

    /**
     * 获取页面隐藏属性的前缀
     * 如果页面支持 hidden 属性,返回 '' 就行
     * 如果不支持,各个浏览器对 hidden 属性,有自己的实现,不同浏览器不同前缀,遍历看支持哪个
     */
    function getPagePropertyPrefix() {
      const prefixes = ['webkit', 'moz', 'ms', 'o'];
      let correctPrefix;
      if ('hidden' in document) return '';
      prefixes.forEach((prefix) => {
        if (`${prefix}Hidden` in document) {
          correctPrefix = prefix;
        }
      });

      return correctPrefix || false;  //返回结果是'webkit', 'moz', 'ms', 'o' ,false
    }
    /**
     * 判断页面是否隐藏(进入后台)
     */
    function isPageHidden() {
      const prefix = getPagePropertyPrefix();
      if (prefix === false) return false;

      const hiddenProperty = prefix ? `${prefix}Hidden` : 'hidden';
      return document[hiddenProperty];  //返回结果是document.hidden,document.mozHidden,document.msHidden, document.webkitHidden,document.oHidden,是判断页面是否隐藏(进入后台)
    }
  </script>
</body>
</html>

 

 类似资料: