蘑菇街移动端混合开发体系的研发与实践

架构师 来源:互联网架构师 151℃ 1评论

蘑菇街无线跨平台技术团队负责人王兴楠于HTML5调优最佳实践专场发表了题为《HTML5与原生的体验融合—蘑菇街移动端混合开发体系的研发与实践》的演讲,现场解读了蘑菇街移动端混合开发体系的演进历程与架构。


首先简单自我介绍一下,我叫王兴楠,现在在美丽联合集团负责混合开发体系的建设,目前专注于基于动态跨平台技术的下一代移动开发体系。之前在毕业之后就加入上海Intel,从事多年浏览器内核和Web引擎研发工作,同时我也是Crosswalk项目的早期开发者。


大家有些人可能知道在年初的时候蘑菇街、美丽说合并成联合集团


上面几个logo就是我们主营的一些App,比如淘世界、番茄炒蛋等。现在整个平台对所有业务进行了统一的支撑。图下方是我们这些App中的一些截图,可以看到画红框的都是HTML5的入口。大家知道电商的场景对于HTML5是有依赖的,它非常注重运营,还要非常快速的更新内容,所以现在在整个业务中HTML5的使用场景是非常多的。


下图是整个HDP平台的结构,分为两部分,一是在App中或者在前端的Runtime是一个运行环境,还有HDP的后台。下层就是App的框架,我们会做一些离线控制、开关控制数据统计、多业务平台切换。在渲染层面现在平台上支持多种内核的切换,旁边会支持扩展能力,App中每个不同场景对应的扩展能力也是不一样的,光微信这一个平台上就有40多个扩展插件,常用的比如相机、分享、通信录、设备信息、图片上传等等。最底层是基础层,我们并没有完全开发这些东西,比如说网络、图片、统计等,还有整个App的基础设施。


后面的部分是整个平台的后台,后台提供了对前端的配置管理、权限管理、插件管理,插件管理这里可以强调一下,它现在还是面临一些很复杂的场景,因为现在App非常多,面临的业务线也非常多,他们对插件或者其它功能都是自己来开发的,但是自主开发的东西不够规范,质量有问题,但最终还是会运行在我们的平台上,这样的话其实是很难管控的。最后我们是通过自建的插件管理平台,把最开始的需求提出、接口定义生成几家功能,最后提交到平台做自动化测试,只有通过的插件才能运行,将整个流程串起来做一个管控。后面还有数据展示、离线化的功能。



优化实践—独立内核

第一个优化点就是基于独立内核的体验优化,其实大家如果在前端开发偏向移动端的话,经常会碰到一个痛苦的问题比如说系统本身是有问题的,包括国内一些定制机有的性能也很有问题,另外还有安全性的问题,而且基本不更新。还有一个功能上的限制,包括一些无法修复的Bug,这是我们遇到很头疼的问题,国内特殊品牌的定制机同样的东西在它上面就特别容易触发Bug,也不知道改什么根本无法修复,想绕也绕不开。


解决这个问题大家的思路都是一致的,就是打包独立内核进去,比如像腾讯的X5、阿里、UC的内核,这些算是闭源的。再就是开源项目就是Crosswalk项目,我们自己在蘑菇街做也是基于Crosswalk项目做的一个二次开发,然后加上很多的功能定制。集成独立内核是能够很好的解决这样的问题的,但是它往往本身也会引入额外的问题,主要是包的大小,另外一个就是它本身也具有稳定性问题,因为内核更多是谷歌开发的,大部分是在国外环境下,在国内环境很多做得不好,因为国内安卓的碎片化更严重。还有就是内核性能可能很好,但是很费资源。


面临这几个问题我们解决方案是这样的,首先对于包大小我们采取减包的处理,首先就是做裁剪,因为这个内核是完全根据自己的业务定制的,可以去掉一些不必要的功能,或者如果在系统层面有API一些功能是可以换掉的,这个就能省掉几兆的空间。另外我们会做压缩,其实最后剩下的包还是很大,方案就是通过动态加载,并不是说立马把功能使用起来,而是App运行起来去把包下载下来再用动态加载起来再用。


面对稳定性问题就要自己修复,整个代码是开源的就维护这个代码,我们是具备修复这种线上能力的,还有对一些不好控制和不好修复的问题我们提供很多的方案,比如说白名单、黑名单控制,还有整个活性机制做一个内核动态切换缓解的问题。


对于资源消耗的问题,我们其实是要通过一些策略控制的,首先要控制整个内核的使用数量,当内核多的时候系统肯定扛不住,所以一般会限制使用内核页面层级,如果超过这样的层级就不会让你使用新的内核。另外就是对整个页面运行起来,对当前系统状态进行监控,像内存达到一定的阈值可能就不会再使用这样的内核了。


