ios 關(guān)于支付寶添加桌面快捷方式的探究

前言

最近公司產(chǎn)品有個(gè)需求衰伯,為我們app中每個(gè)H5應(yīng)用實(shí)現(xiàn)添加桌面快捷方式的功能,與支付寶健康碼添加到桌面一樣张吉。所以特地研究了下相關(guān)產(chǎn)品中該功能的實(shí)現(xiàn)方式,如哈羅單車和支付寶烹植。

原理

在 app 里面通過 OpenURL 方式 跳轉(zhuǎn)到Safari 瀏覽器,打開一個(gè)引導(dǎo)頁面愕贡,然后點(diǎn)擊添加到主屏幕草雕,如下圖:

1241595556274_.pic.jpg

哈羅單車的實(shí)現(xiàn)

548341-fb5e56e2ad03a85a.png

哈羅單車在Safari中展示的是一整張引導(dǎo)圖片,為啥這里說一整張圖呢固以,因?yàn)橹Ц秾氄故镜囊龑?dǎo)頁是由多張圖片拼接而成墩虹。

哈羅單車不同的業(yè)務(wù)使用快捷方式,需要UI每次做一張引導(dǎo)圖憨琳,或者做一張通用圖片當(dāng)引導(dǎo)圖诫钓。

探索支付寶添加桌面快捷方式的實(shí)現(xiàn)

  1. 在關(guān)閉網(wǎng)絡(luò)的情況下,在支付寶健康碼中點(diǎn)擊"添加到桌面",跳轉(zhuǎn)到Safari瀏覽器篙螟,得到url

https://render.alipay.com/p/s/shortcut/index?appId=20000067&appName=杭州健康碼&appIcon=https://gw.alicdn.com/tfs/TB1nUmbzUT1gK0jSZFhXXaAtVXa-1024-1024.png&appUrl=https%3A%2F%2Fh5.dingtalk.com%2FhealthAct%2Findex.html

可以看出url為https://render.alipay.com/p/s/shortcut/index菌湃,后面參數(shù)有appId,appName遍略,appIcon惧所,appUrl

  1. 支付寶小程序也同樣有添加到桌面的功能,同樣的方式獲取到小程序跳轉(zhuǎn)到Safari瀏覽器中的url绪杏,這里以"體育服務(wù)"小程序?yàn)槔?/li>

https://render.alipay.com/p/s/shortcut/index?appId=2018073060792690&appName=體育服務(wù)&appIcon=https://appstoreisvpic.alipayobjects.com/prod/40ad067a-899a-4d31-89ba-c8758c405d36.png@120w.png

  1. 我們將支付寶健康碼頁面的url在mac上的Safari 瀏覽器打開下愈,查看元素


    截屏2020-07-23 下午6.49.10.png

可以看到頁面主要有index.html,index.js蕾久,還有幾張圖片


截屏2020-07-23 下午6.54.49.png

從這張圖我們可以猜到势似,支付寶是通過html和js將appIcon動(dòng)態(tài)嵌入到上圖相應(yīng)的位置,以此來實(shí)現(xiàn)safari中的引導(dǎo)頁面腔彰。

比較

支付寶的實(shí)現(xiàn)方式更加高明叫编,采用動(dòng)態(tài)拼接圖片的方式辖佣,業(yè)務(wù)方只需要傳入appIcon霹抛。

但是哈羅單車主要是靠app客戶端實(shí)現(xiàn)的,支付寶則是靠Web端來實(shí)現(xiàn)卷谈。

支付寶總體實(shí)現(xiàn)

通過對(duì)index.html和index.js的分析杯拐,得出支付寶實(shí)現(xiàn)方式如下:

app客戶端請(qǐng)求站點(diǎn)https://render.alipay.com/p/s/shortcut/index,并將appId世蔗,appName端逼,appIcon,appUrl等傳遞過去污淋。站點(diǎn)下index.html中接收url中攜帶的參數(shù)值顶滩,生成對(duì)應(yīng)的base64。

這樣app客戶端實(shí)現(xiàn)起來很簡(jiǎn)單寸爆,只需要打開url并將參數(shù)傳遞過去即可礁鲁。剩下的交由Web端開發(fā)來做盐欺。

   //支付寶實(shí)現(xiàn)應(yīng)用桌面快捷方式
   //url中不含中文時(shí),可以不對(duì)url進(jìn)行unicode轉(zhuǎn)碼仅醇,使用https://render.alipay.com/p/s/shortcut/index?appId=20000067&appName=123&appIcon=https://gw.alicdn.com/tfs/TB1nUmbzUT1gK0jSZFhXXaAtVXa-1024-1024.png&appUrl=https://Fh5.dingtalk.com/FhealthAct/Findex.html也可以
   NSURL *URL = [NSURL URLWithString:@"https://render.alipay.com/p/s/shortcut/index?appId=20000067&appName=123&appIcon=https://gw.alicdn.com/tfs/TB1nUmbzUT1gK0jSZFhXXaAtVXa-1024-1024.png&appUrl=https%3A%2F%2Fh5.dingtalk.com%2FhealthAct%2Findex.html"];
    
   UIApplication *application = [UIApplication sharedApplication];
   NSURL *URL = [NSURL URLWithString:urlString];

   if (@available(iOS 10.0, *)) {
       [application openURL:URL options:@{}
           completionHandler:^(BOOL success) {
       }];
   } else {
       [application openURL:URL];
   }

