H5打开APP指定页面爬坑记录

一:需求说明:

做过客户端开发的应该都遇到过分享APP内内容至第三方平台吧;
一般而言,主要是分享至QQ,微信,微博,QQ空间等平台;
对于一些国际化的APP可能会有分享至Facebook,twitter等平台的要求;
因为公司APP主要面对的是国内用户,所以下面只考虑国内的一些平台,国外不做兼容;

二:H5打开APP指定页面跳转要求

  • 1、用户已安装应用

    • 点击打开直接将相关参数传递至客户端,打开APP指定页面
  • 2、未安装应用

    • 提示用户下载,或跳转至App Store

三:通过H5打开APP的几种写法

方法一、利用HTML的a标签,通过设置href,在用户点击之后直接跳转

//直接在HTML中设置跳转地址
<a href="dsblockchain://">打开星域</a> 
//通过jQuery设置跳转
$("#openapp").attr("href", "dsblock://");

方法二、通过ifr.src设置跳转链接

var ifr = document.createElement('iframe');
ifr.src = config.scheme_IOS;
ifr.style.display = 'none';
document.body.appendChild(ifr);

方法三、通过设置window.locatin或者window.location.href打开

window.location.href = config.download_url;

四:根据需求初次实现方案如下:(后期改进采用方案二)

  • 方案一逻辑如下:

    • 所有逻辑判断,跳转都在分享出去的当前H5中完成;
    • 点击打开按钮
      • 已安装:打开指定页面,
      • 未安装:下载
    • 因为H5无法像客户端一样知道用户当前安装的程序,所以无法直接通过包名的方式打开APP
    • 处理方式:点击打开之后直接尝试打开客户端同事提供的scheme,然后设置一个延时任务,当用户在一定时间间隔离开了当前页面,就认为用户安装打开了APP,直接清除掉延时任务,不执行打开App Store,或者下载APP的任务;
    • 微信、微博无法直接打开APP以及下载应用,直接提示浏览器打开
  • 方案一代码如下

//打开app按钮
<span id="open"></span>

//点击之后的相关判断
 var config = {
        scheme_IOS: 'dsblock://',   //iOS发开给
        scheme_Adr: 'block://ds/wakeapp',   //Android开发给
        download_url: '下载链接',
        timeout: 2000
 };
 openclient();
 function openclient() {
     var startTime = Date.now();

     //---点击打开之后--尝试打开APP---start------
     var ifr = document.createElement('iframe');
     if (navigator.userAgent.match(/Android/i) != null) {//Android
        //传递参数给客户端
        ifr.src = (config.scheme_Adr + "?Type=2&"+"MatchId="+matchid);
     }else if(navigator.userAgent.match(/(iPhone|iPad|iPod|iOS)/i) != null) {//iOS
        ifr.src = (config.scheme_IOS + "?Type=2&"+"MatchId="+matchid);
     } else {//PC端无法打开app。点击后直接下载安卓安装包即可
        ifr.src = "下载链接";
     }
     ifr.style.display = 'none';
     document.body.appendChild(ifr);
    //---点击打开之后--尝试打开APP---end------

     //根据当前平台修改打开失败的跳转链接
     if (navigator.userAgent.match(/(iPhone|iPad|iPod|iOS)/i) != null) {//iOS跳至App Store
         config.download_url = 'APP在App Store的链接地址';
     } else if (navigator.userAgent.match(/Android/i) != null) {//安卓直接下载
         config.download_url = '下载链接';
     }else{//PC
         config.download_url = '下载链接';
     }
     
    //打开失败要执行的操作
     var t = setTimeout(function () {
         var endTime = Date.now();
         if (!startTime || endTime - startTime < config.timeout + 200) {
             window.location = config.download_url;
         }
     }, config.timeout);
     //当前页面失去焦点,清除延时任务,不提示用户下载
     window.blur = function () {
         clearTimeout(t);
     }
 }
  • 方案一存在的缺陷

    • 缺点:用户已安装APP,但是打开之后又被拉回到QQ或者浏览器页面,提示下载。
      • 原因:当用户打开APP后,window.blur方法没有回调,延时任务没被清除掉,还是提示用户下载了;
        而且因为这个提示,用户界面也会被拉回到QQ或者浏览器中;
    • 小改进:Android下载链接换成APP在应用宝的微下载链接,这样用户打开APP之后就算延时任务没被清除,也不会提示下载。只会打开APP
      • 测试后存在的问题:如果在QQ里面APP已经打开了,当用户打开APP之后,因为延时任务没被清除掉,会跳转到应用宝的推广链接,里面检测到用户已安装该APP,就直接打开了APP,但是只会打开APP并不会携带参数跳转至指定页。导致用户已跳转到指定页之后又被拉到首页了,所以还是无法满足要求直接PASS掉。
  • 方案一总结

    • 方案一经过一次更换下载链接为应用宝推广链接之后,还是无法满足要求,无法打开指定页面;
    • 因为要保证Android和iOS两端逻辑流程大致一样,所以经测试Android无法打开之后直接将该方案废弃,iOS也没有进行测试了;