这个是我们动态加载整个的流程


首先我们将内核做一个拆分,这里拆分为两个部分,首先是加一段代码,前面的打包成APK,剩下内容的会打包成一个SO,这两部分都会通过下载的方式加载App公共资源区,任意一个App启动之后,当你需要使用内核的时候会去公共资源区然后动态加载起来,再去使用最新的内核。为什么放到公共资源区呢?有两个原因,一是这个包本身还是非常大的,如果每个App都单独下载会耗费用户流量,这样做可以很好的节省用户流量。


这个是我们线下的一个性能监测


换了新的内核之后会看到白屏时间和首屏时间都是有明显优势的,其实说到这里本身安卓系统也在不断地升级,但是它本身还是没有最新的内核性能那样好的,所以其实也会在线上看有一点差距。实际上在蘑菇街的业务场景中,很多用户都是三四线城市的妹子,她们用的手机整体上在整个手机分布上还是比较低端的,好多的系统比较老,所以对于老的WebView在业务占比还是比较高的,对于用户体验非常明显。


对于Crosswalk,我们也拿自己的产品中跟X5进行了对比,我们选取了一些业务场景,基本看其实在启动时间上要比X5内核好一些,但是内存消耗上我们要差一点。



优化实践—集成WKWebView

集成WKWebView是有很多优势的

  • 因为很多运行不是跑在进程里的,对进程消耗非常低,APP内的内存使用量降低。

  • 原来的WKWebView是有js锁的,为了保证用户体验,js被暂停执行,我们就会面临一些问题,比如说滚动墙的时候确实要考虑到一些商品的暴光,但是后来WKWebView出来之后这个限制就没有了。

  • WKWebView本身的渲染性能得到了很大的提升。

  • 把WKWebView放到另外一个进程,那么它出现的问题不会导致你当前App崩溃。


下图是一些统计数据,当我们打开一个页面Kian内存占用情况,可以看到如果是UWebView,每新增一个页面大致会增加在十几兆左右,如果是WKWebView基本在一兆左右。、我们对于常见的一些内核层面进行crash的统计,看到用到WKWebView的数量级是有所降低的。

在用WKWebView时也会面临一些问题:

  • 无法同步App中的Cookie,方案是通过JS注入的方式把Cookie带进去再读出来。

  • WKWebView导航的时候是默认PageCache的,无法禁止。解决方式就是说现在每次跳转都要强制刷新页面。

  • JS重复注入有Bug,同样的代码被注入两次还是有问题的,我们就需要把前一次注入先清空再注入。

  • WKWebView本身问题就是页面真正关闭的时候资源并不释放,一个页面关掉了但是实际上页面有弹窗,解决方法就是关闭WKWebView之前要给它注入加载一段空的字符串,使页面完全销毁掉。

  • 目前WKWebView比较大的限制就是无法做网络拦截,这个问题目前我们无法解决,只能避免依赖的场景。


优化实践—新动态跨平台框架XCore

前面是介绍在整个渲染层面和内核层面的优化,下面想介绍一下自研的一套新的动态跨平台框架,目前的移动开发有两大痛点,一个是动态性的问题,还有一个跨平台开发的能力缺失。现在的趋势随着业界的React Native、Weex、LuaView为等新的技术框架发展,移动端动态跨平台能力的建设已经成为业界的趋势。从整个集团业务场景来说我们需要有这样一个框架支撑业务,保持在开发效率上的优势,保持在业界的竞争力。


所以我们做这样的框架大致是这样几个目标,首先至少现在不想去解决硬件问题,所以要以业务场景驱动做这样的事情,完全为集团产品的特定业务场景进行定制。另外这个框架需要保证是跨平台的,是一份代码开发三端(HTML5,Android,iOS)运行,还有要具备很好的动态性,不用随App版本发布而更新内容。另外我们需要既轻量又高效,此外还有完善的基础设施,包括工具链、调试工具、开发工具、后台等满足业务开发人员的需求。


这里可以分享一下做这个事情我们对于业务场景的思考,首先美丽集团算是一个社会化电商,或者也是一个比较典型的电商场景,其实在大部分的应用场景还是属于比较偏展示性的页面,但是实际上现在社交化的元素越来越多,还是会有一些交互型页面,比如说Feed流、图片编辑、聊天、直播等等,这是在实际上我们经常应用场景的两样分类。


