×

实例分析移动基础设施建设的概念及实践

消耗积分:2 | 格式:rar | 大小:0.4 MB | 2017-09-30

分享资料个

  自 iOS 及 Android 操作系统面世以来,移动端的浪潮以极快的速度席卷了整个世界。而在这样的时代演进过程中,技术的不断进步和创新给移动端的开发者们带来了一次又一次的挑战,比如不断增长的日活用户、不断刷新的版本分布、不断碎片化的机型分布以及不断碎片化的操作系统分布等。其中,随着日活用户的不断增长,微小概率的闪退使越来越多的用户在体验上大打折扣。同时, App 版本分布的不断增加亦加剧了代码的碎片化,致使功能管理愈加复杂。

  另一方面,手机型号与操作系统版本增多,也给移动端开发者造成了不少困扰。特别是 Android 平台,由于其开放性,导致了大量专项开发与优化,增加了代码管控的难度与复杂度,这也给移动开发者提出了更高的挑战。

  面对这一系列技术挑战,饿了么开启了移动基础设施的建设工程,以应对未来在移动端所面临的变化以及更多挑战。本文将从以下几个方面细谈饿了么移动基础设施建设的实践:

  移动端用户体验的分级;

  移动基础设施的概念;

  饿了么移动基础设施的建设实践。

  移动端用户体验的分级

  在这样一个以用户体验为王的时代,追求极致的用户体验往往是产品经理们追逐的最高梦想。因此,更简化的流程、更炫酷的动画总是成为每次产品更新迭代的主角,从不会褪去其“No.1”的光环。

  然而事实上,App 和过去 Web 时代的产品有着分明且本质的区别,比如用户更新意愿低、存在故障时难修复、视觉要求更高等。因此,在这样一场追求 App 极致体验的战争中,移动端的程序员兄弟们也应该是并肩作战的团队成员之一。因为技术上的用户体验,重要程度其实并不亚于以上的主角。

  Level 1 能用

  “能用”是每个用户对于使用 App 最基本的诉求。但当我们钟爱的 App 在使用过程中遇到诸如内存访问越界等特殊异常时,它往往会表现出一种最让我们反感的现象——闪退,移动端工程师们称之为“Crash”。

  这种让 App 完全无法使用的情况是用户最为恼火的场景。因此,如何最大限度地降低 App 的 Crash 率便成为了移动端技术人员们追求的用户体验最高核心。一款无法稳定运行的 App 当然无法得到用户的青睐!

  Level 2 可用

  “可用”,来自为用户提供稳定可靠的服务表述,这是比较容易理解的。设想一下,当我们正在选择美食时,却提示网络不可用;又或是页面上的菊花转了一分钟也没有出现想要的菜单,这些都会让我们对这样一款 App 产品失望。然而,“可用”却又是比较难实现的一个等级。当用户量少、业务简单时,网络的流量并不会对服务器端造成过大压力;而随着用户量的增长,不同地域、运营商、网络环境的一点点小的抖动,都会导致用户网络请求的不确定性。

  因此,如何最大限度地保障网络数据的稳定性,让我们的 App“可用”,是每天都在面临的挑战。

  Level 3 好用

  当我们的 App 达成了“能用”和“可用”的体验后,便来到了第三个层次——“好用”。

  与前两者体验需求的不同之处在于,“好用”除了在产品上要求流程简单便捷外,同时也对动画效果提出了更高的要求。在技术上这往往表现为每秒刷新的“FPS”帧率达到一定数值,页面切换无卡顿感,同时 App 能够在第一时间响应用户的触摸操作等。

  “好用”是对产品经理以及应用开发者在页面性能优化上的极致挑战,是一个长期追求,并不断努力的终极目标。

  移动基础设施的概念

  在以上的用户体验分层概念中,我们可以看到,这种用户体验的提升是不断打怪升级的战役,自然也少不了坎坷。在很长一段时间内,由于缺乏相应的测试和监控工具,App 端的问题一度只能通过后端监控来推断。

  为了更好地应对这样一系列与 App 相关的问题,我们提出了建设移动基础设施的构想。本节先来谈谈什么是移动基础设施。

  我们都知道,一款 App 的研发流程大致可以总结为:

  产品提出设计原型,UED 设计好视觉效果稿;

  工程师针对设计稿件进行编码、Debug,完成开发;

  工程师将开发好的程序进行打包、发布;

  App 产品正式上线并发布。

  而在最近几年,随着 iOS 和 Android 的技术进步,又出现了一系列线上热修复技术,因此在以上的 App 研发流程中再添一员——“HotPatch 修复”。

  再简单一点,可以直接归纳为:产品→开发→发布→线上→修复。

  而移动基础设施(Mobile Infrastructure,我们称之为“Minfra”)提供了除产品流程以外的一整套 App 研发管控的 SaaS 服务,即涉及开发、发布、线上和修复的全生命周期管控平台。

  饿了么移动基础设施建设的实践

  饿了么移动技术团队在面对以上流程时,搭建了名为“Grand”的总体管控平台,并分别建立相应的管控模块来进行保障工作。接下来将分模块谈谈每个流程的具体细节。

  持续集成

  持续集成是每个互联网公司基本都会部署的一道开胃菜,通过代码仓库的“Webhook”操作来自动触发一次编译构造,从而快速定位该代码集成是否会导致整个项目崩溃。这一步,是一项“掌控代码”的操作。

  对于持续集成的平台搭建来说,业内最常用的便是“Jenkins”。通过简单的“job”配置就可以轻松跑起一款 App 的持续集成。同时它还具备强大的可扩展性、简便的集群管控能力。也正因为这些优点,我们最终选择了“Jenkins”。

  然而在饿了么,由于 App 数量众多,且随时可能会有新的 App 成员加入。于是乎,在这样一种业务场景下,为每款 App 都单独进行一次自定义配置就成了奢望。如何使用一个“job”就能让所有 App 都跑起来,是我们首要解决的问题。

  得益于“Jenkins”平台的高度可自定义性,我们使用自己的 Python 脚本完成了持续集成的管控工作(如图 1 所示),同时使用“Grand”平台实现前端状态的监控工作。每款 App 使用一个预定义好的配置,来配合脚本内预定义的不同打包行为,最终汇总结果于“Grand”平台。

  持续集成平台的搭建,让团队合作的代码最终趋于稳定状态。完全自定义的脚本代码,让未来“静态代码检测分析”、“自动化测试平台”的加入成为可能,真正实现了对代码的掌控。

  实例分析移动基础设施建设的概念及实践

  图 1 Jenkins 管控页面 Release

  代码已经 OK,功能也已完备,接下来就是接受千万用户检验的时刻了。在此,“发布”这个看起来最寻常、最微乎其微的功能服务,却是发挥极大作用的功臣。我们前面提到,由于用户量巨大,99.99%的 Crash 率也能给成千上万的用户带来困扰。为了最大限度地减少这种困扰,一个具备“灰度发布”的服务被赋予了非凡的意义。

  实例分析移动基础设施建设的概念及实践

  图 2 App 发布灰度配置信息

  通过向特定的手机、城市甚至人群发送升级提醒,让部分、少量的用户升级至新 App(如图 2 所示),以观察这部分用户的 Crash 率成为了我们防止大规模 Crash 最主要的解决方案之一。在灰度的过程中,一旦发现 Crash 率快速上升,则可通过关闭“发布”渠道来防止事态的进一步升级,让“能用”这一用户体验等级得到掌控。

  看到这里,一定有读者会产生疑问,对于 Android 操作系统而言,以上方案或许可行,但对于 iOS 是否有更加可行的方案?

  的确如此,对于 iOS 平台而言,由于 App Store 的存在,往往很难达到灰度控制的目的。事实上,如果有条件的话,可以适当地采用 iOS 企业版来做灰度。不过这种方案并不被推荐,企业版也不是为该种目的而生,所以我们也并未完全采取这种方案。

  另一种 iOS 的灰度渠道方案,则是在各大越狱社区进行分发,同样可以绕过某些监管。当然,由于安全性原因,也具备一定风险。因此,如何取舍还是得看读者自己衡量。

  HotPatch

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !

