This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
<audio id="audio" title="03 | 深入理解跨平台方案的历史发展逻辑" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/68/b8/6831a6a31ad3e3a6413121bfefc82ab8.mp3"></audio>
你好,我是陈航。
今天,我会从跨平台开发方案的诞生背景、原理和发展历史的角度,和你聊聊这些常见的开发方案,以及针对不同的场景我们应该如何选择对应的方案。
## 浅述跨平台开发的背景
我们当下所处的移动互联网时代,以它独有的变革方式,带给我们快捷、经济、安全和方便,改变着生活的方方面面。而对于企业来说,移动应用已然成为各类手机终端上一张必备的产品名片。
在移动互联网的浪潮下我们开发的应用要想取胜开发效率和使用体验可以说是同等重要。但是使用原生的方式来开发App就要求我们必须针对iOS和Android这两个平台分别开发这对于中小型团队来说就是隐患和额外的负担。
因为这样的话我们不仅需要在不同的项目间尝试用不同的语言去实现同样的功能还要承担由此带来的维护任务。如果还要继续向其他平台比如Web、Mac或Windows拓展的话我们需要付出的成本和时间将成倍增长。而这显然是难以接受的。于是跨平台开发的概念顺势走进了我们的视野。
所以从本质上讲,**跨平台开发是为了增加业务代码的复用率,减少因为要适配多个平台带来的工作量,从而降低开发成本。**在提高业务专注度的同时,能够为用户提供一致的用户体验。用一个词来概括这些好处的话,就是“多快好省”。
“一次编码到处运行”。二十多年前Java正是以跨平台特性的口号登场击败了众多竞争对手。这个口号意味着Java可以在任何平台上进行开发然后编译成一段标准的字节码后就可以运行在任何安装有Java虚拟机JVM的设备上。虽然现在跨平台已经不是Java的最大优势而是它繁荣的生态但不可否认它当年打着跨平台旗号横空出世确实势不可挡。
而对于移动端开发来讲,如果能实现“一套代码,多端运行”,这样的技术势必会引发新的生产力变革,在目前多终端时代的大环境下,可以为企业节省人力资源上,从而带来直接的经济效益。
伴随着移动端的诞生和繁荣为了满足人们对开发效率和用户体验的不懈追求各种跨平台的开发方案也如雨后春笋般涌现。除了React Native和Flutter之外这几年还出现过许多其他的解决方案接下来我将会为你一一分析这些方案。这样你在选择适合自己的移动开发框架时也就有章可循了。
在此我特地强调一下我在下文提到的跨平台开发方案如果没有特殊说明的话指的就是跨iOS和Android开发。
## 跨平台开发方案的三个时代
根据实现方式的不同,业内常见的观点是将主流的跨平台方案划分为三个时代。
- Web容器时代基于Web相关技术通过浏览器组件来实现界面及功能典型的框架包括Cordova(PhoneGap)、Ionic和微信小程序。
- 泛Web容器时代采用类Web标准进行开发但在运行时把绘制和渲染交由原生系统接管的技术代表框架有React Native、Weex和快应用广义的还包括天猫的Virtual View等。
- 自绘引擎时代自带渲染引擎客户端仅提供一块画布即可获得从业务逻辑到功能呈现的多端高度一致的渲染体验。Flutter是为数不多的代表。
接下来我们先看一下目前使用最广泛的Web容器方案。
### Web容器时代
Web时代的方案主要采用的是原生应用内嵌浏览器控件WebViewiOS为UIWebView或WKWebViewAndroid为WebView的方式进行HTML5页面渲染并定义HTML5与原生代码交互协议将部分原生系统能力暴露给HTML5从而扩展HTML5的边界。这类交互协议就是我们通常说的JS Bridge
这种开发模式既有原生应用代码又有Web应用代码因此又被称为Hybrid开发模式。由于HTML5代码只需要开发一次就能同时在多个系统运行因此大大降低了开发成本。
由于采用了Web开发技术社区和资源非常丰富开发效率也很高。但**一个完整HTML5页面的展示要经历浏览器控件的加载、解析和渲染三大过程性能消耗要比原生开发增加N个数量级**。
接下来,我以加载过程为例,和你说明这个过程的复杂性。
1. 浏览器控件加载HTML5页面的HTML主文档
1. 加载过程中遇到外部CSS文件浏览器另外发出一个请求来获取CSS文件
1. 遇到图片资源浏览器也会另外发出一个请求来获取图片资源。这是异步请求并不会影响HTML文档的加载。
1. 加载过程中遇到JavaScript文件由于JavaScript代码可能会修改DOM树因此HTML文档会挂起渲染加载解析渲染同步的线程直到JavaScript文件加载解析并执行完毕才可以恢复HTML文档的渲染线程。
1. JavaScript代码中有用到CSS文件中的属性样式于是阻塞等待CSS加载完毕才能恢复执行。
而这只是完成HTML5页面渲染的最基础的加载过程。加载、解析和渲染这三个过程在实际运行时又不是完全独立的还会有交叉。也就是说会存在一边加载一边解析一边渲染的现象。这就使得页面的展示并不像想象中那么容易。
通过上面的分析你可以看出一个HTML5页面的展示是多么得复杂这和原生开发通过简单直接的创建控件设置属性后即可完成页面渲染有非常大的差异。Web与原生在UI渲染与系统功能调用上各司其职因此这个时代的框架在Web与原生系统间还有比较明显的、甚至肉眼可见的边界。
<img src="https://static001.geekbang.org/resource/image/6d/79/6d4035e44b4af68b7588a750fec06c79.png" alt="">
我也曾碰到过很多人觉得跨平台开发不靠谱。但其实Web容器方案是跨平台开发历史上最成功的例子。也正是因为它太成功了以至于很多人都忽略了它也是跨平台方案之一。
### 泛Web容器时代
虽然Web容器方案具有生态繁荣、开发体验友好、生产效率高、跨平台兼容性强等优势但它最大的问题在于承载着大量Web标准的Web容器过于笨重以至于性能和体验都达不到与原生同样的水准在复杂交互和动画上较难实现出优良的用户体验。
而在实际的产品功能研发中我们通常只会用到Web标准中很小的一部分。面对这样的现实我们很快就想到能否对笨重的Web容器进行功能裁剪在仅保留必要的Web标准和渲染能力的基础上使得友好的开发体验与稳定的渲染性能保持一个平衡
答案当然是可以。
泛Web容器时代的解决方案优化了Web容器时代的加载、解析和渲染这三大过程把影响它们独立运行的Web标准进行了裁剪以相对简单的方式支持了构建移动端页面必要的Web标准如Flexbox等也保证了便捷的前端开发体验同时这个时代的解决方案基本上完全放弃了浏览器控件渲染而是采用原生自带的UI组件实现代替了核心的渲染引擎仅保持必要的基本控件渲染能力从而使得渲染过程更加简化也保证了良好的渲染性能。
也就是说在泛Web容器时代我们仍然采用前端友好的JavaScript进行开发整体加载、渲染机制大大简化并且由原生接管绘制即将原生系统作为渲染的后端为依托于JavaScript虚拟机的JavaScript代码提供所需要的UI控件的实体。这也是现在绝大部分跨平台框架的思路而React Native和Weex就是其中的佼佼者。
<img src="https://static001.geekbang.org/resource/image/3a/0b/3af2c590f42c0b924a22bb135134380b.png" alt="">
为了追求性能体验的极致并进一步维持方案的简单可扩展性有些轻量级的跨平台方案甚至会完全抛弃Web标准、放弃JavaScript的动态执行能力而自创一套原生DSL如天猫的[VirtualView](http://pingguohe.net/2017/12/07/Tangram-2.html)框架。从广义上来说这些方案也是泛Web容器类方案。
### 自绘引擎时代
泛Web容器时代使用原生控件承载界面渲染固然解决了不少性能问题但同时也带来了新的问题。抛开框架本身需要处理大量平台相关的逻辑外随着系统版本变化和API的变化我们还需要处理不同平台的原生控件渲染能力差异修复各类奇奇怪怪的Bug。始终需要Follow Native的思维方式就使得泛Web容器框架的跨平台特性被大打折扣。
而这一时期的代表Flutter则开辟了一种全新的思路即从头到尾重写一套跨平台的UI框架包括渲染逻辑甚至是开发语言。
- 渲染引擎依靠跨平台的Skia图形库来实现Skia引擎会将使用Dart构建的抽象的视图结构数据加工成GPU数据交由OpenGL最终提供给GPU渲染至此完成渲染闭环因此可以在最大程度上保证一款应用在不同平台、不同设备上的体验一致性。
- 而开发语言选用的是同时支持JITJust-in-Time即时编译和AOTAhead-of-Time预编译的Dart不仅保证了开发效率更提升了执行效率比使用JavaScript开发的泛Web容器方案要高得多
<img src="https://static001.geekbang.org/resource/image/85/2c/85dfb3f8a803a0bf573f3fce63ddc92c.png" alt="">
通过这样的思路Flutter可以尽可能地减少不同平台之间的差异, 同时保持和原生开发一样的高性能。所以说Flutter成了三类跨平台移动开发方案中最灵活的那个也成了目前最受业界关注的框架。
现在,我们已经弄明白了三类跨平台方案,那么我在开发应用的时候,到底应该如何选择最适合自己的框架呢?
## 我该选择哪一类跨平台开发方案?
从不同的角度来看,三个时代的跨平台框架代表们在开发效率、渲染性能、维护成本和社区生态上各有优劣,如下图所示:
<img src="https://static001.geekbang.org/resource/image/40/3f/40a62d2a54b1814d07479dca1178fb3f.png" alt="">
我们在做技术选型时可以参考以上维度从开发效率、技术栈、性能表现、维护成本和社区生态来进行综合考虑。比如是否必须支持动态化是只解决Android、iOS的跨端问题还是要包括Web对性能要求如何对多端体验的绝对一致性和维护成本是否有强诉求
从各个维度综合考量React Native和Flutter无疑是最均衡的两种跨平台开发方案而其他的方案或多或少都“偏科严重”。
- React Native依托于Facebook经过4年多的发展已经成长为跨平台开发方案的实际领导者并拥有较为丰富的第三方库和开发社区
- Flutter以挑战者姿态出现在我们的面前可以提供更彻底的跨平台技术解决方案。虽然Flutter推出时间不长但也有了诸多商用案例加上清晰的[产品路线图](https://mp.weixin.qq.com/s/aRhQdMd0R74adph9_V0wgQ)和Google的强大号召力Flutter未来的发展非常值得期待。
那么问题来了我究竟应该选择React Native还是Flutter呢
在这里,我和你说一下我的建议吧。
**对于知识学习来说,**这两个应用层面的框架最好都学。学习的过程中最重要的是打好基础深入理解框架的原理和设计思想重点思考它们的API设计的取舍发现它们的共性和差异。
Flutter作为后来者也从React Native社区学习和借鉴了不少的优秀设计很多概念两边都有对应比如React Native的Component和Flutter的Widget、Flex布局思想、状态管理和函数式编程等等这类的知识都是两个框架通用的技术。**未来也许还会出现新的解决方案,老框架也会不断更新,只有掌握核心原理才能真正立于不败之地。**
**对于实际项目来说,**这两个框架都已达到了大面积商业应用的标准。综合成熟度和生态目前俩看React Native略胜Flutter。因此如果是中短期项目的话我建议使用React Native。但作为技术选型我们要看得更远一些。Flutter的设计理念比较先进解决方案也相对彻底在渲染能力的一致性以及性能上和React Native相比优势非常明显。
此外Flutter的野心不仅仅是移动端。前段时间Google团队已经完成了Hummingbird即Flutter的Web的官方Demo在桌面操作系统的探索上也取得了进展未来大前端技术栈是否会由Flutter完成统一值得期待。
## 小结
这就是今天分享的全部内容了。
在不同平台开发和维护同一个产品所付出的成本一直以来一个令人头疼的问题于是各类跨平台开发方案顺应而生。从Web容器时代到以React Native、Weex为代表的泛Web容器时代最后再到以Flutter为代表的自绘引擎时代这些优秀的跨平台开发框架们慢慢抹平了各个平台的差异使得操作系统的边界变得越来越模糊。
与此同时这个时代对开发者的要求也到达了一个新的阶段拥抱大前端的时代已经向我们走来。在这个专栏里我会假设你有一定的前端Android、iOS或Web开发基础。比如你知道View是什么路由是什么如何实现一个基本页面布局等等。我会让希望迅速掌握Flutter开发的爱好者们通过一种比较熟悉和友好的路径去学习Flutter相关的代码和程序以及背后的原理和设计思想。
## 思考题
你有哪些跨平台开发框架的使用经历呢?
欢迎你在评论区给我留言分享你的经历和观点,我会在下一篇文章中等待你!感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。

View File

@@ -0,0 +1,167 @@
<audio id="audio" title="04 | Flutter区别于其他方案的关键技术是什么" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/de/ab/de5bdc701dffe439dee3a9db7138c8ab.mp3"></audio>
你好,我是陈航。
Flutter是什么它出现的动机是什么解决了哪些痛点相比其他跨平台技术Flutter的优势在哪里……相信很多人在第一眼看到Flutter时都会有类似的疑问。
别急在今天的这篇文章中我会与你介绍Flutter的历史背景和运行机制并以界面渲染过程为例与你讲述其实现原理让你对Flutter能够有一个全方位的认知和感受。在对Flutter有了全面了解后这些疑问自然也就迎刃而解了。
接下来我们就从Flutter出现的历史背景开始谈起吧。
## Flutter出现的历史背景
为不同的操作系统开发拥有相同功能的应用程序,开发人员只有两个选择:
1. 使用原生开发语言即Java和Objective-C针对不同平台分别进行开发。
1. 使用跨平台解决方案,对不同平台进行统一开发。
原生开发方式的体验最好但研发效率和研发成本相对较高而跨平台开发方式研发虽然效率高但为了抹平多端平台差异各类解决方案暴露的组件和API较原生开发相比少很多因此研发体验和产品功能并不完美。
所以最成功的跨平台开发方案其实是依托于浏览器控件的Web。浏览器保证了99%的概率下Web的需求都是可以实现的不需要业务将就“技术”。不过Web最大的问题在于它的性能和体验与原生开发存在肉眼可感知的差异因此并不适用于对体验要求较高的场景。
对于用户体验更接近于原生的React Native对业务的支持能力却还不到浏览器的5%仅适用于中低复杂度的低交互类页面。面对稍微复杂一点儿的交互和动画需求开发者都需要case by case地去review甚至还可能要通过原生代码去扩展才能实现。
这些因素,也就导致了虽然跨平台开发从移动端诞生之初就已经被多次提及,但到现在也没有被很好地解决。
带着这些问题我们终于迎来了本次专栏的主角——Flutter。
Flutter是构建Google物联网操作系统Fuchsia的SDK主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发App一套代码可以同时运行在 iOS 和 Android平台。 Flutter使用Native引擎渲染视图并提供了丰富的组件和接口这无疑为开发者和用户都提供了良好的体验。
从2017年5月谷歌公司发布的了Alpha版本的Flutter到2018年底Flutter Live发布的1.0版本再到现在最新的1.5版本截止至2019年7月1日Flutter正在赢得越来越多的关注。
很多人开始感慨跨平台技术似乎终于迎来了最佳解决方案。那么接下来我们就从原理层面去看看Flutter是如何解决既有跨平台开发方案问题的。
## Flutter是怎么运转的
与用于构建移动应用程序的其他大多数框架不同Flutter是重写了一整套包括底层渲染逻辑和上层开发语言的完整解决方案。这样不仅可以保证视图渲染在Android和iOS上的高度一致性即高保真在代码执行效率和渲染性能上也可以媲美原生App的体验即高性能
就是Flutter和其他跨平台方案的本质区别
- React Native之类的框架只是通过JavaScript虚拟机扩展调用系统组件由Android和iOS系统进行组件的渲染
- Flutter则是自己完成了组件渲染的闭环。
那么,**Flutter是怎么完成组件渲染的呢**?这需要从图像显示的基本原理说起。
在计算机系统中图像的显示需要CPU、GPU和显示器一起配合完成CPU负责图像数据计算GPU负责图像数据渲染而显示器则负责最终图像显示。
CPU把计算好的、需要显示的内容交给GPU由GPU完成渲染后放入帧缓冲区随后视频控制器根据垂直同步信号VSync以每秒60次的速度从帧缓冲区读取帧数据交由显示器完成图像显示。
操作系统在呈现图像时遵循了这种机制而Flutter作为跨平台开发框架也采用了这种底层方案。下面有一张更为详尽的示意图来解释Flutter的绘制原理。
<img src="https://static001.geekbang.org/resource/image/95/2a/95cb258c9103e05398f9c97a1113072a.png" alt="">
可以看到Flutter关注如何尽可能快地在两个硬件时钟的VSync信号之间计算并合成视图数据然后通过Skia交给GPU渲染UI线程使用Dart来构建视图结构数据这些数据会在GPU线程进行图层合成随后交给Skia引擎加工成GPU数据而这些数据会通过OpenGL最终提供给GPU渲染。
在进一步学习Flutter之前我们有必要了解下构建Flutter的关键技术即Skia和Dart。
## Skia是什么
要想了解Flutter你必须先了解它的底层图像渲染引擎Skia。因为Flutter只关心如何向GPU提供视图数据而Skia就是它向GPU提供视图数据的好帮手。
Skia是一款用C++开发的、性能彪悍的2D图像绘制引擎其前身是一个向量绘图软件。2005年被Google公司收购后因为其出色的绘制表现被广泛应用在Chrome和Android等核心产品上。Skia在图形转换、文字渲染、位图渲染方面都表现卓越并提供了开发者友好的API。
因此架构于Skia之上的Flutter也因此拥有了彻底的跨平台渲染能力。通过与Skia的深度定制及优化Flutter可以最大限度地抹平平台差异提高渲染效率与性能。
底层渲染能力统一了上层开发接口和功能体验也就随即统一了开发者再也不用操心平台相关的渲染特性了。也就是说Skia保证了同一套代码调用在Android和iOS平台上的渲染效果是完全一致的。
## 为什么是Dart
除了我们在第2篇预习文章“[预习篇 · Dart语言概览](https://time.geekbang.org/column/article/104071)”中提到的Dart因为同时支持AOT和JIT所以具有运行速度快、执行性能好的特点外Flutter为什么选择了Dart而不是前端应用的准官方语言JavaScript呢这个问题很有意思但也很有争议。
很多人说选择Dart是Flutter推广的一大劣势毕竟多学一门新语言就多一层障碍。想想Java对AndroidJavaScript对NodeJS的推动如果换个语言可能就不一样了。
但,**Google公司给出的原因很简单也很直接**Dart语言开发组就在隔壁对于Flutter需要的一些语言新特性能够快速在语法层面落地实现而如果选择了JavaScript就必须经过各种委员会和浏览器提供商漫长的决议。
事实上Flutter的确得到了兄弟团队的紧密支持。2018年2月发布的Dart 2.02018年12月发布的Dart 2.12019年2月发布的Dart 2.22019年5月发布的Dart2.3每次发布都包含了为Flutter量身定制的诸多改造比如改进的AOT性能、更智能的类型隐式转换等
当然Google公司选择使用Dart作为Flutter的开发语言我想还有其他更有说服力的理由
1. Dart同时支持即时编译JIT和事前编译AOT。在开发期使用JIT开发周期异常短调试方式颠覆常规支持有状态的热重载而发布期使用AOT本地代码的执行更高效代码性能和用户体验也更卓越。
1. Dart作为一门现代化语言集百家之长拥有其他优秀编程语言的诸多特性比如完善的包管理机制。也正是这个原因Dart的学习成本并不高很容易上手。
1. Dart避免了抢占式调度和共享内存可以在没有锁的情况下进行对象分配和垃圾回收在性能方面表现相当不错。
Dart是一门优秀的现代语言最初设计也是为了取代JavaScript成为Web开发的官方语言。竞争对手如此强劲最后的结果可想而知。这也是为什么相比于其他热门语言Dart的生态要冷清不少的原因。
而随着Flutter的发布Dart开始转型其自身定位也发生了变化专注于改善构建客户端应用程序的体验因此越来越多的开发者开始慢慢了解、学习这门语言并共同完善它的生态。凭借着Flutter的火热势头辅以Google强大的商业运作能力相信转型后的Dart前景会非常光明。
## Flutter的原理
在了解了Flutter的基本运作机制后我们再来深入了解一下Flutter的实现原理。
首先我们来看一下Flutter的架构图。我希望通过这张图以及对应的解读你能在开始学习的时候就建立起对Flutter的整体印象能够从框架设计和实现原理的高度去理解Flutter区别其他跨平台解决方案的关键所在为后面的学习打好基础而不是直接一上来就陷入语言和框架的功能细节“泥潭”而无法自拔。
<img src="https://static001.geekbang.org/resource/image/ac/2f/ac7d1cec200f7ea7cb6cbab04eda252f.png" alt="">
备注:此图引自[Flutter System Overview](https://flutter.dev/docs/resources/technical-overview)
Flutter架构采用分层设计从下到上分为三层依次为Embedder、Engine、Framework。
- Embedder是操作系统适配层实现了渲染Surface设置线程设置以及平台插件等平台相关特性的适配。从这里我们可以看到Flutter平台相关特性并不多这就使得从框架层面保持跨端一致性的成本相对较低。
- Engine层主要包含Skia、Dart和Text实现了Flutter的渲染引擎、文字排版、事件处理和Dart运行时等功能。Skia和Text为上层接口提供了调用底层渲染和排版的能力Dart则为Flutter提供了运行时调用Dart和渲染引擎的能力。而Engine层的作用则是将它们组合起来从它们生成的数据中实现视图渲染。
- Framework层则是一个用Dart实现的UI SDK包含了动画、图形绘制和手势识别等功能。为了在绘制控件等固定样式的图形时提供更直观、更方便的接口Flutter还基于这些基础能力根据Material和Cupertino两种视觉设计风格封装了一套UI组件库。我们在开发Flutter的时候可以直接使用这些组件库。
接下来,我**以界面渲染过程为例和你介绍Flutter是如何工作的。**
页面中的各界面元素Widget以树的形式组织即控件树。Flutter通过控件树中的每个控件创建不同类型的渲染对象组成渲染对象树。而渲染对象树在Flutter的展示过程分为四个阶段布局、绘制、合成和渲染。
### 布局
Flutter采用深度优先机制遍历渲染对象树决定渲染对象树中各渲染对象在屏幕上的位置和尺寸。在布局过程中渲染对象树中的每个渲染对象都会接收父对象的布局约束参数决定自己的大小然后父对象按照控件逻辑决定各个子对象的位置完成布局过程。
<img src="https://static001.geekbang.org/resource/image/f9/00/f9e6bbf06231fbad54ed11ef291e8d00.png" alt="">
为了防止因子节点发生变化而导致整个控件树重新布局Flutter加入了一个机制——布局边界Relayout Boundary可以在某些节点自动或手动地设置布局边界当边界内的任何对象发生重新布局时不会影响边界外的对象反之亦然。
<img src="https://static001.geekbang.org/resource/image/42/de/42961a84ecc8181d1afe7ffbaa1a55de.png" alt="">
### 绘制
布局完成后渲染对象树中的每个节点都有了明确的尺寸和位置。Flutter会把所有的渲染对象绘制到不同的图层上。与布局过程一样绘制过程也是深度优先遍历而且总是先绘制自身再绘制子节点。
以下图为例节点1在绘制完自身后会再绘制节点2然后绘制它的子节点3、4和5最后绘制节点6。
<img src="https://static001.geekbang.org/resource/image/8c/b8/8c1d612990d9ada0508c5a41c9e4cab8.png" alt="">
可以看到由于一些其他原因比如视图手动合并导致2的子节点5与它的兄弟节点6处于了同一层这样会导致当节点2需要重绘的时候与其无关的节点6也会被重绘带来性能损耗。
为了解决这一问题Flutter提出了与布局边界对应的机制——重绘边界Repaint Boundary。在重绘边界内Flutter会强制切换新的图层这样就可以避免边界内外的互相影响避免无关内容置于同一图层引起不必要的重绘。
<img src="https://static001.geekbang.org/resource/image/da/ee/da430de8f265f444c4801758902a8bee.png" alt="">
重绘边界的一个典型场景是Scrollview。ScrollView滚动的时候需要刷新视图内容从而触发内容重绘。而当滚动内容重绘时一般情况下其他内容是不需要重绘的这时候重绘边界就派上用场了。
### 合成和渲染
终端设备的页面越来越复杂因此Flutter的渲染树层级通常很多直接交付给渲染引擎进行多图层渲染可能会出现大量渲染内容的重复绘制所以还需要先进行一次图层合成即将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果将相同的图层归类合并简化渲染树提高渲染效率。
合并完成后Flutter会将几何图层数据交由Skia引擎加工成二维图像数据最终交由GPU进行渲染完成界面的展示。这部分内容我已经在前面的内容中介绍过这里就不再赘述了。
接下来我们再看看学习Flutter都需要学习哪些知识。
## 学习Flutter需要掌握哪些知识
终端设备越来越碎片化需要支持的操作系统越来越多从研发效率和维护成本综合考虑跨平台开发一定是未来大前端的趋势我们应该拥抱变化。而Flutter提供了一套彻底的移动跨平台方案也确实弥补了如今跨平台开发框架的短板解决了业界痛点极有可能成为跨平台开发领域的终极解决方案前途非常光明。
那么,**我们学习Flutter都需要掌握哪些知识呢**
我按照App的开发流程开发、调试测试、发布与线上运维将Flutter的技术栈进行了划分里面几乎包含了Flutter开发需要的所有知识点。而这些所有知识点我会在专栏中为你一一讲解。掌握了这些知识点后你也就具备了企业级应用开发的必要技能。
这些知识点,如下图所示:
<img src="https://static001.geekbang.org/resource/image/99/64/9959006fe52706a123cc7fc596346064.jpg" alt="">
有了这张图你是否感觉到学习Flutter的路线变得更加清晰了呢
## 小结
今天我带你了解了Flutter的历史背景与运行机制并以界面渲染过程为例从布局、绘制、合成和渲染四个阶段讲述了Flutter的实现原理。此外我向你介绍了构建Flutter底层的关键技术Skia与Dart它们是Flutter有别于其他跨平台开发方案的核心所在。
最后我梳理了一张Flutter学习思维导图围绕一个应用的迭代周期介绍了Flutter相关的知识点。我希望通过这个专栏能和你把Flutter背后的设计原理和知识体系讲清楚让你能对Flutter有一个整体感知。这样在你学完这个专栏以后就能够具备企业级应用开发的理论基础与实践。
## 思考题
你是如何理解Flutter的三大特点跨平台、高保真、高性能的你又打算怎么学习这个专栏呢
欢迎你在评论区给我留言分享你的观点,我会在下一篇文章中等待你!感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。

View File

@@ -0,0 +1,149 @@
<audio id="audio" title="05 | 从标准模板入手体会Flutter代码是如何运行在原生系统上的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/59/83/5938890d5ed2d824e18237299f9fb283.mp3"></audio>
你好,我是陈航。
在专栏的第一篇预习文章中我和你一起搭建了Flutter的开发环境并且通过自带的hello_world示例和你演示了Flutter项目是如何运行在Android和iOS模拟器以及真机上的。
今天我会通过Android Studio创建的Flutter应用模板带你去了解Flutter的项目结构分析Flutter工程与原生Android和iOS工程有哪些联系体验一个有着基本功能的Flutter应用是如何运转的从而加深你对构建Flutter应用的关键概念和技术的理解。
如果你现在还不熟悉Dart语言也不用担心只要能够理解基本的编程概念比如类型、变量、函数和面向对象并具备一定的前端基础比如了解View是什么、页面基本布局等基础知识就可以和我一起完成今天的学习。而关于Dart语言基础概念的讲述、案例分析我会在下一个模块和你展开。
## 计数器示例工程分析
首先我们打开Android Studio创建一个Flutter工程应用flutter_app。Flutter会根据自带的应用模板自动生成一个简单的计数器示例应用Demo。我们先运行此示例效果如下
<img src="https://static001.geekbang.org/resource/image/3a/24/3afe6b35238d1e57c8ae6bec9be61624.png" alt="">
每点击一次右下角带“+”号的悬浮按钮,就可以看到屏幕中央的数字随之+1。
### 工程结构
在体会了示例工程的运行效果之后我们再来看看Flutter工程目录结构了解Flutter工程与原生Android和iOS工程之间的关系以及这些关系是如何确保一个Flutter程序可以最终运行在Android和iOS系统上的。
<img src="https://static001.geekbang.org/resource/image/e7/fc/e7ecbd5c21895e396c14154b2f226dfc.png" alt="">
可以看到除了Flutter本身的代码、资源、依赖和配置之外Flutter工程还包含了Android和iOS的工程目录。
这也不难理解因为Flutter虽然是跨平台开发方案但却需要一个容器最终运行到Android和iOS平台上所以**Flutter工程实际上就是一个同时内嵌了Android和iOS原生子工程的父工程**我们在lib目录下进行Flutter代码的开发而某些特殊场景下的原生功能则在对应的Android和iOS工程中提供相应的代码实现供对应的Flutter代码引用。
Flutter会将相关的依赖和构建产物注入这两个子工程最终集成到各自的项目中。而我们开发的Flutter代码最终则会以原生工程的形式运行。
### 工程代码
在对Flutter的工程结构有了初步印象之后我们就可以开始学习Flutter的项目代码了。
Flutter自带的应用模板也就是这个计数器示例对初学者来说是一个极好的入门范例。在这个简单示例中从基础的组件、布局到手势的监听再到状态的改变Flutter最核心的思想在这60余行代码中展现得可谓淋漓尽致。
为了便于你学习理解领会构建Flutter程序的大体思路与关键技术而不是在一开始时就陷入组件的API细节中我删掉了与核心流程无关的组件配置代码及布局逻辑在不影响示例功能的情况下对代码进行了改写并将其分为两部分
- 第一部分是应用入口、应用结构以及页面结构可以帮助你理解构建Flutter程序的基本结构和套路
- 第二部分则是页面布局、交互逻辑及状态管理能够帮你理解Flutter页面是如何构建、如何响应交互以及如何更新的。
首先,我们来看看**第一部分的代码**,也就是应用的整体结构:
```
import 'package:flutter/material.dart';
void main() =&gt; runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) =&gt; MaterialApp(home: MyHomePage(title: 'Flutter Demo Home Page'));
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() =&gt; _MyHomePageState();
}
class _MyHomePageState extends State&lt;MyHomePage&gt; {
Widget build(BuildContext context) =&gt; {...};
}
```
在本例中Flutter应用为MyApp类的一个实例而MyApp类继承自StatelessWidget类这也就意味着应用本身也是一个Widget。事实上在Flutter中Widget是整个视图描述的基础在Flutter的世界里包括应用、视图、视图控制器、布局等在内的概念都建立在Widget之上**Flutter的核心设计思想便是一切皆Widget**。
Widget是组件视觉效果的封装是UI界面的载体因此我们还需要为它提供一个方法来告诉Flutter框架如何构建UI界面这个方法就是build。
在build方法中我们通常通过对基础Widget进行相应的UI配置或是组合各类基础Widget的方式进行UI的定制化。比如在MyApp中我通过MaterialApp这个Flutter App框架设置了应用首页即MyHomePage。当然MaterialApp也是一个Widget。
MaterialApp类是对构建material设计风格应用的组件封装框架里面还有很多可配置的属性比如应用主题、应用名称、语言标识符、组件路由等。但是这些配置属性并不是本次分享的重点如果你感兴趣的话可以参考Flutter官方的[API文档](https://api.flutter.dev/flutter/material/MaterialApp/MaterialApp.html)来了解MaterialApp框架的其他配置能力。
MyHomePage是应用的首页继承自StatefulWidget类。这代表着它是一个有状态的WidgetStateful Widget而_MyHomePageState就是它的状态。
如果你足够细心的话就会发现虽然MyHomePage类也是Widget但与MyApp类不同的是它并没有一个build方法去返回Widget而是多了一个createState方法返回_MyHomePageState对象而build方法则包含在这个_MyHomePageState类当中。
那么,**StatefulWidget与StatelessWidget的接口设计为什么会有这样的区别呢**
这是因为Widget需要依据数据才能完成构建而对于StatefulWidget来说其依赖的数据在Widget生命周期中可能会频繁地发生变化。由State创建Widget以数据驱动视图更新而不是直接操作UI更新视觉属性代码表达可以更精炼逻辑也可以更清晰。
在了解了计数器示例程序的整体结构以后,我们再来看看这个**示例代码的第二部分**,也就是页面布局及交互逻辑部分。
```
class _MyHomePageState extends State&lt;MyHomePage&gt; {
int _counter = 0;
void _incrementCounter() =&gt; setState(() {_counter++;});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Widget.title)),
body: Text('You have pushed the button this many times:$_counter')),
floatingActionButton: FloatingActionButton(onPressed: _incrementCounter)
);
}
```
_MyHomePageState中创建的Widget Scaffold是Material库中提供的页面布局结构它包含AppBar、Body以及FloatingActionButton。
- AppBar是页面的导航栏我们直接将MyHomePage中的title属性作为标题使用。
- body则是一个Text组件显示了一个根据_counter属性可变的文本You have pushed the button this many times:$_counter
- floatingActionButton则是页面右下角的带“+”的悬浮按钮。我们将_incrementCounter作为其点击处理函数。
_incrementCounter的实现很简单使用setState方法去自增状态属性_counter。setState方法是Flutter以数据驱动视图更新的关键函数它会通知Flutter框架我这儿有状态发生了改变赶紧给我刷新界面吧。而Flutter框架收到通知后会执行Widget的build方法根据新的状态重新构建界面。
**这里需要注意的是状态的更改一定要配合使用setState。**通过这个方法的调用Flutter会在底层标记Widget的状态随后触发重建。于我们的示例而言即使你修改了_counter如果不调用setStateFlutter框架也不会感知到状态的变化因此界面上也不会有任何改变你可以动手验证一下
下面的图3就是整个计数器示例的代码流程示意图。通过这张图你就能够把这个实例的整个代码流程串起来了
<img src="https://static001.geekbang.org/resource/image/34/36/3401b99587eafa7c0c1ed446eb936f36.png" alt="">
MyApp为Flutter应用的运行实例通过在main函数中调用runApp函数实现程序的入口。而应用的首页则为MyHomePage一个拥有_MyHomePageState状态的StatefulWidget。_MyHomePageState通过调用build方法以相应的数据配置完成了包括导航栏、文本及按钮的页面视图的创建。
而当按钮被点击之后其关联的控件函数_incrementCounter会触发调用。在这个函数中通过调用setState方法更新_counter属性的同时也会通知Flutter框架其状态发生变化。随后Flutter会重新调用build方法以新的数据配置重新构建_MyHomePageState的UI最终完成页面的重新渲染。
Widget只是视图的“配置信息”是数据的映射是“只读”的。对于StatefulWidget而言当数据改变的时候我们需要重新创建Widget去更新界面这也就意味着Widget的创建销毁会非常频繁。
为此Flutter对这个机制做了优化其框架内部会通过一个中间层去收敛上层UI配置对底层真实渲染的改动从而最大程度降低对真实渲染视图的修改提高渲染效率而不是上层UI配置变了就需要销毁整个渲染视图树重建。
这样一来Widget仅是一个轻量级的数据配置存储结构它的重新创建速度非常快所以我们可以放心地重新构建任何需要更新的视图而无需分别修改各个子Widget的特定样式。关于Widget具体的渲染过程细节我会在后续的第9篇文章“Widget构建Flutter界面的基石”中向你详细介绍在这里就不再展开了。
## 总结
今天的这次Flutter项目初体验我们就先进行到这里。接下来我们一起回顾下涉及到的知识点。
首先我们通过Flutter标准模板创建了计数器示例并分析了Flutter的项目结构以及Flutter工程与原生Android、iOS工程的联系知道了Flutter代码是怎么运行在原生系统上的。
然后我带你学习了示例项目代码了解了Flutter应用结构及页面结构并认识了构建Flutter的基础也就是Widget以及状态管理机制知道了Flutter页面是如何构建的StatelessWidget与StatefulWidget的区别以及如何通过State的成员函数setState以数据驱动的方式更新状态从而更新页面。
有原生Android和iOS框架开发经验的同学可能更习惯命令式的UI编程风格手动创建UI组件在需要更改UI时调用其方法修改视觉属性。而Flutter采用声明式UI设计我们只需要描述当前的UI状态即State即可不同UI状态的视觉变更由Flutter在底层完成。
虽然命令式的UI编程风格更直观但声明式UI编程方式的好处是可以让我们把复杂的视图操作细节交给框架去完成这样一来不仅可以提高我们的效率也可以让我们专注于整个应用和页面的结构和功能。
所以在这里我非常希望你能够适应这样的UI编程思维方式的转换。
## 思考题
最后,我给你留下一个思考题吧。
示例项目代码在_MyHomePageState类中直接在build函数里以内联的方式完成了Scaffold页面元素的构建这样做的好处是什么呢
在实现同样功能的情况下如果将Scaffold页面元素的构建封装成一个新Widget类我们该如何处理
欢迎你在评论区给我留言分享你的观点,我会在下一篇文章中等待你!感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。