五:最终方案

  • 方案一被pass掉后就一直在找相关的代替方法,在网上找了一家提供类似服务的公司,但是经过测试还是存在被拉回到QQ页面的问题;

  • 最后在参考斗鱼以及TapTap之后将方案调整如下:

    • 用户点击打开APP之后,将原来的跳转链接更换为一个中转的H5,当安卓用户在QQ里面打开之后不在分享的H5里面提示下载,而在中转页提供下载功能
    • 斗鱼中转页:http://live.qq.com/api/douyu
    • 注:斗鱼在微信里面也可以打开指定页面,应该是微信给斗鱼开了白名单
    • TapTap中转页:https://d.taptap.com/taptap/dispatch
    • 中转页功能,接收分享页的参数,当用户点击[打开APP继续]之后打开APP指定页
  • 本方案在开发过程中遇到的坑

  • 1、三种设置跳转地址方法的兼容问题;

    • 在将Android的跳转下载链接更换为跳转到中转页之后,Android基本满足要求,主要是iOS的兼容问题了
  • 2、设置跳转链接三种方式对比--------针对iOS平台

    • 方法一:a href="dsblock"
      • 手机QQ只能通过该方式跳转到APP里面,方法二和方法三都无法跳转,所以当当前是QQ里面打开,就只能通过这种方式设置打开APP的链接了
      • 手机QQ和Safari都可以跳转,但是如果没有安装的时候
    • 方法二:通过ifr.src设置跳转链接
      • 手机QQ无效,Safari浏览器无效
    • 方法三:除了手机QQ都可以
  • 代码如下

    HTML代码
      <div class="down_right">
      <a href="javascript:void(0);" onclick="fn();" id="openapp">打开星域</a></div>
    
    //Js代码
    var articleId = getParamValue("ArticleId");
    if (isIos()) {
      if (is_qqbrowser()) {//QQ里面需要通过这种方式才可以打开
          $("#openapp").attr("href", createUrl("dsblockchain://", {'Type': 1, 'PostType': 1, 'PostId': articleId}));
      }
    }
    
    function fn() {
      if (is_weixin() || isWeibo()) {//微信,微博都无法直接下载,只能打开中转页
          window.location.href = "中转页地址"
      } else {
          var config = {
              scheme_IOS: 'dsblock://',
              scheme_Adr: 'block://ds/wakeapp',
              download_url: '中转页',
              timeout: 1000
          };
          openclient();
          function openclient() {
    
              var startTime = Date.now();
              var ifr = document.createElement('iframe');
              if (navigator.userAgent.match(/Android/i) != null) {
                  ifr.src = createUrl("block://ds/wakeapp", {'Type': 1, 'PostType': 1, 'PostId': articleId});
              } else if (navigator.userAgent.match(/(iPhone|iPad|iPod|iOS)/i) != null) {
                  window.location.href = createUrl("dsblock://", {'Type': 1, 'PostType': 1, 'PostId': articleId});
              } else {//pc端
                  ifr.src = "apk下载地址";
              }
              ifr.style.display = 'none';
              document.body.appendChild(ifr);
              if (navigator.userAgent.match(/Android/i) != null){
                  config.download_url = createUrl("中转页.html",
                      {'Type': 1, 'ArticleId': articleId});
              }else if (navigator.userAgent.match(/(iPhone|iPad|iPod|iOS)/i) != null){
                  config.download_url = 'App Store地址';
              }else {//pc
                  config.download_url = 'apk下载地址';
              }
              var t = setTimeout(function () {
                  var endTime = Date.now();
                  if (!startTime || endTime - startTime < config.timeout + 2500) {
                      document.body.removeChild(ifr);
                      window.location.href = config.download_url;
                  }
              }, config.timeout);
              window.blur = function () {
                  clearTimeout(t);
              }
          }
      }
    
    }  
    
    //涉及到的相关方法
     function is_weixin() {
        var ua = navigator.userAgent.toLowerCase();
        if (ua.match(/MicroMessenger/i) == "micromessenger") {
            return true;
        } else {
            return false;
        }
    }
    
    function is_qqbrowser() {
        var ua = navigator.userAgent.toLowerCase();
        if (/mqqbrowser|qq/i.test(ua)) {
            return true;
        } else {
            return false;
        }
    }
    
    function isWeibo() {
        var ua = window.navigator.userAgent;
        return !!/__weibo__/.exec(ua);
    }
    
    function isIos() {
        return navigator.userAgent.match(/(iPhone|iPad|iPod|iOS)/i) != null;
    }
    
    function isSafari() {
        var ua = window.navigator.userAgent;
        return !!/Version[|\/]([\w.]+)(\s\w.+)?\s?Safari|like\sGecko\)\sMobile\/\w{3,}$/.exec(ua);
    }
    
    function createUrl(url, obj) {//拼接跳转链接及参数生成新的链接
        let params = [];
        for (let p in obj) {
            if (obj[p] != null && obj[p] != '') {
                params.push(p + '=' + obj[p])
            }
        }
        return url + '?' + params.join("&");
    }
    
    function getParamValue(name) {//获取地址栏指定参数的参数值
        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if (r != null) {
            return unescape(r[2]);
        } else {
            return null;
        }
    }
    
  • 整理快完成的时候,发现代码还可以优化,遂改为如下代码