'+ '

'+ '

'+ ''+ '
'+ ''+ ''+ '
'+ ''+ '' ); $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code ==5){ $(pop_this).attr('href',"/login/index.html"); return false } if(data.code == 2){ //跳转到VIP升级页面 window.location.href="//m.obk20.com/vip/index?aid=" + webid return false } //是会员 if (data.code > 0) { $('body').append(htmlSetNormalDownload); var getWidth=$("#poplayer").width(); $("#poplayer").css("margin-left","-"+getWidth/2+"px"); $('#tips').html(data.msg) $('.download_confirm').click(function(){ $('#dialog').remove(); }) } else { var down_url = $('#vipdownload').attr('data-url'); isBindAnalysisForm(pop_this, down_url, 1) } }); }); //是否开通VIP $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code == 2 || data.code ==5){ //跳转到VIP升级页面 $('#vipdownload>span').text("开通VIP 免费下载") return false }else{ // 待续费 if(data.code == 3) { vipExpiredInfo.ifVipExpired = true vipExpiredInfo.vipExpiredDate = data.data.endoftime } $('#vipdownload .icon-vip-tips').remove() $('#vipdownload>span').text("VIP免积分下载") } }); }).on("click",".download_cancel",function(){ $('#dialog').remove(); }) var setWeixinShare={};//定义默认的微信分享信息,页面如果要自定义分享,直接更改此变量即可 if(window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){ var d={ title:'实例分析移动基础设施建设的概念及实践',//标题 desc:$('[name=description]').attr("content"), //描述 imgUrl:'https://'+location.host+'/static/images/ele-logo.png',// 分享图标,默认是logo link:'',//链接 type:'',// 分享类型,music、video或link,不填默认为link dataUrl:'',//如果type是music或video,则要提供数据链接,默认为空 success:'', // 用户确认分享后执行的回调函数 cancel:''// 用户取消分享后执行的回调函数 } setWeixinShare=$.extend(d,setWeixinShare); $.ajax({ url:"//www.obk20.com/app/wechat/index.php?s=Home/ShareConfig/index", data:"share_url="+encodeURIComponent(location.href)+"&format=jsonp&domain=m", type:'get', dataType:'jsonp', success:function(res){ if(res.status!="successed"){ return false; } $.getScript('https://res.wx.qq.com/open/js/jweixin-1.0.0.js',function(result,status){ if(status!="success"){ return false; } var getWxCfg=res.data; wx.config({ //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId:getWxCfg.appId, // 必填,公众号的唯一标识 timestamp:getWxCfg.timestamp, // 必填,生成签名的时间戳 nonceStr:getWxCfg.nonceStr, // 必填,生成签名的随机串 signature:getWxCfg.signature,// 必填,签名,见附录1 jsApiList:['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: setWeixinShare.title, // 分享标题 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 type: setWeixinShare.type, // 分享类型,music、video或link,不填默认为link dataUrl: setWeixinShare.dataUrl, // 如果type是music或video,则要提供数据链接,默认为空 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ”按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ空间”按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); }); }); } }); } function openX_ad(posterid, htmlid, width, height) { if ($(htmlid).length > 0) { var randomnumber = Math.random(); var now_url = encodeURIComponent(window.location.href); var ga = document.createElement('iframe'); ga.src = 'https://www1.elecfans.com/www/delivery/myafr.php?target=_blank&cb=' + randomnumber + '&zoneid=' + posterid+'&prefer='+now_url; ga.width = width; ga.height = height; ga.frameBorder = 0; ga.scrolling = 'no'; var s = $(htmlid).append(ga); } } openX_ad(828, '#berry-300', 300, 250);