美文网首页
ios 关于支付宝添加桌面快捷方式的探究

ios 关于支付宝添加桌面快捷方式的探究

作者: 不仅仅是个程序猿 | 来源:发表于2020-07-24 11:47 被阅读0次

    前言

    最近公司产品有个需求,为我们app中每个H5应用实现添加桌面快捷方式的功能,与支付宝健康码添加到桌面一样。所以特地研究了下相关产品中该功能的实现方式,如哈罗单车和支付宝。

    原理

    在 app 里面通过 OpenURL 方式 跳转到Safari 浏览器,打开一个引导页面,然后点击添加到主屏幕,如下图:

    1241595556274_.pic.jpg

    哈罗单车的实现

    548341-fb5e56e2ad03a85a.png

    哈罗单车在Safari中展示的是一整张引导图片,为啥这里说一整张图呢,因为支付宝展示的引导页是由多张图片拼接而成。

    哈罗单车不同的业务使用快捷方式,需要UI每次做一张引导图,或者做一张通用图片当引导图。

    探索支付宝添加桌面快捷方式的实现

    1. 在关闭网络的情况下,在支付宝健康码中点击"添加到桌面",跳转到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,后面参数有appId,appName,appIcon,appUrl

    1. 支付宝小程序也同样有添加到桌面的功能,同样的方式获取到小程序跳转到Safari浏览器中的url,这里以"体育服务"小程序为例

    https://render.alipay.com/p/s/shortcut/index?appId=2018073060792690&appName=体育服务&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动态嵌入到上图相应的位置,以此来实现safari中的引导页面。

    比较

    支付宝的实现方式更加高明,采用动态拼接图片的方式,业务方只需要传入appIcon。

    但是哈罗单车主要是靠app客户端实现的,支付宝则是靠Web端来实现。

    支付宝总体实现

    通过对index.html和index.js的分析,得出支付宝实现方式如下:

    app客户端请求站点https://render.alipay.com/p/s/shortcut/index,并将appId,appName,appIcon,appUrl等传递过去。站点下index.html中接收url中携带的参数值,生成对应的base64。

    这样app客户端实现起来很简单,只需要打开url并将参数传递过去即可。剩下的交由Web端开发来做。

       //支付宝实现应用桌面快捷方式
       //url中不含中文时,可以不对url进行unicode转码,使用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];
       }
    

    但是我身为一个ios开发,可不可以不借助Web端开发,自己单独来实现呢?
    如果自己单独实现,需要解决哪些问题呢?

    1. 首先我从mac端浏览器中可以获取到index.html和index.js和图片等资源文件
    2. 我们需要将支付宝的index.html文件中原本通过url中获取参数的形式改为app本地替换

    步骤

    1. 开发引导页面

    我们首先在app项目工程的根目录下创建一个文件夹Web,文件夹下存放引导页面


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

    content.html

    content.html是拷贝的是支付宝index.html文件,对其做了修改。

    1. 支付宝的index.html文件通过从浏览器的url中获取参数,通过js函数getQueryParams实现。因为我的参数没有放在URL中,所以通过getQueryParams函数获取到的参数都是空的。可以给appId等参数赋个默认值,这样就实现了本地参数替换。

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

    修改过后的content.html代码如下:

    将其中的"企业萤石云"改成自己的主app名称即可,也可使用字符串进行动态替换。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title id="J_desktopTitle">企业萤石云</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 以上用在线页面当快捷方式,解决 -1 屏搜索点击不动的兼容性问题
        //  location.href = '' + '?appId=' + appId + '&scheme=' + scheme + '&appUrl=' + appUrl + '&appName=' + appName + '&appIcon=' + appIcon;
        //}
      } else {
        alert('参数错误');
      }
      </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://img.haomeiwen.com/i1679132/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">你即将进入</div>
      <img id="B_icon" class="icon" src=""></img>
      <div id="B_appname" class="appname">企业萤石云</div>
      <button class="enter" onclick="jumpSchema()" data-aspm="c37816.d76317" data-aspm-expo data-aspm-param="appId=${appId}">立即进入</button>
    </div>
    <div class="main" id="J_container" style="display: none;">
      <div class="subject">添加服务到桌面</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">企业萤石云</div>
        </div>
      </div>
      <div class="guide">
        <div class="content">
          <p class="tips">点击下方工具栏上的<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:快捷方式在桌面的图标
    TransitCodeAppTitle:快捷方式的名称
    TransitCodeAppScheme:跳转页面对应的 scheme,比如 ezvizsaas://web/app?appId=1

    通过这几个字符串代替实际内容,就可以做到动态替换。"企业萤石云"替换为自己的主app名称即可。

    这里通过 window.navigator.standalone 可以知道引导页是否是全屏展示,如果是全屏那么跳转到 app 对应页面,非全屏则插入具体 HTML 内容,展示引导内容。

    index.js

    index.js中代码过长,由于简书对内容长度的限制,会导致无法发布,这里就不贴出来了。

    index.js用的是支付宝的代码,其中许多代码是无用的,不过不影响我们的业务,可以不用删减。js中的一些关于"支付宝"的文本可以替换成我们的。

    2. 使用 Data URI Scheme 技术将引导页转为一个字符串

    代码如下

    //iconUrl:桌面图标 appTitle:默认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 技术就是将一个文件,图片或者HTML文件等等,通过 Base64 加密等转为字符串放到 HTML 里面直接加载,这样就不用请求服务器了。这里我们需要将上面介绍的本地的引导 HTML 页面转为一个字符串,后面要将这个字符串拼到服务器HTML url 后面。

    看代码可以知道,通过 getcontentHTMLTempletPath 方法拿到本地 HTML 文件路径,然后将里面内容进行替换为我们应用的快捷方式相关内容。然后将 HTML 内容转为 base64 编码。最后通过将 base64 编码按照特定格式拼接即可,这里是
    [NSString stringWithFormat:@"data:text/html;charset=utf-8;base64,%@",contentHtmlString];以 data:text/html;charset=utf-8;base64, 开头,后面我们可以看到存储的时候也是保存的这个。

    3. 打开服务器 HTML 页,用本地引导页替换

    用上面生成的字符串拼到服务器 HTML 页面url (这里我是直接将 HTML 文件发给Web端同事,放到 Web网站上面然后拿到链接)后面,代码如下

    //下面是添加快捷方式到桌面的相关代码
    - (void)addShortcutToDesk {
        NSString *baseUrl = @"https://www.baidu.com";  //这里将https://www.baidu.com替换为你自己的shortcut-middle.html所在的Web服务器地址
        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];
        }
    }
    

    这里服务器保存的 HTML页面shortcut-middle.html很简单,加载后主要就是通过上面拼接的链接获取本地引导页内容,然后加载引导页,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内容,然后拿到我们拼上去的内容,使用 window.location.replace 方法来重新进行内容替换,就可以展示我们本地的引导页了。此时展示就是非全屏展示,跟从快捷键打开不一样。

    可能遇到的问题

    1. 在Safari中出现如下错误提示 414 Request-URI Too Large

    这是因为我们的url超过了nginx 服务器的限制,找Web端开发人员设置下nginx 服务器的配置即可。
    参考https://blog.csdn.net/qq_41198398/article/details/83618204

    1. 添加到桌面的图标有黑边
      这是因为我们传入的appIcon图标有圆角,appIcon用大图512x512或1024x1024,不要圆角

    参考资料

    1. 哈罗单车乘车码添加桌面快捷方式
      https://www.jianshu.com/p/15e36a3bd22c
    2. 支付宝健康码和小程序添加桌面快捷方式

    相关文章

      网友评论

          本文标题:ios 关于支付宝添加桌面快捷方式的探究

          本文链接:https://www.haomeiwen.com/subject/jmyhlktx.html