但是我身為一個(gè)ios開發(fā)冗美,可不可以不借助Web端開發(fā),自己?jiǎn)为?dú)來實(shí)現(xiàn)呢?
如果自己?jiǎn)为?dú)實(shí)現(xiàn)析二,需要解決哪些問題呢?

  1. 首先我從mac端瀏覽器中可以獲取到index.html和index.js和圖片等資源文件
  2. 我們需要將支付寶的index.html文件中原本通過url中獲取參數(shù)的形式改為app本地替換

步驟

1. 開發(fā)引導(dǎo)頁面

我們首先在app項(xiàng)目工程的根目錄下創(chuàng)建一個(gè)文件夾Web粉洼,文件夾下存放引導(dǎo)頁面


截屏2020-07-23 下午7.44.32.png

content.html

content.html是拷貝的是支付寶index.html文件,對(duì)其做了修改叶摄。

  1. 支付寶的index.html文件通過從瀏覽器的url中獲取參數(shù)属韧,通過js函數(shù)getQueryParams實(shí)現(xiàn)。因?yàn)槲业膮?shù)沒有放在URL中准谚,所以通過getQueryParams函數(shù)獲取到的參數(shù)都是空的挫剑。可以給appId等參數(shù)賦個(gè)默認(rèn)值柱衔,這樣就實(shí)現(xiàn)了本地參數(shù)替換樊破。

var query = getQueryParams(location.href);
var { appId = "TransitCodeAppId", appName= "TransitCodeAppTitle", appIcon = "TransitCodeAppIcon", appUrl = "ezvizsaas://123", scheme = "TransitCodeAppScheme" } = query;

修改過后的content.html代碼如下:

