在2016年,我参加了两场移动技术大会,在当时的GMTC大会上,原生开发看起来如日中天。
转眼到了2017年,HTML5性能越来越好,Facebook的React Native、阿里的Weex等跨平台方案在越来越多的公司中实践,微信小程序更是给了原生开发最沉重的一击,许多小公司可能不再需要开发自己的应用。
“Write once, run anywhere”,人们对跨平台开发的尝试从来没有停止过。特别在这个终端碎片化的时代,一份代码可以在同一个平台不同的系统版本,甚至在不同的平台上运行,对开发者的吸引力越来越大。
回想一下,HTML5与Native开发的斗争已经持续快十年了,那它们的现状是怎样的呢?React Native和Weex方案有哪些优势,又存在什么问题?小程序是不是真的可以一统江湖?焦虑的Native开发又应该如何在这个潮流之下谋发展呢?
从2017年开始,GMTC“移动技术大会”就更名为“大前端技术大会”。从现在看来,前端开发和Native开发并没有谁取代谁,而是正在融合,融合之后的产物就是所谓的“大前端”。为了顺应这种趋势,很多大公司的组织架构也做了相应的调整,把前端团队和iOS、Android一起合并为大前端团队。
移动Web技术、React Native和Weex、小程序,它们是目前最常用到的跨平台开发方案,下面我们一起来看看它们的应用现状。当然对于今年最为火热的Flutter技术,专栏后面我会花专门的篇幅去介绍。
1. Web
从桌面时代开始,以浏览器为载体的Web技术就具备跨平台、动态更新、扩展性强等优点。随着移动设备性能的增强,Web页面的性能也逐渐变得可以接受。客户端中出现越来越多的内嵌Web页面,很多应用也会把一些功能模块改为Web实现。
浏览器内核
一个Web页面是由HTML + CSS + JavaScript组成,通过浏览器内核执行并渲染成开发者预期的界面。
浏览器内核主要包括两大块功能,它们分别是:
浏览器引擎。浏览器引擎负责处理HTML和CSS,遵照的是W3C标准。
JavaScript引擎。JS引擎负责处理JS,遵照的是ECMAScript标准。
它们两者互相独立但又有非常紧密的结合,而且在不同浏览器内核中的实现也是不太一样的。但随着微软的Edge宣布将内核切换成Chromium,目前这个战场主要就剩下苹果和Google两个玩家,它们的浏览器引擎分别是Webkit和Blink(其实Blink也是fork自Webkit),JS引擎分别是JavaScriptCore和V8。
对于浏览器的渲染流程,可能很多Android开发并没有前端同学熟悉。一般来说,HTML、CSS、JS以及页面用到的一些其他资源(图片、视频、字体等)都需要从网络下载。而HTML会被解析成DOM,CSS会被解析成CSSOM,JS会由JS引擎执行,最后整合DOM和CSSOM之后合成为一棵Render Tree。
当然整个浏览器的渲染流程不是三言两语就可以说清楚的,下面是我找的一些不错的参考资料,感兴趣的同学可以深入学习:
浏览器渲染:一颗像素的诞生;强烈推荐看PPT: Life of a Pixel
浏览器系列教程:How Browsers Work: Behind the scenes of modern web browsers
Google Web开发者官网:Rendering on the Web
虽然Chromium是开源的,但是因为它的复杂性,国内对它有深入研究的人非常少,而拥有定制修改能力的人更是少之又少。因此这块需要投入大量的人力物力,国内比较有名的是UC浏览器的U4内核以及腾讯浏览器的X5内核。
性能现状
基于WebView的H5跨平台方案,优点确实非常明显。但是性能是它目前最大的问题,主要表现在以下两个方面:
启动白屏时间。WebView是一个非常重量级的控件,无论是WebView的初始化,还是整个渲染流程都非常耗时。这导致界面启动的时候会出现一段白屏时间,体验非常糟糕。
响应流畅度。由于单线程、历史包袱等原因,页面的渲染和JavaScript的执行效率都不如原生。在一些重交互或者动画复杂的场景,H5的性能还无法满足诉求。
所以在移动端H5主要应用在一些交互不太复杂的场景,一般来说即使帧率不如原生,但也基本符合要求。从我个人的感受来看,H5当前最大的问题在于启动的白屏时间。
对于Android界面启动的过程,我们在窗口动画还没结束的时候,大部分时候就已经完成了页面的渲染。启动一个Activity界面,我们一般要求在300毫秒以内。
回顾一下浏览器内核渲染的流程,我们其实可以把整个过程拆成三个部分:
Native时间。主要是Activity、WebView创建以及WebView初始化的时间。虽然首次创建WebView的时间会长一些,但总体Native时间是可控的。
网络时间。这里包括DNS、TCP、SSL的建连时间和下载主文档的时间。当解析主文档的时候,也需要同步去下载主文档依赖的CSS和JS资源,以及必要的数据。
渲染时间。浏览器内核构建Render Tree、Layout并渲染到屏幕的时间。
如上图所示,我们会更加关心用户看到完整的页面的时间T2,这里可以用T2秒开率作为启动速度的衡量标准。
优化方法
Native界面的T2秒开率做到90%以上并不困难,相比之下大部分没有经过优化的Web页面的T2秒开率可能都在40%以下,差距还是非常明显的。
那又应该如何去优化呢?从前端的角度来看,常用的优化方法有:
加快请求速度。整个启动过程中,网络时间是最不可控的。这里的优化方法有很多,例如预解析DNS、减少域名数、减少HTTP请求数、CDN分发、请求复用、懒加载、Gzip压缩、图片格式压缩。
代码优化。主文档的大小越小越好(要求小于15KB),这里要求我们对HTML、CSS以及JS进行代码优化。以JS为例,前端的库和框架真的太多了,可能一不小心就引入了各种的依赖框架。对于核心页面,我们要求只能使用原生JS或者非常轻量级的JS框架,例如使用只有几KB的Preact代替庞大的React框架。
SSR。对于浏览器的渲染流程,我上面描述的是CSR渲染模式,在这种模式下,服务器只返回页面的基本框架。事实上还有一种非常流行的SSR(Server Side Rendering)渲染模式,服务器可以一次性生成直接进行渲染的HTML。这样在T2之前,我们可以做到只有一个网络请求,但是带来的代价就是服务器计算资源的增加。一般来说,我们会在服务器前置CDN来解决访问量的问题。
通过上面的这些优化,特别是SSR这个“终极大招”,页面的T2秒开率达到70%并不是非常困难的事情。
前端同学能做的都已经做了,接下来我们还可以做些什么呢?这个时候就需要客户端开发登场了。
WebView预创建。提前创建和初始化WebView,以及实现WebView的复用,这块大约可以节省100~200毫秒。
缓存。H5是有多级的缓存机制,例如Memory Cache存放在内存中,一般资源响应回来就会放进去,页面关闭就会释放。Client Cache也就是客户端缓存,例如我们最常用的离线包方案,提前将需要网络请求的数据下发到客户端,通过拦截浏览器的资源请求实现加载。Http Cache是我们比较熟悉的缓存机制,而Net Cache就是指DNS解析结果的缓存,或预连接的缓存等。
从性能上看,Memory Cache > Client Cache >= Http Cache > Net Cache。所谓的缓存,就是在用户真正点击打开页面之前,提前把数据、资源下载到本地内存或者磁盘中,并放到内核相应的缓存中。例如即使我们使用了SSR,也可以在用户点击之前,提前把服务器渲染好的HTML下载好,这样用户真正打开页面的时候,可以做到完全没有网络请求。
通过预请求的优化,即使比较复杂的页面,T2秒开率也可以达到80%以上。但是既然是预请求就会有命中率的问题,服务器也增加了没有真正命中的请求数。所以在客户端性能和服务器压力之间,我们需要找到一个平衡点。
那还有没有进一步优化的空间?这个时候需要我们进一步往底层走,需要我们有定制修改甚至优化内核的能力。例如很多接口官方的浏览器内核可能并没有暴露,而腾讯和UC的内核里面都会有很多的特殊接口。
托管所有网络请求。我们不仅可以托管浏览器的Get请求,其他的所有Post请求也能接管,这样我们可以做非常多的定制化优化。
私有接口。我们可以暴露很多浏览器的一些非公开接口。以预渲染为例,我可以指定在内存直接渲染某个页面,当用户真正打开的时候,只需要直接做刷新就可以了,实现真正的“秒开”。
兼容性和安全。Android的碎片化导致浏览器内核的兼容性实在令人头疼,而且旧版本内核还存在不少的安全漏洞。在应用自带浏览器内核可以解决这些问题,而且高版本的内核特性也会更加完善,例如支持TLS 1.3、QUIC等。但是带来的代价是安装包增大20MB左右,当然我们也可以采用动态下载的方式。
定制的自有页面 + 定制的浏览器内核 + 极致的优化,即使是比较复杂的页面T2秒开率也可以达到90%以上,平均T2时间可以做到400毫秒以下。
2. React Native和Weex
基于WebView的H5跨平台方案,经过近乎疯狂的性能优化,看起来性能真的不错了。但是对于一些交互和动画复杂的场景(例如左右滑屏、手势),性能还是无法满足要求。
Facebook在2015年开源了React Native,它抛弃了WebView,利用JavaScriptCore来做桥接,将JavaScript调用转为Native调用。也就是说,React Native最终会生成对应的自定义原生控件,走的是系统原生的渲染流程。
而阿里在2016年也开源了Weex,它的思路跟React Native很像,但是上层DSL使用的是Vue。对于Weex和React Native的架构介绍,网上的文章非常多,例如《大前端的下一站何去何从?》和《Weex技术演进》。
但是世上哪有十全十美的方案?React Native/Weex方案为了能达到接近原生开发的性能和交互体验,必然要在跨平台和动态性上面做出了牺牲。
React Native和Weex向上对接了前端生态,向下对接了原生渲染,看起来是非常完美的方案。但是前端和客户端,客户端中的Android和iOS,它们的差异并不那么容易抹平,强行融合就会遇到各种各样的坑。
“React Native从入门到放弃”是很多开发者的心声,去年Airbnb、Udacity都相继宣布放弃使用React Native。React Native/Weex并没有彻底解决跨平台的问题,而且考虑到对外分享和降级容灾的需要,我们依然需要开发一个H5版本的页面。
为了解决这个问题,React Native的使用者需要引入一层非常重的中间层,期望在这个中间层中帮助我们去抹平这些差异。例如京东的JDReact、携程的Ctrip React Native。
既然React Native和Weex在跨平台上面做了牺牲,那它的性能和交互是不是能直接对齐Native开发呢?非常遗憾, 目前它们的性能我觉得主要还有两个瓶颈。
JS的执行时间。React Native和Weex使用的JavaScriptCore引擎,虽然它每年都在进步,但是JS是解释性的动态语言,它的执行效率相比AOT编译后的Java,性能依然会在几倍以上的差距。
跨语言的通信成本。既然要对接前端和原生两个生态,就无法避免JS -> C++ -> Java/Objective-C 之间频繁的通信和转换,所以这里面会涉及各种序列化,对性能的影响比较大。
虽然相比H5方案在性能方面有了很大的提升,但是React Native和Weex也要面对启动时间慢、帧率不如原生的性能问题。它属于一种比较中庸的方案,当然也会有自己的应用场景。例如一些二级页面(例如淘宝的分会场),它们的业务也比较重要,但是交互不会特别复杂,同时希望保持一定的动态化能力。
当然,Facebook已经意识到React Native的种种性能问题,目前正在疯狂重构中,希望让React Native更加轻量化、更适应混合开发,接近甚至达到原生的体验。Facebook现在透漏的信息并不多,感兴趣的同学可以参考《庖丁解牛!深入剖析React Native下一代架构重构》。
3. 小程序
2017年初,张小龙宣布微信小程序诞生。如今小程序已经走过了两年,在这两年间,小程序的生态也在健康的发展。
每一个应用都有成为超级App的梦想,各个大厂纷纷推出自己的小程序框架:微信、厂商、支付宝、今日头条、百度、淘宝、Google Play,小程序这个战场已然是“七国大乱战”。
但是小程序并不属于一种跨平台开发方案,大家更看重的是它的渠道优势,考虑如何通过微信、支付宝这些全民App获得更多的流量和用户。从技术上看,小程序的框架技术也是开放的,我们可以采用H5方案,也可以采用React Native和Weex,甚至是Flutter。
从实践上看,我们一起来看看已经正式上线的微信小程序、快应用、支付宝小程序以及百度小程序的差异(技术方面大家公开得并不多,可以参考《支付宝小程序框架》)。
我们可以看到除了独树一帜的快应用,其他小程序的技术方案基本都跟随了微信。但是考虑到H5在一些场景的性能问题,利用浏览器内核提供的同层渲染能力,在WebView之上支持一些原生的控件。如果哪一天微信小程序支持了所有的原生控件,那也就成为了另外一套React Native/Weex方案。
“神仙打架,百姓遭殃”,如果我们想从所有的小程序厂商上面获得流量,那就要开发七个不同的小程序。不过幸运的是,支付宝小程序和快应用也希望已有的微信小程序能快速迁移到自己平台上,所以它们的DSL设计都参考了微信的语法,可以说微信推出的DSL已然成为了事实标准。
如上图所示,我们希望有一套可以整合所有小程序框架的解决方案,一次开发就可以生成不同的小程序。滴滴的Chameleon和京东的Taro都致力于解决这个问题,目前它们都已经在GitHub上开源。
从移动开发诞生之初,跨平台就已经是大家前赴后继不断追求的目标。我们可以看看nwind在2015年写的一篇文章《聊聊移动端跨平台开发的各种技术》。如今四年过去了,大部分观点依然成立,并且从最后Dart的介绍中,我们甚至可以看到现在Flutter的雏形。
1. 跨平台开发的场景
Android、iOS、PC,不同的平台人们的操作习惯、喜好都不尽相同。对于大公司来说,完全的跨平台开发可能是一个伪命题,不同的平台应用的UI和交互都不太一样。
那我们对跨平台苦苦追寻了那么多年,希望得什么呢?以我的经验来看,跨平台主要的应用场景有:
部分业务。某个业务或者页面的跨平台共享,有的时候我们还希望可以做到跨应用。例如“全民答题”的时候,可以看到这个功能可以运行在头条系的各个应用中。一个公司共用同一套跨平台方案有非常重大的意义,业务可以在不同的应用中尝试。
核心功能。C++才是生命力最顽强的跨平台方案,大公司也将越来越多的核心模块往底层迁移,例如网络库、数据上报、加解密、音视频等。
2. 跨平台开发对比
H5的跨平台方案只要投入不太高的开发成本,就能开发出性能、功能还不错的应用。但是如果想做到极致优化,很容易发现开发者可控的东西实在比较少,性能和功能都依赖浏览器的支持。
这个时候如果想走得更远,我们不仅需要了解浏览器的内部机制,可能还需要具备定制、修改浏览器内核的能力,这也是阿里、腾讯、头条和百度都要组建内核团队的原因。
原生开发则相反,刚开始要投入很高的开发成本,但是一旦开始有产出之后,开发者能够有更的发挥空间,而React Native和Weex方案更是希望打造兼顾跨平台、开发成本以及性能的全方位解决方案。
从目前来看,每一种方案都有着自己的使用场景,无论是React Natve还是H5,都无法完全取代Native开发。当然这里也有一个例外,那就是如果我们不再开发应用,全面投向小程序。小程序跟原生开发的竞争,更多的是在渠道层面的竞争。
现在好像有个观点说“Android开发没人要”,大家都想转去做大前端开发,是不是真的是这样呢?事实上,无论我们使用哪一种跨平台方案,它们最终都要运行在Android平台上。崩溃、内存、卡顿、耗电这些问题依然存在,而且可能会更加复杂。而且从H5极致体验优化的例子来看,很多优化是需要深入研究平台特性和系统底层机制,我们在“高质量开发”中学到的底层和系统相关的知识依然很重要。
对开发者来说,唯一不变的就是学习能力。掌握了学习能力和钻研的精神,就能够应对这些趋势变化。无论移动开发未来如何变化,哪怕有一天AI真的能够自动写代码,具备应变能力的人也丝毫不会惧怕的。
跨平台开发也是一个很大很大的话题,今天我只能算是抛砖引玉。对于跨平台开发,你有什么看法?在你的应用中,使用了哪种跨平台开发方式?欢迎留言跟我和其他同学一起讨论。
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。我也为认真思考、积极分享的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。