谈到业务场景驱动的开发模式,我们由简单的交互场景向复杂的交互场景推进,这样做的难度至少在技术推进上相对低一点,然后我们会用单一的场景向多场景推进,像会场的场景,如果我们要支持它,全都换掉是最好的,但如果换不掉就涉及到传统的HTML5页面和新页面的交互问题。还有什么样的业务需求最高?对动态性、体验以及成本的要求,符合开发成本的就会最快速度开发出来,所以我们是优先考虑这样的业务,我们自己的框架本身的演进也是基于这个场景去做的。


这是我们框架的设计结构


我们首先设计了一个微浏览器内核设计。最底层是用App的基础框架,对于扩展能力的支持,首先是支持自定义图片的扩展,会提供一些技术组件,但是这些组件并不能完全解决开发能力。还有原有一些老业务大量都是用的老插件,我们把整个插件机制在平台上跑起来,然后附用到业务上。以后会考虑到更多的插件就是React。好处就是会非常灵活,对于前端的支持并不绑定,理论上都是可以支持的。


下图是我刚才说的更灵活的前端框架,现在我们接入了两套框架,react.js,还有vue.js 2.0

还有就是渲染模型,首先是有一层DOM设计,在WEB端会直接映射到WEB上去,我们在JS线程会跑V8,然后通过渲染映射App调用,应用到整个Native的框架下。


这个是整个渲染模型详细的结构


从渲染层面上我们有很多理念都是借鉴浏览器内核的,现在整个渲染模型上有三棵树,最上端是DOM Tree,然后到Native DOM,再到Shadow Tree再到Native Tree。这才是真正的渲染层实现,任何的UI操作会最终通过Shadow Tree操作到Native Tree。


这是我们平台的组件模型


实际上还是依赖整个前端的组件化,组件分成几类,首先是技术组件,还有自定义的扩展组件,要使用扩展组件就要实现三端实现的代码,对于扩展组件我们还是受到Native View,这个View是开发者自己提供的。


对于扩展组件的支持,自定义组件已经有一些常用的比如轮播图,下拉刷新,这些组件会跟其它的Native组件共用,像轮播图这种其实在我们的框架中还不支持这种操作,可以做一个单独的层面去使用。对于业界通常扩展方式的支持,首先是完善支持Cardova插件支持,现在至少在XCore页面中对于同样的框架是有能力的。其实扩展能力建设我们尽量不要做自定义的扩展,因为有大量的第三方扩展,别人开发好直接可以拉来用,这是生态系统完善带来的好处,所以我们不完全按自己的核心定义,而且用业界通用方案。


对于排版这部分支持我们形成的是Flex排版,有相对布局、绝对布局、宽高等等这些都是支持的,但是可能也只是支持一部分,这一块很大的好处就是它是标准的,另外我们会不断地去用业务需求去添加,现在就针对业务场景去做自己需求的一个分级。


这个是排版运算的过程,大概介绍一下流程。


对于CSS子集直接使用,到DOM Tree分好大小之后会同步到Shadow Tree上,然后到Native Tree上会直接渲染出来,所以对于Native层面来说没有用到任何Native排版方式,只是说是一个绝对的排版,没有View的大小和位置都是之前算好的。


重点介绍一下对于ListView的优化,在使用经验上来讲除了那些很简单的页面,大部分场景都是ListView的场景,只不过ListView的复杂程度不一样,一个简单的图形是一样的,但是比如说上面有Banner和动态展示,就是一个很复杂的ListView,对于这种场景的实现我们还是用Native ListView组件,安卓用ListView,iOS用UI TobView,但是发现用的时候还是出现了问题,实际上它对应的都是前端不同的模块,通过这些模块组合出来的这个页面,但是对于这样的产品,每一个模块再调用ListView的时候会作为一个Item,大小不一,但ListView在真正底层滚动的时候是要复用ListView的Item的。


怎么解决这样的问题?逻辑上很简单,我们就引用中间一层的Shadow ListView,根据每一个组件里面的逻辑进行拆分,把一个大的Item分拆成细小的Item,形成真正的ListView Item,这样在整个滚动时间就能很好的提升性能。


在ListView层面上我们也做了其它很多优化,这里重点介绍两点,一个就是View Pool,实际上在业务场景中很多页面是不一致的,我们就把整个页面卸掉,在一个View层级上把所有的View做View Pool。另外原来Item创建和销毁的时候是需要重新排版的,但是我们排版是在之前做好的,现在ListView滚动时就可以直接读取它的大小。

 