var articleId = getParamValue("ArticleId");
if (isIos()) {
    if (isWeibo()||is_weixin()){//微博微信打不开APP,(所以要做区分,这里区分和在后面的fn方法中区分都行)直接将跳转地址设置为中转页
        //之所以这里区分出微信和微博,而不是直接通过延时任务的location.href直接跳转到App Store,
        //是因为跳转到中转页会提示用户在浏览器打开,可以打开指定页面
        $("#openapp").attr("href", createUrl("中转页地址", {'Type': 1, 'PostType': 1,'ArticleId': articleId}));
    } else{
        $("#openapp").attr("href", createUrl("dsblock://", {'Type': 1, 'PostType': 1, 'PostId': articleId}));
    }
} else if (navigator.userAgent.match(/Android/i) != null) {
    $("#openapp").attr("href", createUrl("block://ds/wakeapp", {'Type': 1, 'PostType': 1, 'PostId':articleId}));
} else {
    $("#openapp").attr("href", "apk下载地址");
}

function fn() {
    var config = {
        scheme_IOS: 'dsblock://',
        scheme_Adr: 'block://ds/wakeapp',
        download_url: '中转页',
        timeout: 1200
    };
    var startTime = Date.now();
    if (navigator.userAgent.match(/Android/i) != null) {
        config.download_url = createUrl("中转页地址",{'Type': 1, 'ArticleId': articleId});
    } else if (navigator.userAgent.match(/(iPhone|iPad|iPod|iOS)/i) != null) {
        config.download_url = 'App Store跳转地址';
    } else {//pc置空,因为前面已经设置了,要不然会提示下载多个文件,不过只要调用了window.location,href都会刷新当前页面
        config.download_url = '';
    }
    var t = setTimeout(function () {
        var endTime = Date.now();
        if (!startTime || endTime - startTime < config.timeout + 2500) {
            window.location.href = config.download_url;
        }
    }, config.timeout);
    window.blur = function () {
        clearTimeout(t);
    }
}

六:中转页部分代码及网页截图

中转页.png
//如果是微博,微信,则隐藏打开按钮,显示下载按钮,显示打开提示文字,其余平台隐藏提示文字,显示下载和打开按钮
tip 顶部提示文字:已安装,在浏览器打开
open: 打开APP,继续按钮
<a class="open-button download-button" id="install">立即安装</a>
<p><a class="open-button" id="open">打开 APP 继续</a></p>
if (browserName === "Wechat" || browserName === "Weibo") {
    showDiv("#tip", true);
    showDiv("#open", false);
} else {
    showDiv("#tip", false);
    showDiv("#open", true);
}

//获取地址栏携带的参数,获取之后传递给客户端
var params = window.location.href.split("?")[1];
console.log(params);
if (isIos()) {//iOS
    $("#open").attr("href", "dsblockchain://"+"?"+params);
    $("#install").attr("href", "App Store推广链接");
} else {//安卓
    $("#open").attr("href", "block://ds/wakeapp"+"?"+params);
    if (browserName === "Wechat") {//微信无法下载,打开应用宝推广页
        $("#install").attr("href", "应用宝推广链接");
    } else {
        $("#install").attr("href", "APK下载地址");
    }
}

function showDiv(divName, isShow) {
    if (isShow) {
        $(divName).show();
    } else {
        $(divName).hide();
    }
}

总结:

  • 从开始调试到现在最终方案的形成差不多花了1周;
  • 主要的坑就是window.blur方法没有回调,a标签设置跳转路径,iframe打开连接的兼容问题
  • 上面的这些坑估计H5开发大佬分分钟解决,落魄的Android开发流下了没有技术的泪水。。


    129156.jpg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容