將其中的"企業(yè)螢石云"改成自己的主app名稱即可,也可使用字符串進(jìn)行動(dòng)態(tài)替換唆铐。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title id="J_desktopTitle">企業(yè)螢石云</title>
    <meta http-equiv="cache-control" content="no-cache" />
    <meta content="yes" name="apple-touch-fullscreen" />
    <meta content="yes" name="apple-mobile-web-app-capable" />
    <meta content="white" name="apple-mobile-web-app-status-bar-style" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0, minimal-ui" />
    <meta name="data-aspm" content="a192" />
    <link id="J_desktopIcon" sizes="114x114" rel="apple-touch-icon-precomposed" />
    <a href="TransitCodeAppScheme" id="qbt" style="display:none"></a>
  <script>
    var Base64 = function() {
    var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var _utf8_encode = function (string) {
      string = string.replace(/\r\n/g,"\n");
      var utftext = "";
      for (var n = 0; n < string.length; n++) {
        var c = string.charCodeAt(n);
        if (c < 128) {
          utftext += String.fromCharCode(c);
        } else if((c > 127) && (c < 2048)) {
          utftext += String.fromCharCode((c >> 6) | 192);
          utftext += String.fromCharCode((c & 63) | 128);
        } else {
          utftext += String.fromCharCode((c >> 12) | 224);
          utftext += String.fromCharCode(((c >> 6) & 63) | 128);
          utftext += String.fromCharCode((c & 63) | 128);
        }
      }
      return utftext;
    };
    this.encode = function (input) {
      var output = "";
      var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
      var i = 0;
      input = _utf8_encode(input);
      while (i < input.length) {
        chr1 = input.charCodeAt(i++);
        chr2 = input.charCodeAt(i++);
        chr3 = input.charCodeAt(i++);
        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;
        if (isNaN(chr2)) {
          enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
          enc4 = 64;
        }
        output = output +
        _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
        _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
      }
      return output;
    };
  };
  var getQuery = function(url) {
    var query = {};
    var hashParts = url.split('#');
    var urlParts = hashParts[0].split('?');
    if (urlParts.length > 1) {
      // 有query
      var queryParts = urlParts[1].split('&');
      for (var i = 0, len = queryParts.length; i < len; i++) {
        var items = queryParts[i].split('=');
        query[items[0]] = decodeURIComponent(items[1]);
      }
    }
    return query;
  };
  var htmlEncode = function(myStr) {
    myStr = myStr || '';
    //myStr = myStr.replace(/&/g, "&amp;");
    myStr = myStr.replace(/'/g, "&#39;");
    myStr = myStr.replace(/"/g, "&quot;");
    myStr = myStr.replace(/</g, "&lt;");
    myStr = myStr.replace(/>/g, "&gt;");
    myStr = myStr.replace(/\s/g, '&nbsp;');
    return myStr;
  };

  var iOSversion = function() {
    if (/iP(hone|od|ad)/.test(navigator.platform)) {
      var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
      return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
    }
  }

  var base64 = new Base64();
  var query = getQuery(location.href);
  var { appId = "TransitCodeAppId", appName= "TransitCodeAppTitle", appIcon = "TransitCodeAppIcon", appUrl = "ezvizsaas://123", scheme = "TransitCodeAppScheme" } = query;
<!--  var { appId, appName, appIcon, appUrl, scheme } = query;-->
  var sysVer = iOSversion();
 
  appName = htmlEncode(appName);
  appIcon = htmlEncode(appIcon);
  
  if ((appId || scheme) && appName && appIcon) {
    if (sysVer[0] < 14) {
      // 14 以下的版本走離線版
      var htmlTpl = "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title id=\"J_desktopTitle\">\u652F\u4ED8\u5B9D<\/title><meta content=\"yes\" name=\"apple-touch-fullscreen\"><meta content=\"yes\" name=\"apple-mobile-web-app-capable\"><meta content=\"white\" name=\"apple-mobile-web-app-status-bar-style\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,minimal-ui\"><meta name=\"data-aspm\" content=\"a192\"><link id=\"J_desktopIcon\" sizes=\"114x114\" rel=\"apple-touch-icon-precomposed\"><a href=\"TransitCodeAppScheme" id=\"qbt\" style=\"display:none\"><\/a>
<\/head><script>window._to={autoExpo:!0},document.documentElement.style.fontSize=100*document.documentElement.clientWidth\/375+\"px\"<\/script><script>!function(r){function n(t){if(a[t])return a[t].exports;var e=a[t]={exports:{},id:t,loaded:!1};return r[t].call(e.exports,e,e.exports,n),e.loaded=!0,e.exports}var a={};n.m=r,n.c=a,n.p=\"\",n(0)}([function(t,e){\"use strict\";!function(){if(!window.Tracert){for(var o={_isInit:!0,_readyToRun:[],_guid:function(){return\"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(\/[xy]\/g,function(t){var e=16*Math.random()|0;return(\"x\"===t?e:3&e|8).toString(16)})},get:function(t){if(\"pageId\"!==t)return this[t];if(window._tracert_loader_cfg=window._tracert_loader_cfg||{},window._tracert_loader_cfg.pageId)return window._tracert_loader_cfg.pageId;var e=document.querySelectorAll(\"meta[name=data-aspm]\"),r=e&&e[0].getAttribute(\"content\"),n=document.body&&document.body.getAttribute(\"data-aspm\"),a=r&&n?r+\".\"+n+\"_\"+o._guid()+\"_\"+Date.now():\"-_\"+o._guid()+\"_\"+Date.now();return window._tracert_loader_cfg.pageId=a},call:function(){var e,r=arguments;try{e=[].slice.call(r,0)}catch(t){var n=r.length;e=[];for(var a=0;a<n;a++)e.push(r[a])}o.addToRun(function(){o.call.apply(o,e)})},addToRun:function(t){var e=t;\"function\"==typeof e&&(e._logTimer=new Date-0,o._readyToRun.push(e))}},t=[\"config\",\"logPv\",\"info\",\"err\",\"click\",\"expo\",\"pageName\",\"pageState\",\"time\",\"timeEnd\",\"parse\",\"checkExpo\",\"stringify\",\"report\",\"set\",\"before\"],e=0;e<t.length;e++){!function(t){o[t]=function(){var e,r=arguments;try{e=[].slice.call(r,0)}catch(t){var n=r.length;e=[];for(var a=0;a<n;a++)e.push(r[a])}e.unshift(t),o.addToRun(function(){o.call.apply(o,e)})}}(t[e])}window.Tracert=o}}()}])<\/script><script src=\"index.js\"><\/script><style>*{margin:0;padding:0}body,html{height:100%;width:100%;overflow:hidden;background:#f3f2f2;text-align:center}.backguide{height:100%;width:100%}.backguide .enter{position:absolute;height:.44rem;width:3.43rem;background:#108ee9;border:0;font-size:.18rem;color:#fff;bottom:.52rem;left:.16rem;border-radius:.02rem}.backguide .tips{margin-top:1.29rem;font-size:.14rem;line-height:.2rem;color:#999}.backguide .icon{height:.71rem;width:.71rem;border-radius:.14rem}.backguide .appname{font-size:.18rem;line-height:.25rem;color:#333;margin-top:-.14rem}.main{color:#333;text-align:center}.subject{margin-top:.3rem;font-size:.2rem}.preview{margin-top:.36rem;position:relative}.preview .content{position:relative;margin:0 .55rem}.preview .bg{width:2.5rem;margin-left:.05rem;position:relative}.preview .icon{position:absolute;display:block;border-radius:21%}.preview .title{position:absolute;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.preview .icon.large{width:1.25rem;height:1.25rem;left:1.22rem;top:.42rem}.preview .title.large{width:1.5rem;left:1.13rem;top:1.7rem;font-size:.17rem}.preview .icon.small{width:.14rem;height:.14rem;left:.8rem;top:1.125rem}.preview .title.small{width:.16rem;left:.78rem;top:1.266rem;font-size:.025rem}.guide{width:100%;position:absolute;left:0;bottom:.3rem}.guide .content{position:relative;z-index:20;width:3.5rem;padding-top:.16rem;padding-bottom:.06rem;margin:0 auto;border-radius:.04rem;box-shadow:0 6px 15px rgba(0,0,0,.13);background:#fff;font-size:.14rem}.guide .tips{position:relative;z-index:20}.guide .icon{width:.2rem;height:.24rem;margin:0 .035rem .02rem;vertical-align:bottom}.guide .toolbar{width:100%;height:auto;margin-top:-.12rem;position:relative;z-index:10}.guide .arrow{width:.27rem;height:auto;position:absolute;left:50%;bottom:-.26rem;margin-left:-.135rem;z-index:10}<\/style><body data-aspm=\"b15502\"><div id=\"B_container\" class=\"backguide\" style=\"display:none\"><div class=\"tips\">\u4F60\u5373\u5C06\u8FDB\u5165<\/div><img class=\"icon\" src=\"" + appIcon + "\"><div class=\"appname\">" + appName + "<\/div><button class=\"enter\" onclick=\"jumpSchema()\" data-aspm=\"c37816.d76317\" data-aspm-expo data-aspm-param=\"appId=" + appId + "\">\u7ACB\u5373\u8FDB\u5165<\/button><\/div><div class=\"main\" id=\"J_container\" style=\"display:none\"><div class=\"subject\">\u6DFB\u52A0\u670D\u52A1\u5230\u684C\u9762<\/div><div class=\"preview\"><div class=\"content\"><img class=\"bg\" src=\"https:\/\/gw.alipayobjects.com\/zos\/rmsportal\/ceOiTFtaubdHkhSrNfPj.jpg\"> <img class=\"icon small\" src=\"" + appIcon + "\"> <img class=\"icon large\" src=\"" + appIcon + "\"><div class=\"title large\">" + appName + "<\/div><\/div><\/div><div class=\"guide\"><div class=\"content\"><p class=\"tips\">\u70B9\u51FB\u4E0B\u65B9\u5DE5\u5177\u680F\u4E0A\u7684<img class=\"icon\" src=\"https:\/\/gw.alipayobjects.com\/zos\/rmsportal\/XEbFrgamEdvSxVFOBeuZ.png\"><\/p><p class=\"tips\">\u5E76\u9009\u62E9<img class=\"icon\" src=\"https:\/\/gw.alipayobjects.com\/zos\/rmsportal\/IkKEhyTLQpYtqXMZBYtQ.png\">\u201C<strong>\u6DFB\u52A0\u5230\u4E3B\u5C4F\u5E55<\/strong>\u201D<\/p><img class=\"toolbar\" src=\"https:\/\/gw.alipayobjects.com\/zos\/rmsportal\/oFNuXVhPJYvBDJPXJTmt.jpg\"><\/div><img class=\"arrow\" src=\"https:\/\/gw.alipayobjects.com\/zos\/rmsportal\/FlBEnTRnlhMyLyVhlfZT.png\"><\/div><\/div><script>function jumpSchema(){window.location.href=window.AlipaySchema;window.Tracert.click(\"c37816.d76317\",{appId:'" + (appId || "") + "'})}function getIOSversion(){if(\/iP(hone|od|ad)\/.test(navigator.platform)){var e=navigator.appVersion.match(\/OS (\\d+)_(\\d+)_?(\\d+)?\/);return[parseInt(e[1],10),parseInt(e[2],10),parseInt(e[3]||0,10)]}}var appId='" + (appId || "") + "',appUrl='" + (appUrl || "") + "',scheme='" + (scheme || "") + "',href=\"\";if(href=scheme||\"TransitCodeAppScheme:\/\/platformapi\/startapp?appId=" + appId + "&chInfo=ch_desktop\"+(appUrl=appUrl?\"&url=\"+encodeURIComponent(appUrl):\"\"),window.AlipaySchema=href,window.navigator.standalone){var v=getIOSversion();13<=v[0]&&(document.getElementById(\"B_container\").style.display=\"block\",window.Tracert.call(\"expoCheck\")),window.location.href=window.AlipaySchema}else document.getElementById(\"J_container\").style.display=\"block\",document.getElementById(\"J_desktopTitle\").textContent=\"" + appName + "\",document.getElementById(\"J_desktopIcon\").setAttribute(\"href\",\"" + appIcon + "\")<\/script><\/body><\/html>";;
      var base64Tpl = base64.encode(htmlTpl);
      location.href = 'data:text/html;base64,' + base64Tpl;
    }
    //} else { // iOS 13 以上用在線頁面當(dāng)快捷方式哲戚,解決 -1 屏搜索點(diǎn)擊不動(dòng)的兼容性問題
    //  location.href = '' + '?appId=' + appId + '&scheme=' + scheme + '&appUrl=' + appUrl + '&appName=' + appName + '&appIcon=' + appIcon;
    //}
  } else {
    alert('參數(shù)錯(cuò)誤');
  }
  </script>
  <script>
    window._to = { autoExpo: true };
    document.documentElement.style.fontSize = document.documentElement.clientWidth * 100 / 375 + 'px';
    </script>
    <script>!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={exports:{},id:moduleId,loaded:!1};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.loaded=!0,module.exports}var installedModules={};return __webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.p="",__webpack_require__(0)}([function(module,exports){"use strict";!function(){if(!window.Tracert){for(var Tracert={_isInit:!0,_readyToRun:[],_guid:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=16*Math.random()|0,v="x"===c?r:3&r|8;return v.toString(16)})},get:function(key){if("pageId"===key){if(window._tracert_loader_cfg=window._tracert_loader_cfg||{},window._tracert_loader_cfg.pageId)return window._tracert_loader_cfg.pageId;var metaa=document.querySelectorAll("meta[name=data-aspm]"),spma=metaa&&metaa[0].getAttribute("content"),spmb=document.body&&document.body.getAttribute("data-aspm"),pageId=spma&&spmb?spma+"."+spmb+"_"+Tracert._guid()+"_"+Date.now():"-_"+Tracert._guid()+"_"+Date.now();return window._tracert_loader_cfg.pageId=pageId,pageId}return this[key]},call:function(){var argsList,args=arguments;try{argsList=[].slice.call(args,0)}catch(ex){var argsLen=args.length;argsList=[];for(var i=0;i<argsLen;i++)argsList.push(args[i])}Tracert.addToRun(function(){Tracert.call.apply(Tracert,argsList)})},addToRun:function(_fn){var fn=_fn;"function"==typeof fn&&(fn._logTimer=new Date-0,Tracert._readyToRun.push(fn))}},fnlist=["config","logPv","info","err","click","expo","pageName","pageState","time","timeEnd","parse","checkExpo","stringify","report","set","before"],i=0;i<fnlist.length;i++){var fn=fnlist[i];!function(fn){Tracert[fn]=function(){var argsList,args=arguments;try{argsList=[].slice.call(args,0)}catch(ex){var argsLen=args.length;argsList=[];for(var i=0;i<argsLen;i++)argsList.push(args[i])}argsList.unshift(fn),Tracert.addToRun(function(){Tracert.call.apply(Tracert,argsList)})}}(fn)}window.Tracert=Tracert}}()}]);</script>
    <script src="index.js"></script>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      html,body{
        height: 100%;
        width: 100%;
        overflow: hidden;
        background: #F3F2F2;
        text-align: center;
      }
      .backguide {
        height: 100%;
        width: 100%;
      }
      .backguide .enter {
        position: absolute;
        height: 0.44rem;
        width: 3.43rem;
        background: #108EE9;
        border: 0;
        font-size: 0.18rem;
        color: white;
        bottom: 0.52rem;
        left: 0.16rem;
        border-radius: 0.02rem;
      }
      .backguide .tips {
        margin-top: 1.29rem;
        font-size: 0.14rem;
        line-height: 0.2rem;
        color: #999999;
      }
      .backguide .icon {
        height: 0.71rem;
        width: 0.71rem;
        border-radius: 0.14rem;
      }
      .backguide .appname {
        font-size: 0.18rem;
        line-height: 0.25rem;
        color: #333333;
        margin-top: -0.14rem;
      }
      .main {
        color: #333;
        text-align: center;
      }
      .subject {
        margin-top: .3rem;
        font-size: 0.2rem;
      }

      .preview {
        margin-top:0.36rem;
        position: relative;
      }
      .preview .content {
        position: relative;
        margin: 0 0.55rem;
      }
      .preview .bg {
        width: 2.5rem;
        margin-left: 0.05rem;
        position: relative;
      }
      .preview .icon {
        position: absolute;
        display: block;
        border-radius: 21%;
      }
      .preview .title {
        position: absolute;
        text-align: center;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      .preview .icon.large {![548341-fb5e56e2ad03a85a.png](https://upload-images.jianshu.io/upload_images/1679132-bcfab3ad95c4a826.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

        width: 1.25rem;
        height: 1.25rem;
        left: 1.22rem;
        top: .42rem
      }
      .preview .title.large {
        width: 1.5rem;
        left: 1.13rem;
        top: 1.70rem;
        font-size: 0.17rem;
      }
      .preview .icon.small {
        width: 0.14rem;
        height: 0.14rem;
        left: 0.8rem;
        top: 1.125rem;
      }
      .preview .title.small {
        width: 0.16rem;
        left: 0.78rem;
        top: 1.266rem;
        font-size: 0.025rem;
      }

      .guide {
        width: 100%;
        position: absolute;
        left: 0;
        bottom: .30rem;
      }
      .guide .content {
        position: relative;
        z-index: 20;
        width: 3.50rem;
        padding-top: 0.16rem;
        padding-bottom: 0.06rem;
        margin: 0 auto;
        border-radius: 0.04rem;
        box-shadow: 0 6px 15px rgba(0, 0, 0, 0.13);
        background: #fff;
        font-size: .14rem;
      }
      .guide .tips {
        position: relative;
        z-index: 20;
      }
      .guide .icon {
        width: 0.20rem;
        height: 0.24rem;
        margin: 0 0.035rem 0.02rem;
        vertical-align: bottom;
      }
      .guide .toolbar {
        width: 100%;
        height: auto;
        margin-top: -0.12rem;
        position: relative;
        z-index: 10;
      }
      .guide .arrow {
        width: 0.27rem;
        height: auto;
        position: absolute;
        left: 50%;
        bottom: -0.26rem;
        margin-left: -0.135rem;
        z-index: 10;
      }
    </style>

  </head>
  <body data-aspm="b15502">
      
<div id="B_container" class="backguide" style="display: none;">
  <div class="tips">你即將進(jìn)入</div>
  <img id="B_icon" class="icon" src=""></img>
  <div id="B_appname" class="appname">企業(yè)螢石云</div>
  <button class="enter" onclick="jumpSchema()" data-aspm="c37816.d76317" data-aspm-expo data-aspm-param="appId=${appId}">立即進(jìn)入</button>
</div>
<div class="main" id="J_container" style="display: none;">
  <div class="subject">添加服務(wù)到桌面</div>
  <div class="preview">
    <div class="content">
      <img class="bg" src="https://gw.alipayobjects.com/zos/rmsportal/ceOiTFtaubdHkhSrNfPj.jpg">
      <img id="J_iconsmall" class="icon small" src="">
      <img id="J_iconlarge" class="icon large" src="">
      <div id="J_appname" class="title large">企業(yè)螢石云</div>
    </div>
  </div>
  <div class="guide">
    <div class="content">
      <p class="tips">點(diǎn)擊下方工具欄上的<img class="icon" src="https://gw.alipayobjects.com/zos/rmsportal/XEbFrgamEdvSxVFOBeuZ.png"></p>
      <p class="tips">并選擇<img class="icon" src="https://gw.alipayobjects.com/zos/rmsportal/IkKEhyTLQpYtqXMZBYtQ.png">“<strong>添加到主屏幕</strong>”</p>
      <img class="toolbar" src="https://gw.alipayobjects.com/zos/rmsportal/oFNuXVhPJYvBDJPXJTmt.jpg">
    </div>
    <img class="arrow" src="https://gw.alipayobjects.com/zos/rmsportal/FlBEnTRnlhMyLyVhlfZT.png">
  </div>
</div>
<script>
  function jumpSchema() {
      var lnk = document.getElementById("qbt").click();

<!--    window.location.href = window.AlipaySchema;-->
<!--    var appId = window.AlipayShortCutAppId;-->
<!--    window.Tracert.click('c37816.d76317', {appId: appId});-->
  }
  function getQueryParams(url) {
    var query = {};
    var hashParts = url.split('#');
    var urlParts = hashParts[0].split('?');
    if (urlParts.length > 1) {
      // 有query
      var queryParts = urlParts[1].split('&');
      for (var i = 0, len = queryParts.length; i < len; i++) {
        var items = queryParts[i].split('=');
        query[items[0]] = decodeURIComponent(items[1]);
      }
    }
    return query;
  };
  function getIOSversion() {
    if (/iP(hone|od|ad)/.test(navigator.platform)) {
      var v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
      return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
    }
  }
  var query = getQueryParams(location.href);
  var { appId = "TransitCodeAppId", appName= "TransitCodeAppTitle", appIcon = "TransitCodeAppIcon", appUrl = "ezvizsaas://123", scheme = "TransitCodeAppScheme" } = query;
  window.AlipayShortCutAppId = appId;
  var href = '';
  if (scheme) {
    href = scheme;
  } else {
    appUrl = appUrl ? '&url=' + encodeURIComponent(appUrl) : '';
    href = `TransitCodeAppScheme`;
  }

  window.AlipaySchema = href;
  if (window.navigator.standalone) {
    var v = getIOSversion();
    if (v[0] >= 13) {
      document.getElementById('B_container').style.display = 'block';
      document.getElementById('B_icon').setAttribute('src', appIcon);
      document.getElementById('B_appname').textContent = appName;
      window.Tracert.call('expoCheck');
    }
    var lnk = document.getElementById("qbt").click();

<!--    window.location.href = window.AlipaySchema;-->
  } else {
    document.getElementById('J_container').style.display = 'block';
    document.getElementById('J_desktopTitle').textContent = appName;
    document.getElementById('J_desktopIcon').setAttribute('href', appIcon);
    document.getElementById('J_iconsmall').setAttribute('src', appIcon);
    document.getElementById('J_iconlarge').setAttribute('src', appIcon);
    document.getElementById('J_appname').textContent = appName;

  }
</script>

  </body>
</html>

代碼中包含的一些字符串含義如下:
TransitCodeAppIcon:快捷方式在桌面的圖標(biāo)
TransitCodeAppTitle:快捷方式的名稱
TransitCodeAppScheme:跳轉(zhuǎn)頁面對(duì)應(yīng)的 scheme,比如 ezvizsaas://web/app?appId=1

通過這幾個(gè)字符串代替實(shí)際內(nèi)容艾岂,就可以做到動(dòng)態(tài)替換顺少。"企業(yè)螢石云"替換為自己的主app名稱即可。

這里通過 window.navigator.standalone 可以知道引導(dǎo)頁是否是全屏展示王浴,如果是全屏那么跳轉(zhuǎn)到 app 對(duì)應(yīng)頁面脆炎,非全屏則插入具體 HTML 內(nèi)容,展示引導(dǎo)內(nèi)容氓辣。

index.js

index.js中代碼過長(zhǎng)秒裕,由于簡(jiǎn)書對(duì)內(nèi)容長(zhǎng)度的限制,會(huì)導(dǎo)致無法發(fā)布钞啸,這里就不貼出來了几蜻。

index.js用的是支付寶的代碼,其中許多代碼是無用的体斩,不過不影響我們的業(yè)務(wù)梭稚,可以不用刪減。js中的一些關(guān)于"支付寶"的文本可以替換成我們的絮吵。

2. 使用 Data URI Scheme 技術(shù)將引導(dǎo)頁轉(zhuǎn)為一個(gè)字符串

代碼如下

//iconUrl:桌面圖標(biāo) appTitle:默認(rèn)app名稱
- (NSString *)oppcreateDesktopWithPreUrl:(NSString *)preUrl iconUrl:(NSString *)iconUrl appTitle:(NSString *)title scheme:(NSString *)scheme {
    if ([preUrl length] == 0) {
        return nil;
    }
    NSString *contentHtmlString = [self contentHtmlWithIconImageString:iconUrl title:title appScheme:scheme];
    NSData *data = [contentHtmlString dataUsingEncoding:NSUTF8StringEncoding];
    contentHtmlString = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; // base64格式的字符串;
    NSString *DataURIString = [NSString stringWithFormat:@"data:text/html;charset=utf-8;base64,%@",contentHtmlString];
    NSString *urlString = [NSString stringWithFormat:@"%@%@", preUrl, DataURIString];
    return urlString;
}

-(NSString *)contentHtmlWithIconImageString:(NSString *)iconImageString title:(NSString *)title appScheme:(NSString *)scheme{
    NSString *contentHtmlPath = [self getcontentHTMLTempletPath];
    NSString *contentHtmlString = [NSString stringWithContentsOfFile:contentHtmlPath encoding:NSUTF8StringEncoding error:nil];
    /*替換字符串*/
    contentHtmlString = [contentHtmlString stringByReplacingOccurrencesOfString:@"TransitCodeAppIcon" withString:iconImageString];
    contentHtmlString = [contentHtmlString stringByReplacingOccurrencesOfString:@"TransitCodeAppTitle" withString:title];
    contentHtmlString = [contentHtmlString stringByReplacingOccurrencesOfString:@"TransitCodeAppScheme" withString:scheme];
    return contentHtmlString;
}

- (NSString *)getcontentHTMLTempletPath{
    NSString * path = [[NSBundle mainBundle] pathForResource:@"content" ofType:@"html"];
    return path;
}

Data URI Scheme 技術(shù)就是將一個(gè)文件弧烤,圖片或者HTML文件等等,通過 Base64 加密等轉(zhuǎn)為字符串放到 HTML 里面直接加載蹬敲,這樣就不用請(qǐng)求服務(wù)器了暇昂。這里我們需要將上面介紹的本地的引導(dǎo) HTML 頁面轉(zhuǎn)為一個(gè)字符串想幻,后面要將這個(gè)字符串拼到服務(wù)器HTML url 后面。

看代碼可以知道话浇,通過 getcontentHTMLTempletPath 方法拿到本地 HTML 文件路徑脏毯,然后將里面內(nèi)容進(jìn)行替換為我們應(yīng)用的快捷方式相關(guān)內(nèi)容。然后將 HTML 內(nèi)容轉(zhuǎn)為 base64 編碼幔崖。最后通過將 base64 編碼按照特定格式拼接即可食店,這里是
[NSString stringWithFormat:@"data:text/html;charset=utf-8;base64,%@",contentHtmlString];以 data:text/html;charset=utf-8;base64, 開頭赏寇,后面我們可以看到存儲(chǔ)的時(shí)候也是保存的這個(gè)吉嫩。

3. 打開服務(wù)器 HTML 頁,用本地引導(dǎo)頁替換

用上面生成的字符串拼到服務(wù)器 HTML 頁面url (這里我是直接將 HTML 文件發(fā)給Web端同事嗅定,放到 Web網(wǎng)站上面然后拿到鏈接)后面自娩,代碼如下

//下面是添加快捷方式到桌面的相關(guān)代碼
- (void)addShortcutToDesk {
    NSString *baseUrl = @"https://www.baidu.com";  //這里將https://www.baidu.com替換為你自己的shortcut-middle.html所在的Web服務(wù)器地址
    NSString *preUrl = [NSString stringWithFormat:@"%@/shortcut-middle.html",baseUrl];
    preUrl = [NSString stringWithFormat:@"%@?dataurl=", preUrl];
    NSString *title = [self.webAppParams objectForKey:@"appName"];
    NSString *scheme = [NSString stringWithFormat:@"ezvizsaas://web/app?appId=%ld",self.webAppId];
    NSString *iconUrl = [self.webAppParams objectForKey:@"appLogo"];
    NSString *urlString = [self oppcreateDesktopWithPreUrl:preUrl iconUrl:iconUrl appTitle:title scheme:scheme];
    UIApplication *application = [UIApplication sharedApplication];
    NSURL *URL = [NSURL URLWithString:urlString];

    if (@available(iOS 10.0, *)) {
        [application openURL:URL options:@{}
           completionHandler:^(BOOL success) {
        }];
    } else {
        [application openURL:URL];
    }
}

這里服務(wù)器保存的 HTML頁面shortcut-middle.html很簡(jiǎn)單,加載后主要就是通過上面拼接的鏈接獲取本地引導(dǎo)頁內(nèi)容渠退,然后加載引導(dǎo)頁忙迁,shortcut-middle.html代碼如下

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
</body>
<script type="text/javascript">
    const dataurl = '';
    var url = location.search;
    if (url && url.length > 1 && url.indexOf('?dataurl=') !== -1) {
        url = url.replace("?dataurl=", "");
        window.location.replace(url);
    } else {
        window.location.replace(dataurl);
    }
</script>
</html>

通過 location.search 方法可以獲取到拼接好的url內(nèi)容,然后拿到我們拼上去的內(nèi)容碎乃,使用 window.location.replace 方法來重新進(jìn)行內(nèi)容替換姊扔,就可以展示我們本地的引導(dǎo)頁了。此時(shí)展示就是非全屏展示梅誓,跟從快捷鍵打開不一樣恰梢。

可能遇到的問題

  1. 在Safari中出現(xiàn)如下錯(cuò)誤提示 414 Request-URI Too Large

這是因?yàn)槲覀兊膗rl超過了nginx 服務(wù)器的限制,找Web端開發(fā)人員設(shè)置下nginx 服務(wù)器的配置即可梗掰。
參考https://blog.csdn.net/qq_41198398/article/details/83618204

  1. 添加到桌面的圖標(biāo)有黑邊
    這是因?yàn)槲覀儌魅氲腶ppIcon圖標(biāo)有圓角嵌言,appIcon用大圖512x512或1024x1024,不要圓角

