美团大众点评 Hybrid 化建设
SPKVickey
8年前
<h2>导语</h2> <p>上周末,精神哥去参加了好友小青在北京办的T沙龙,探讨移动端热更新相关的话题。Bugly曾为大家介绍过不少腾讯内部的热更新的框架,正好这次看到了美团,去哪儿以及微博同学在应用热更新方面的实践,整理出来发给大家,本周整理的是美团大众点评的吴卓同学分享的分享的 美团 Hybrid 化建设,其他的内容也会在后面陆续放出。</p> <p>Hybrid 是移动端热更新最常用的手段,限于 App Store 上架审核时间较长,美团大众点评也采取了该方案,欢迎来自美团大众点旅游业务 iOS 负责人 <strong>吴卓</strong> 分享 <strong>《美团大众点评 酒旅方面 Hybrid 化建设》</strong> 。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/f6621e92c12fa63d555c25e684d641df.jpg"></p> <p>首先自我介绍一下:</p> <p>大家好!我是吴卓,很高兴能来到 T 沙龙做这个分享,今天我将从 iOS 的角度跟大家一起探讨一下美团点评整体在 Hybrid 建设中做一些事情。</p> <p>我进入比较早,在 2011 年的 7 月份最早在美团实习。后来又继续出国读研,同时做一名兼职的开发者,在 2013 年的时候,做过 iOS 的独立开发,有很多人把它作为自己的一项事业去做。</p> <p>后来在 2014 年 12 月份重新加入美团,现在是旅游 iOS 的负责人。</p> <p>我负责的主要是住宿,度假,大交通,整个业务部门成立时间是相对比较晚,像住宿只做了三年,度假做了两年,大交通是去年才开始做的。快速迭代的能够给业务一个非常好的支持。</p> <p>今天的内容主要分成四个部分:</p> <ul> <li> <p>第一简单介绍一下为什么我们要做一个 Hybrid 化这样一个东西。</p> </li> <li> <p>第二部分是今天的重点部分,会讲一下我们在 Hybrid 化上做的一些事情。</p> </li> <li> <p>第三部分会简单回顾一下,我们做的一些内容和对现有的一些方案做一些对比。</p> </li> <li> <p>最后,如果大家有问题,可以做一些交流。</p> </li> </ul> <h2>一、为什么做 Hybrid 化?</h2> <p>第一个问题,我们为什么要做 Hybrid 这个东西,其实刚刚提到整个业务发展非常迅速。在迅速发展中,我们直接面临了以下两个非常棘手的问题:</p> <p>1. 客户端发版周期长</p> <p>第一个问题客户端发版周期比较长,相信大家应该有类似的感受,特别是在一个大公司里面,迭代是相对固定的周期。另外在 iOS 里面如果需要发版还需要 App Store 的审核。</p> <p>2. 前端资源严重不足</p> <p>第二个问题是我们公司一个现状,前端资源严重不足。</p> <p>解决方案</p> <p>首先,针对第一个问题,客户端发版周期长,我们希望通过一些手段脱离客户端发版限制。</p> <p>至于第二个问题,我们希望把现有的前端和客户端的同学完全结合起来,共同开发我们主要的一个 APP 。</p> <h2>二、Hybrid 化设计</h2> <p>接下来讲一下我们 Hybrid 化整体的设计,总体上我们是用一种 <strong>Native 和 H5 页面强混合的模式</strong> 。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/8d8e36ddd6daa7551c26d58e46e4fa14.png"></p> <p>如果在美团上买一个火车票,我不知道有没有同学买过。其实在美团上买一张火车票,有一部分是 Native 页面,有一些是 H5 页面,有一部分组件是 Native 做的,有一部分组件是 H5 做的。</p> <p>如果使用这种方式做的话,我们会遇到以下三个问题:</p> <ol> <li> <p>H5 和 Native 上线时间不一致,如何衔接?</p> </li> <li> <p>H5 和 Native 之间如何进行通信?</p> </li> <li> <p>H5 页面如何接近 Native 的体验?</p> </li> </ol> <p>1. H5 和 Native 上线时间不一致,如何衔接?</p> <p>第一个问题是说现在的页面里面既有 H5 页面,也有 Native 页面,Native 页面在 App Store 上面的, H5 相对比较灵活的。</p> <p>所以有个问题,当H5上线之后,客户端需要给H5提供一些跳转的入口,这个跳转的入口提供的应该是在不发版的情况下去给出的,能够通过这种灵活的配置去实现 H5 到 Native 的一个过渡。</p> <p>美团 APP 现状</p> <p>我们来讲一下美团 APP 的现状,早在 2014 年美团 APP 其实大部分页面是由 Native 编写的,只有一些活动的展示页面,是用 H5 形式的页面展示的。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/5b17b819784d1ef523a1841e68ea7625.jpg"></p> <p>为了实现页面之间的解耦合,每个页面其实会有一个 URL 进行标识,根据每次跳转到一个 Native 页面,现在很多公司都采用类似的方式去做。现在是这样的模式,那怎样让 Native 页面过渡到 H5 呢?</p> <p>我们的方案是对这个跳转去做一些扩展。本质上来说,客户端这边是从 URL 到 Native 页面的路由表,我们想办法对跳转的参数做的一些扩展,让他能够支持跳转到 H5 里面,甚至跳转到 URL 的页面。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/563cbbd2812c643b002f47102eb1f066.jpg"></p> <p>上图的这个配置能够通过后台进行下发,进行同时的更新,同时为了做这个更新,我们也为这个路由配置做了一个前端的展示页面。整体来说通过我们在原有的这种跳转模式下做了一些动态化的扩展,实现后续客户端发版之后能够从后台下发一些配置。</p> <p>举一个简单的例子:</p> <p>在美团 APP 买一个团购的订单,用户需要访问列表页,商家的详情页,创建订单,最后购买成功。</p> <p>如果我们有一些新版本的上线没办法支持展示这些新的产品,对一个新的产品做一个 H5 的产品详情和创立订单页面,把这个产品切换到走 H5 的流程最终的客户端发版走这种 H5 的流程。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/a520343760c5a502565a51e3ae1c2d66.jpg"></p> <p>这样无论是新的用户还有没有升级的老的用户,都及时的访问到我们最新的产品。</p> <p>小结</p> <p>在这儿简单回顾一下,我们做这个事情的一些设计思路。</p> <p>刚才说的配置下发只是在特殊情况下做的,因为这种情况是少数,不会每天所有的页面都做这种事情,所以我们并不会下发整个客户端里面的所有的配置,我们只是把一些需要更新的内容做一些回应,从后台下发下去。</p> <p>另外一点是说上层的使用方,我们内部会帮上层调用方,做好所有的相关的工作。</p> <p>2. H5 和 Native 之间如何进行通信?</p> <p>第二个问题,前端的 H5 页面和 Native 页面怎么更新,因为他两个是完全不同语言开发的,其实这个方案的话我们一般来说, 把 Native 和 H5 的通信机制约定为,称之为桥协议 。</p> <p><img src="https://simg.open-open.com/show/4c15041cbf2439136ec85dba76ae4ed0.jpg"></p> <p>这个桥协议,它是一个双向的通信方式。绿色部分是讲 NativeJS ,这个是比较直观的,在 WEB 应用里面直接可以调用这个方法。</p> <p>JS 调用 Native</p> <p>但是 JS 调用 Native 的方法其实系统没有提供一个很直接的方法,这个地方其实是我们需要解决的一个问题。</p> <p>基本上, JS 调用 Native 本质上就是,给客户端去传递一些消息,传递的消息格式其实是比较随意的,而且时间只要约定好就可以了。</p> <p>现在问题就是怎么去传?</p> <p>这个问题,我们当时在做的时候,其实调研了一下常规的方案来分析。有三个方案,我具体说一下:</p> <ul> <li> <p>第一个方案是通过 URL 拦截的方法</p> <p>这个什么意思呢?就是说,对于前端来说如果 JS 需要给应用传消息,一般会开一个 Server ,会访问一个地址,这个地址他的 Scheme 是一个特殊的 Scheme 。客户端这边会拦截到这种指令格式的 URL 需求,实现一个 JS 到 Native 传递消息的一个过程。</p> </li> <li> <p>第二个方案叫主动轮询</p> <p>对于 JS 他需要把给 Native 传递的消息,转化成一个 JSON ,客户端这边一般会开一个线程,每隔一段时间会调 JS 的方法,从这个方法里面把 JS 需要给 Native 传递的消息全部取出来,取出来之后再去做相应的操作。</p> </li> <li> <p>第三个就是 JSContext</p> <p>前端可以直接调用客户端本地的方法。</p> </li> </ul> <p><img src="https://simg.open-open.com/show/bccc2a7eb4ade5db03e2849845d05d1d.jpg"></p> <p>我们简单对比一下这三种方案,第一个方案是 URL 拦截,他的优点无论是哪种 WebView 都是支持这种方式的,但是它的 URL 拦截延时高一点。第二个方案,主动轮询,可以并发处理多条消息,但是如果在客户端性能开销大,第三个方案是我们现在正在用的,直接调用,但是他只支持 iOS 7 以上的系统。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/92dc14419fa6940750327d0962ebccaf.jpg"></p> <p>接下来讲一个非常重要的一个点,叫 <strong>模块化拆解</strong> ,其实像我们业务,每个业务,都需要在上面定制自己的桥协议,实际上这个也方便管理。 我们除了底下红色,刚才讲的消息通讯层以外,上面有模块化的管理方式,像客户端这边,右侧是客户端这边有模块的管理模范,各个业务可以自己把自己的模块注册在这个里面,对应的JS层也有底层的封装,左边每一个JS对应右边每一个模块,会做一些模块化拆解的工作。</p> <p>开发调试</p> <p>再说说我们怎么前端和客户端怎么去开发,调试方式,其实现在方式是说,如果我们需要新增一个桥协议的话,前端会先准备一个 Demo 页面,把这次需要新加的桥里面放在这个 Demo 页面里面,客户端基于这个 Demo 开发,会给前端打一个模拟器,前端会用这个模拟器安装包,自己完成剩余的链条开发工作,这样的好处是前端和客户端可以同时开发。</p> <p>小结</p> <p>简单回顾一下桥协议,桥协议通信用最简单最直接的方式进行调用,桥协议的实现,最关键一点支持可扩展的能力,开发调试我们希望前端和客户段可独立并行开发。</p> <p>3. H5 页面如何接近 Native 的体验?</p> <p>第三个问题是指我们的 H5 页面怎么去接近 Native 的体验,在体验差距上主要两个方面。</p> <p>页面渲染瓶颈</p> <p>第一个是前端的页面代码渲染,受限于 JS 的解析效率,以及手机硬件设备的一些性能。其实这个问题从应用开发的角度来说,是难以解决的。</p> <p>资源加载缓慢</p> <p>第二个方面是 H5 页面是从服务器上下发的,客户端的页面在内存里面,页面加载时间上面, H5 页面和 Native 相比是有些差距的,但是这个差距我们可以通过一些方式弥补的,比如说我们做了一些 <strong>资源预加载</strong> 的方案。</p> <p>在 <strong>资源预加载</strong> 方面,其实也很多方式,我主要列举了一些,基本上每种方式我们都尝试的做了。</p> <p>第一种方式是说使用 WebView 自身的缓存机制。</p> <p>如果我们在 APP 里面访问一个页面,短时间内再次访问这个页面的时候,会感觉到第二次打开的时候流畅很多,加载速度比第一次的时间要短。</p> <p>这个就是因为,苹果自己内部 Web 自身会做一些缓存,只要打开过的资源,他都会试着缓存在本地,第二次需要访问的时候他直接从本地读取,但是这个读取其实是不太稳定的东西,关掉之后,或者说这种缓存之后,系统会自动把它清掉,我们没法进行控制。</p> <p>基于这个 WebView 自身的缓存,有一种资源预加载方案,我们在应用启动的时候可以开一个像素的 WebView ,事先去访问一下我们常用的资源,后续打开页面的时候如果再用到这些资源他就可以从本地获取到,页面加载的时间会短一些。</p> <p>第二种方案是说,我们自己去构建,自己管理缓存。</p> <p>把这些需要预加载的资源放在 APP 里面,他可能是预制放进去的,也可能是后续下载的。</p> <p>问题在于前端这些页面怎么去缓存?</p> <p>两个方案,一个是,前端可以在 H5 打包的时候把里面的资源 URL 进行替换,这样可以直接访问本地的地址。客户端可以拦截到这些网页发出的所有请求做替换。</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/a54543cfa35d91f62e1cc98c5e9d8a78.jpg"></p> <p>这个是我们做的资源预加载的方案,采用的刚才说的第二种方案,每当这个 WebView 发起资源请求的时候,我们会拦截到这些资源的请求,去本地检查一下我们的这些静态资源本地离线包有没有。针对本地的缓存文件我们有些策略能够及时的去更新它。为了安全考虑的话我们也做了一些预下载和安全包的一些加密的工作。</p> <p>预加载方案的优势?</p> <ul> <li> <p>第一,我们拦截了 WebView 里面发出的所有的请求,但是并没有替换里面的前端应用的任何代码,前端这套页面代码可以在 APP 内,或者其他的 APP 里面都可以直接访问,他不需要为我们 APP 做定制化的东西。</p> </li> <li> <p>第二,这些 URL 请求,他会直接带上先前用户操作所留下的 cookie 而都能够留下来,因为我们没有更改资源的 URL 地址。</p> </li> <li> <p>第三,整个前端在用离线包的时候,缓存文件的时候是完全无感知的,前端只用管写一个自己的页面,客户端会帮他处理好这样一些静态资源预加载的问题,有这个离线包的话,他加载速度会变快很多,没有这些离线包加载速度会慢一些。如果版本不能跟他匹配的话,他的页面也不会发生什么问题。</p> </li> </ul> <p style="text-align: center;"><img src="https://simg.open-open.com/show/2dd82b0a35cba3cbaeca2de9157df4c2.jpg"></p> <p>这个是我们当时做完之后,做完资源预加载之后的一些效果。比如说,这个图里面可以看三个部分,一个是前端部分是没有用资源预加载的下面,深色的部分是有资源预加载的效果,可以看到,如果把有些资源打成离线包放在本地的话,其实他的加载时间是可以缩短很多的。</p> <p>另外一点可以横向的看,其实像一二三,或者是这边的一二三,三个页面,其实本质上这三个页面是一个购买流程人员,需要访问到的路径。</p> <p>举个例子,要进入第三个页面,他一定会先打开第二个页面,如果他打开第二个页面,他一定会先打开第一个页面。</p> <pre> 前置筛选页->车次列表页->车次详情页</pre> <p>所以可以看到,整体的加载时间是不断的缩短的。这个也就符合我们现在说的 Webview 自身是有一套缓存的。因为访问后面页面的时候有些资源其实在前面的页面已经访问过了,所以整个加载时间是不断递减的。</p> <p>总结一下今天 Hybrid 化讲的一些东西,包括我们做的动态路由切换,包括我们做的自定义桥协议,还有资源预加载的一些方案。</p> <p>Hybrid vs Native</p> <p>我们其实现在整个页面里面既有 Hybrid 页面也有 Native 页面,那么我们是怎么做区分的?</p> <p>一般来说Hybrid的项目一般是用在一些快速迭代试错的地方。另外包括有一些非主流产品的页面,我们倾向于用 Hybrid 的形式做.</p> <p>但是像前端购买一些交易环节,特别核心的流程的话,我们一般情况下会用 Native 的形式去写这些页面,去提升,达到一个极致的用户体验。</p> <h2>三、其他方案对比</h2> <p>最后想对比一下,简单聊一下我们现有的一些其他方案,当然这些方案,各个其他公司也正在去做。</p> <p>1. React</p> <p>第一个是 React 这边,现在做了一些尝试,因为 React 和安卓的平台差异性是比较小,如果安卓端写好代码的话,成本很低,在项目发展初期的话,很好的去应用了这样一种方式,减少成本。但是我们后面发现,当中也遇到了一些问题,如果其他同学有解决方案的话也欢迎分享一下。</p> <p>第一稳定性没有达到一个很好的标准,当然也有可能是我们在使用上还存在一些没有掌握的地方。</p> <p>第二个问题是人力的问题,我觉得可能比技术问题更复杂一点,就是说,其实现有市面上,我们很难在很短的时间内招到 10 个 iOS 的同学去做我们相应的开发。另外我们即使招到一些人,但是现有的公司里面培养体系,不太适合培养他们往更高层面发展。这个例子在后台比较常见,像我们现在美团点评是后台绝大部分都是用 Java 去写的,说白一点,就是说 Java 这个东西,还是比较好招人,好大规模的去扩展去做事的。</p> <p>2. Weex</p> <p>Weex 方面,我们内部有一些调研和学习,但是人力的问题还是很凸显。</p> <p>3. 动态模板化</p> <p>我们从业务发展的角度来说,也想获得一些动态性的一些东西。希望考虑说把有一些局部的模块能够通过后台下发的方式去做。我们的名字叫动态模板化,但是目前还是在做的阶段,如果其他同学有相同的想法的话可以共同做一些分享。</p> <h2>互动问答</h2> <p><strong>Q1:我有一个问题,刚才你说, JS 调用 Native 里面,有一个类似轮询。</strong></p> <p>吴卓:我那句话意思是说,一次只能拦截到一条消息,如果用轮询的方式的话,可以多条。因为最近应该很少有,最近几期很少有美团的同学来这儿讲课,如果大家对美团的其他的技术也兴趣的话也可以提出来,我如果知道的话尽量也跟大家解释一下。</p> <p><strong>Q2:这里哪一个页面是 Hybrid 的?</strong></p> <p>吴卓:您下的是最近的版本吗?举个例子机票里面选一个国际的城市,你能看到的就是, Hybrid 的页面。国际城市里面切换选择日期的时候,看到的就是 Hybrid 的页面。国际机票的列表也是用 Hybrid 走的。火车票里面以前是用 Hybrid 做的,现在的话,主流改成 Native 做的,当然如果出现一些紧急的情况,我们通过刚才的切换系统切换到原来的 Hybrid 上。</p> <p>另外如果您打开交通里面的船票也是 Hybrid 的形式。因为我是做大交通业务的,所以说可能比较熟悉一点,向您推荐的也是我们的产品。从您点击船票开始后面都是 Hybrid 的页面,当然这个页面里面有一些弹窗,有一些部分是Native做的。</p> <p><strong>Q3:你觉得 Hybrid 的模式和 Native 的模式,您觉得哪种可能是未来的发展趋势,技术上。</strong></p> <p>吴卓:这是一个好问题。我只说一下我个人的观点,不代表公司的观点。首先我觉得从一个用户体验的角度来说,我更希望把所有页面做成 Native 的,但是如果怎么说呢,我觉得比如像 WebView,我刚才说两个问题,一个是说稳定性的问题,还有一个人力资源的问题,如果这两个问题能解决的话,现在属于观望状态,我们其实可以朝着这方面去做。因为我个人的观点还是说,所有页面都能尽可能的做成 Native 。在做 Hybrid 上,我们想尽方式让它接近 Native 。</p> <p><strong>Q4:你们是如何管理 Hybrid 代码更新的呢?</strong></p> <p>吴卓:离线包的形式肯定会增加内存的大小。我们的团队做增量的更新,以减少这种资源包下载的流量,这是战略空间的问题。</p> <p>第二个是离线包里面有什么,最主要是一些静态资源文件,包括JS,CSS。基本上H5页面访问,就是在访问一个页面的时候需要加载这些资源我们都可以从本地给他获取。当然现在不是100%资源的离线化,一是考虑安全的因素,第二战略方面的原因有些技术没法做离线化。</p> <p> </p> <p style="text-align:center"> </p> <p>来自:http://mp.weixin.qq.com/s/rNGD6SotKoO8frmxIU8-xw</p> <p> </p>