极客时间已完结课程限时免费阅读

36 | 跨平台开发的现状与应用

36 | 跨平台开发的现状与应用-极客时间

36 | 跨平台开发的现状与应用

讲述:冯永吉

时长18:51大小17.22M

在 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 引擎分别是JavaScriptCoreV8
对于浏览器的渲染流程,可能很多 Android 开发并没有前端同学熟悉。一般来说,HTML、CSS、JS 以及页面用到的一些其他资源(图片、视频、字体等)都需要从网络下载。而 HTML 会被解析成 DOM,CSS 会被解析成 CSSOM,JS 会由 JS 引擎执行,最后整合 DOM 和 CSSOM 之后合成为一棵 Render Tree。
当然整个浏览器的渲染流程不是三言两语就可以说清楚的,下面是我找的一些不错的参考资料,感兴趣的同学可以深入学习:
浏览器渲染:一颗像素的诞生;强烈推荐看 PPT: Life of a Pixel
浏览器引擎:What is a browser engine?
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 真的能够自动写代码,具备应变能力的人也丝毫不会惧怕的。

课后作业

跨平台开发也是一个很大很大的话题,今天我只能算是抛砖引玉。对于跨平台开发,你有什么看法?在你的应用中,使用了哪种跨平台开发方式?欢迎留言跟我和其他同学一起讨论。
欢迎你点击“请朋友读”,把今天的内容分享给好友,邀请他一起学习。我也为认真思考、积极分享的同学准备了丰厚的“学习加油礼包”,期待与你一起切磋进步哦。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 10

提建议

上一篇
35 | Native Hook 技术,天使还是魔鬼?
下一篇
37 | 移动开发新大陆:工作三年半,移动开发转型手游开发
unpreview
 写留言

精选留言(15)

  • warmcheng
    2019-04-06
    慢慢觉得,以不变应万变才能避免焦虑。回归系统本质,底层原理,编程周边基础等等。其他,只需要有学习能力以及对新鲜事物的尝新兴趣即可。

    作者回复: 对的,哪有那么多新的东西,大部分都是微创新,一通则百通

    9
  • CatTalk
    2019-03-30
    年前利用Flutter,重构了一个子功能模块,以插件的形式运行在主App中。开发期间最大的感受就是Flutter的生态还是才刚刚起步,尤其这种混合开发,相关开发工具或者流程上的东西欠缺还很多,不是很得劲。
    5
  • 伦特
    2019-04-01
    目前正在学习flutter,但从市面招聘来看,岗位不多,朋友推荐我学rn,但我好像很排斥rn。。。

    作者回复: 嗯,Flutter的岗位现在确实不多,有一些公司有岗位,比如头条、阿里,Flutter趋势比较好,可以多关注一些

    2
  • 跳跳
    2022-09-22 来自上海
    更喜欢在 Xamarin/MAUI.NET。
  • 微尘
    2022-09-06 来自北京
    没什么可以焦虑的,怎么都跳不出Android /iOS平台,只是在不断的前进中,提高了门槛,我们只要保持进步,就一定可以跑赢。
  • 三杯两盏
    2020-05-18
    未来的趋势是原生 + web + c++,这才是以不变应万变。什么rn weex flutter,有机会的话,会用就行,建议不要倾注太多精力在这类技术上。
  • splm
    2019-09-11
    托管所有网络请求,这个能不能再稍微讲讲,没太理解。

    作者回复: 这个是建立在我们有自己的内核,提供了单独的接口出来

  • splm
    2019-06-21
    提前渲染出用户要点击的url内容,怎么预判?

    作者回复: 可以看看chromium的predictor代码,简单来说,例如当用户光标在某个url,一些下一页这些关键字,用户在输入框输入的某些关键池

  • 江龙
    2019-05-26
    图中T1\T2这些定义有出处吗?我们使用一个标准监控服务。使用了W3C 规范中定义的 Navigation Timing API :https://www.w3.org/TR/navigation-timing/?spm=a2c4g.11186623.2.15.37df2e88EQYwwV来统计关键指标,其中First Paint Time, 首次渲染时间 = responseEnd - fetchStart ,也就是在DOM解析前。跟你图中的 T1 first paint有差异

    作者回复: W3C的确有一套标准,不过那套指标不算特别细。 T0、T1、T2、T3的定义在阿里倒是比较通用的

  • Leo
    2019-04-02
    如果要投入精力去学习一种跨平台的技术,选哪一种比较好?

    作者回复: H5是所有跨平台方式的基础

  • andavid
    2019-04-02
    1、页面使用自定义的 HTML 标签,通过原生解析、渲染、排版 2、页面内脚本语言使用 Lua,通过调用客户端封装的自定义 Lua 执行原生代码 3、服务端只需开发一套页面,就可以在 Android 和 iOS 平台原生渲染展示 4、页面通过离线资源的方案进行动态更新

    作者回复: 如果都已经使用了HTML,那就可以直接通过JSBridge了。 通过Lua的原生界面动态框架之前手Q搞过一套,但是写界面实在太痛苦,所以推不起来

  • 时间去哪儿了
    2019-04-01
    未来flutter和原生开发会是什么样的关系呢?据说性能要好于rn,weex

    作者回复: 就是一些本来使用原生开发的功能,会使用Flutter代替

  • Jiantao
    2019-03-31
    用过RN开发过部分业务,采用混合开发。实际开发过程及用户体验Android都不如iOS;现在项目也是原生+RN+H5模式,看好flutter但暂时没有投入实际项目,也期待RN新版本。

    作者回复: 新版本希望4月底的F8会议可以放出来

  • huguo
    2019-03-30
    非常期待关注flutter以及fuchisa的那期
  • 王佐
    2019-03-30
    大前端时代,移动开发者需要掌握更多的技能才行

    作者回复: 技多不压身