參考資料

  1. 哈羅單車乘車碼添加桌面快捷方式
    http://www.reibang.com/p/15e36a3bd22c
  2. 支付寶健康碼和小程序添加桌面快捷方式
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末及穗,一起剝皮案震驚了整個(gè)濱河市摧茴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拥坛,老刑警劉巖蓬蝶,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尘分,死亡現(xiàn)場(chǎng)離奇詭異猜惋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)培愁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門著摔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人定续,你說我怎么就攤上這事谍咆『檀福” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵摹察,是天一觀的道長(zhǎng)恩掷。 經(jīng)常有香客問我,道長(zhǎng)供嚎,這世上最難降的妖魔是什么黄娘? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮克滴,結(jié)果婚禮上逼争,老公的妹妹穿的比我還像新娘。我一直安慰自己劝赔,他們只是感情好誓焦,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著着帽,像睡著了一般杂伟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仍翰,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天稿壁,我揣著相機(jī)與錄音,去河邊找鬼歉备。 笑死傅是,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蕾羊。 我是一名探鬼主播喧笔,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼龟再!你這毒婦竟也來了书闸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤利凑,失蹤者是張志新(化名)和其女友劉穎浆劲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哀澈,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牌借,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了割按。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膨报。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出现柠,到底是詐尸還是另有隱情院领,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布够吩,位于F島的核電站比然,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏周循。R本人自食惡果不足惜谈秫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鱼鼓。 院中可真熱鬧拟烫,春花似錦、人聲如沸迄本。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘉赎。三九已至置媳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間公条,已是汗流浹背拇囊。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留靶橱,地道東北人寥袭。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像关霸,于是被迫代替她去往敵國和親传黄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359