再就是整个框架的通信方式,跨境框架是一份源在三个上面跑,你到iOS、JSC、API都需要跨境,但是在安卓上就面临很复杂的问题,首先没有提供默认的JS引擎的API,就需要打包进去,我们就把V8打包进去,整个过程做起来比较复杂。这里我们是复用了NativeScript的框架,但是现在这个东西实现比较复杂,还是会陆续改进一些问题。


这里面分享两个案例,第一个案例就是在616大促良品会场用XCore框架来做,基本能覆盖到大部分的活动性场景,包括上面的Banner滚动,楼层、图墙展示,这是6月份上线的第一个版本,这个版本基本验证了XCore框架在跨平台的能力包括整个体验上和动态性能力已经得到充分的验证。但是原来的框架我们只是接了一个JSX,开发页面的同学觉得这个开发效率太低。我们在第二个版本就把整个.RS开发版本接入进来了,现在这个版本得到了很大的提升。我们的第二个案例是蘑菇街的品牌站,在性能上又有了一些优化。



下面讲一讲我们与RN、Weex的对比,主要有两点不同,首先我们做这个事情的定位不同,我们是想解决特定业务场景的问题,所以并不是一个业界通用的开源方案。还有就是目不同,我们想做到轻量、高效和灵活,所以采用了一个浏览器的架构,这样并不绑定此前固定的前端框架,对于前端框架选择来说我们其实是更灵活的。这个东西JS层面是没有限制的,只要是你使用的DOM层面或者是模板的层面,在我们模板范围内就可以了。这些就是我们架构上的不同,其实整个过程中我们思想上有很多借鉴,包括最开始我们也做过一版RN的业务,最后没有上线,一个是RN这个东西太大了,代码也很多,并不可控。还有就是RN它更适合一些中小型App的一个快速原型。另外RN本身也有一些性能问题,最简单就是ListView。


优化实践—页面离线化

最后分享下我们对于页面离线化的一些实践,正常一个页面先加载HTML、JS、CSS,再加载数据图片到整个页面完全展示出来。实际上对于白屏那个时间段的体验非常差,尤其在弱网的情况下,而且刚才说到用户群很多是二三线城市的妹子,她们的手机本身性能也不好,所受到的网络环境也不好,所以说提升首屏体验对整个用户体验是非常有用的。


这是我们离线化体系的结构


首先我们有离线化的后台,并不是针对于某一个业务,要想走我们的业务就上传离线包,会对整个离线包进行统一管理再进行二层分包,其次还有主动推动配置更新,当配置更新之后App会拉文件的加载模块,然后做解压,最后放到本地的存储区域。当整个App启动的时候会做资源加载的拦截,拦截发现这个资源已经在离线资源的就会进行本地的加载,这样就省掉了首屏网络时间。其实还有另外一个好处,有一些场景比如在12点上线就可以很早的把那些模块下发,然后用户请求以后会先把一些资源先拉到本地,打开之后整个页面就是本地的。


这就是我们如何做资源拦截的


原理上就是只要在页面启动的时候,把网络系统拦截到做一个资源映射,如果发现是本地资源就可以拉取本地资源。拦截方式iOS是用URLProtocol拦截的,在安卓是用ShouldlnterceptRequest拦截。这个是我们离线化的性能数据,走离线化在页面的最开始加载时间是非常明显的提升,尤其对于首屏的数据。


最后聊聊之后的一些计划和展望,首先整个框架其实是提供整个集团所有的App但实际上对不同的业务线整合并不是很好,所以我们可能要支持更多的简易框架整合,离线化这方面只是初步构建了离线化的体系,需要更加深入的完善。最后说到XCore,算是感慨一下,这个东西我觉得是一个业界的趋势,其实很明显,移动端发展到现在大家还是在对HTML5有很强的需求,其实这也是一种趋势,包括PC时代也是这样,后来基本随着浏览器性能的提升和普及,还有flash的作用,所以整个PC时代就变成了WEB时代。


从我个人观点来说浏览器并不能拯救移动端的HTML5,其实有很多的其它方式在进行,比如说超级App,真正在移动端HTML5可以发展成什么样子。未来的趋势大家拭目以待吧。




-END-

欢迎关注“互联网架构师”,我们分享最有价值的互联网技术干货文章,助力您成为有思想的全栈架构师,我们只聊互联网、只聊架构,不聊其他!打造最有价值的架构师圈子和社区。

本公众号覆盖中国主要首席架构师、高级架构师、CTO、技术总监、技术负责人等人 群。分享最有价值的架构思想和内容。打造中国互联网圈最有价值的架构师圈子。

  • 长按下方的二维码可以快速关注我们

  • 如想加群讨论学习,请点击右下角的“加群学习”菜单入群