什么是Flutter
Flutter是google开发跨平台方案,可以实现一份代码同时运行在iOS和Android设备上。
Flutter以Dart作为开发语言,提供Meterial和Copertino两套视觉库。
可以理解为SDK工具包,有自己渲染引擎Skia,还包含一个C++引擎,
类 Flutter 的自绘引擎方案在未来会有机会大放异彩。
JIT:在运行时即时编译,在开发周期中使用,可以动态下发和执行代码,开发测试效率高,但运行速度和执行性能则会因为运行时即时编译受到影响。
AOT:即提前编译,可以生成被直接执行的二进制代码,运行速度快、执行性能表现好,但每次执行前都需要提前编译,开发测试效率低。
Dart 的特点
Dart 具有运行速 度快、执行性能好的特点。
编译原理:开发时 JIT,提升开发效率;发布时 AOT,提升性能。
跟node比较相似,但是不会面对 JS 与 Native 之间交互的问题了。
Dart 的内存策略,采用多生代算法(与 Node 有一些类似)。
线程模型依旧是单线程 Event Loop 模型,通过 isolate 进行隔离,可以降低开发难度(与 Node 也非常类似)。
Dart 的生态,这个跟 Node.js 差距十分明显,npm 还是行业中最活跃的。
而静态语法与排版方式,纯前端入门还是有一定成本
Flutter 选择 Dart 的原因
健全的类型系统,同时支持静态类型检查和运行时类型检查。
代码体积优化(TreeShaking),编译时只保留运行时需要调用的代码(不允许反射这样的隐式引用),所以庞大的 Widgets 库不会造成发布体积过大。
丰富的底层库,Dart 自身提供了非常多的库。多生代无锁垃圾回收器,专门为 UI 框架中常见的大量 Widgets 对象创建和销毁优化。
跨平台,iOS 和 Android 共用一套代码。
JIT & AOT 运行模式,支持开发时的快速迭代和正式发布后最大程度发挥硬件性能。
Native Binding。在 Android 上,v8 的 Native Binding 可以很好地实现,但是 iOS 上的 JavaScriptCore 不可以,所以如果使用 JavaScript,Flutter 基础框架的代码模式就很难统一了。而 Dart 的 Native Binding 可以很好地通过 Dart Lib 实现。
Flutter缺点
不支持反射,无法热修复。
Flutter 实现思路
自研 UI 框架,它的渲染引擎是 Skia 图形库来实现的,而开发语言选择了同时支持 JIT 和 AOT 的 Dart。不仅保证了开发效率,同时也提升了执行效率。由于 Flutter 自绘 UI 的实现方式,因此也尽可能的减少了不同平台之间的差异。也保持和原生应用一样的高性能。因此,Flutter 也是跨平台开发方案中最灵活和彻底的那个,它重写了底层渲染逻辑和上层开发语言的一整套完整解决方案。
Skia
Skia 是一个 2D 的绘图引擎库,其前身是一个向量绘图软件,Chrome 和 Android 均采用 Skia 作为绘图引擎。Skia 提供了非常友好的 API,并且在图形转换、文字渲染、位图渲染方面都提供了友好、高效的表现。Skia 是跨平台的,所以可以被嵌入到 Flutter 的 iOS SDK 中,而不用去研究 iOS 闭源的 CoreGraphics / Core Animation。
Flutter绘制流程
Dart 进行视图数据的合成,然后交给 Skia 引擎进行处理,处理之后再交给 GPU 进行数据合成,然后准备上屏。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(VSync),所以 60Hz 的屏幕就会一秒内发出 60 次这样的信号。
用户操作-》动画-〉构建-》布局元素-〉绘制元素-》合成-〉光栅化
当Vsync信号到来以后,Flutter 框架会按照图里的顺序执行一系列动作: 动画(Animate)、构建(Build)、布局(Layout)和绘制(Paint),最终生成一个场景(Scene)之后送往底层,由GPU绘制到屏幕上。
绘制流程
当调⽤ markNeedsPaint() 时, RenderObject 就会 往上的⽗节点去查找,根据 isRepaintBoundary 是否为 true,会决定是否从这⾥开 始去触发重绘。换个说法就是,确定要更新哪些区域。
通过 isRepaintBoundary 往上确定了更新区域,通过requestVisualUpdate ⽅法触发更新往下绘制。
以 Vsync 信号为驱动,在框架渲染完成之后会输出 Layer Tree。Layer Tree 被送入 Engine,Engine 会把 Layer Tree 调度到 GPU 线程,在 GPU 线程内合成(compsite)Layer Tree,然后由 Skia 2D 渲染引擎渲染后送入 GPU 显示。这里提到 Layer Tree 是因为我们即将要分析的渲染流水线绘制阶段最终输出就是这样的 LayerTree。所以绘制阶段并不是简单的调用 Paint 函数这么简单了,而是很多地方都涉及到 Layer Tree 的管理。
Skia 在 VSync 信号同步时从渲染树合成位图,然后交给 CPU 进而完成上屏。
重绘:一个典型场景就是 ScrollView。ScorllView 滚动的时候会刷新视图,从而触发内容重绘,而当滚动内容重绘时,一般情况下其它内容是不需要被重绘的。这个时候重绘边界就非常有价值了。
Widget概念
Flutter 中的 Widget 是完全不可变的!只要当视图发生变化,Flutter 就会重新创建一个新的 Widget 进行更新
Element 是 Widget 的一个实例化对象
Element 承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁; Element 是一个可变的数据结构, 可以大致理解为 Virtual DOM。可以进行 diff 更新; 可以将真正需要修改的数据同步到 RenderObject 中。最大程度的降低渲染视图的修改,提升渲染效率。
Flutter 的渲染分为 4 个部分。布局、绘制、合成、渲染,其中 布局和绘制是在 RenderObject 中完成的。 Flutter 采用深度优的方式渲染对象树,确定树中的各个对象的位置和尺寸,并把它绘制到不同图层, 绘制完成之后交给
Widget 只是 Element 的⼀个配置描述
Widget 和 Element 之间是⼀对多的关系 。实际上渲染树是由 Element 实例的节点构成的 树,⽽作为配置⽂件的 Widget 可能被复⽤到树的多个部分,对应产⽣多个Element
RenderObject 才是实际的渲染对象,Element 持有 RenderObject 和 Widget
Widget 都是通过实现 RenderBox 实现布局的
Widget 重新创建,Element 树和 RenderObject 树并不会完全重新创 建
在 newWidget 与 oldWidget 的 runtimeType 和 key 相等时会选择使⽤ newWidget 去更新已经存在的 Element 对象,不然就选 择重新创建新的 Element。
树的更新规则
找到widget对应的element节点,设置element为dirty,触发drawframe, drawframe会调用element的performRebuild()进行树重建
widget.build() == null, deactive element.child,删除子树,流程结束
element.child.widget == NULL, mount 的新子树,流程结束
element.child.widget == widget.build() 无需重建,否则进入流程5
Widget.canUpdate(element.child.widget, newWidget) == true,更新child的slot,element.child.update(newWidget)(如果child还有子节点,则递归上面的流程进行子树更新),流程结束,否则转6
Widget.canUpdate(element.child.widget, newWidget) != true(widget的classtype 或者 key 不相等),deactivew element.child,mount 新子树
Provider原理
Model变化后自动通知ChangeNotifierProvider(订阅者),ChangeNotifierProvider内部会重新构建InheritedWidget
依赖InheriterWidget的子孙Widget就会更新
带来的益处:
1.我们的业务代码更关注数据,只要更新model,则UI会自动更新,而不用在状态改变后再去手动调用setState显示更新
2.数据改变的消息传递被屏蔽了,我们无需手动处理改变事件的发布订阅,一切都被封装在Proverder中
3.大型复杂应用中,尤其是全局共享状态非常多时,使用Provider会大大简化代码逻辑,提高开发效率
生命周期
构造函数-》initState-〉didChangeDependencies-》显示在RenderTree
状态改变
组件状态改变-〉didUpdateWidget-》重新build组件树
销毁
Deactive-〉disponse-》结束
如何获取控件大小
通过 key 去获取到控件对象的BuildContext ,⽽我们也知道 BuildContext 的实现其实是 Element
图片绘制流程
1、⾸先 Image 通过 ImageProvider 得到 ImageStream 对象
2、然后 _ImageState 利⽤ ImageStream 添加监听,等待图⽚数据
3、接着 ImageProvider 通过 load ⽅法去加载并返回ImageStreamCompleter 对象
4、然后 ImageStream 会关联 ImageStreamCompleter
5、之后 ImageStreamCompleter 会通过 http 下载图⽚,再经过PaintingBinding 编码转化后,得到 ui.Codec 可绘制对象,并封装成ImageInfo 返回
6、接着 ImageInfo 回调到 ImageStream 的监听,设置给 _ImageState build的 RawImage 对象。
7、最后 RawImage 的 RenderImage 通过 paint 绘制 ImageInfo 中的ui.Codec
作用:
Image :⽤于显示图⽚的 Widget,最后通过内部的 RenderImage 绘制。
ImageProvider :提供加载图⽚的⽅式如 NetworkImage 、 FileImage、 MemoryImage 、 AssetImage 等,从⽽获取 ImageStream ,⽤于监听结果。
ImageStream :图⽚的加载对象,通过 ImageStreamCompleter 最后会返回⼀ 个 ImageInfo ,⽽ ImageInfo 内包含有 RenderImage 最后的绘制对象ui.Image 。
Stream
Stream 并不是 Flutter 中特 有的,⽽是 Dart 中⾃带的逻辑。
基于事件流驱动设计代码,然后监听订阅事件,并针对事件变换处理响应。
StreamController :如类名描述,⽤于整个 Stream 过程的控制,提供各类接 ⼝⽤于创建各种事件流。
StreamSink:⼀般作为事件的⼊⼝,提供如 add , addStream 等。
Stream:事件源本身,⼀般可⽤于监听事件或者对事件进⾏转换,如 listen
、 where 。
StreamSubscription:事件订阅后的对象,表⾯上⽤于管理订阅过等各类操 作,如 cacenl 、 pause ,同时在内部也是事件的中转关键。
Stream 分单订阅流和广播流。
多线程
因为 Dart 是 单线程应⽤ ,和⼤多数单 线程应⽤⼀样,Dart 是以 消息循环机制 来运⾏的,⽽这⾥⾯主要包含两个任务队 列,⼀个是 microtask 内部队列,⼀个是 event 外部队列,⽽ microtask 的优先级 ⼜⾼于event 。
在 Flutter 中通过 StreamBuilder 构建 Widget
Future 表示一个不会立即完成的计算过程。与普通函数直接返回结果不同的是异步函数返回一个将会包含结果的 Future。该 Future 会在结果准备好时通知调用者。
await、async
async是和await搭配使用的,await只在async函数中出现。在async 函数里可以没有await或者有多个await。
Stream 是一系列异步事件的序列。其类似于一个异步的 Iterable,不同的是当你向 Iterable 获取下一个事件时它会立即给你,但是 Stream 则不会立即给你而是在它准备好时告诉你。
状态管理
局部状态:
Flutter提供了类似StatefulWidget、InheritWidget组件来实现局部状态管理,当这些Widget发生变化时,所有子树中依赖其数据的widget都会进行rebuild。
全局状态:
Flutter没有提供原生的全局状态管理机制,虽然可以在根布局控件使用InheritWidget来实现全局状态管理,但是这样会存在类似依赖传递过深等问题。因此大多数情况下,需要依赖一些第三方库实现全局状态管理
最简单的状态管理
我们可以使用 State + InheritedWidget实现最简单的状态管理机制
Stream在Flutter中标志着的事件流或者管道一类的概念,通过Stream可以快速的实现给予事件流驱动的业务逻辑。界面通过订阅事件,并针对各个事件进行变化处理,实现响应式更新界面。
无论是Flutter原生提供的Stream,还是ReactiveX提供的RxDart,亦或是Provider,以及没有在文章中出现的scoped_model、阿里开源的fish_redux,这一系列的组件,都为我们提供了一个很好的状态管理机制,而我们在使用过程中,大可通过自身业务需求,按需选型。
而setState的刷新机制,其实我们大家应该都知道,实质上是调用了markNeedsBuild,markNeedsBuild方法会标记element为dirty,这样在下一帧WidgetsBinding.drawFrame的时候,会进行绘制
如下图所示,在 scoped_model 的整个实现流程中, ScopedModel 这个 Widget 很 巧妙的借助了 AnimatedBuildler 。 因为 AnimatedBuildler 继承了 AnimatedWidget ,在 AnimatedWidget 的⽣命周期
中会对 Listenable 接⼝添加监听,⽽ Model 恰好就实现了 Listenable 接⼝
AnimatedWidget 和 Listenable 在 Flutter
中的特性组合,⾄于 ScopedModelDescendant 就只是为了跨 Widget 共享 Model
⽽做的⼀层封装,主要还是通过 ScopedModel.of
Model 对象,这这个实现上, scoped_model 依旧利⽤了 Flutter 的特性控件
InheritedWidget 实现。
Flutter 中 context 的实现是 Element , 在 Element 的 inheritFromWidgetOfExactType ⽅法实现⾥,有⼀个 Map<Type,
InheritedElement> _inheritedWidgets 的对象。
BloC 全称 Business Logic Component ,它属于⼀种设计模式,在 Flutter 中它主 要是通过 Stream 与 SteamBuilder 来实现设计的
redux 中⼀般有 Store 、 Action 、 Reducer 三个主要对 象
创建 Store ⽤于管理状态 。 给 Store 增加 appReducer 合集⽅法,增加需要拦截的 middleware ,并初 始化状态。 将 Store 设置给 StoreProvider 这个 InheritedWidget 。
通过 StoreConnector / StoreBuilder 加载显示 Store 中的数据。
手势
⼿势 信息被打包成 ByteBuffer 进⾏传递,最后在 Dart 层的
_dispatchPointerDataPacket ⽅法中,通过 _unpackPointerDataPacket ⽅法解析成 可⽤的 PointerDataPacket 对象使⽤。
GestureBinding 的 _handlePointerEvent ⽅法中主要是 hitTest
和 dispatchEvent : 通过 hitTest 碰撞,得到⼀个包含控件的待处理成员列表
HitTestResult ,然后通过 dispatchEvent 分发事件并产⽣竞争,得到胜利者相 应。
因为 RenderObject 默认都实现了 HitTestTarget 接⼝,所以可以理解为:
HitTestTarget ⼤部分时候都是 RenderObject ,⽽ HitTestResult 就是⼀个带着 碰撞测试后的控件列表。
递归就让我们⾃下⽽上的得到了⼀个 HitTestResult 的相应控件列表了,最底 下的 Child 在最上⾯。
只有带有 RenderPointerListener (RenderObject) / Listener (Widget) 的才 会处理 handleEvent 事件,并且从上述源码可以看出,handleEvent 的执⾏是不 会被拦截打断的
事件竞争
通过⼀个竞技场,各个 控件参与竞争,直接胜利的或者活到最后的第⼀位,你就获胜得到了胜利。
GestureRecognizer :⼿势识别器基类
GestureArenaManagerr :⼿势竞技管理,它管理了整个“战争”的过程,原则上 竞技胜出的条件是 :第⼀个竞技获胜的成员或最后⼀个不被拒绝的成员。
GestureArenaEntry :提供⼿势事件竞技信息的实体,内封装参与事件竞技的 成员。
所有竞技的成员可以理解为就是 GestureRecognizer 之间的竞争。
如果⼀个⼿势试图在竞技场开 放时(isOpen=true)获胜,它将成为⼀个带有“渴望获胜”的属性的对象。当竞 技场关闭(isOpen=false)时,竞技场将寻找⼀个“渴望获胜”的对象成为新的参 与者,如果这时候刚好只有⼀个,那这⼀个参与者将成为这次竞技场胜利的⻘ 睐存在。
⽽事件流程中第⼀个事件⼀般都会是PointerDownEvent 。
Down 事件时通过 addPointer 加⼊了 GestureRecognizer 竞技场的区域,在没移 除的情况下,事件可以参加后续事件的竞技,在某个事件阶段移除的话,之后的事 件序列也会⽆法接受。事件的竞争如果没有胜利者,在 UP 流程中会强制指定第⼀ 个为胜利者。
state
⽽事实上 State 实现跨帧共享,就是将 State
保存在 Element 中,这样 Element 每次调⽤ Widget build() 时,是通过
state.build(this); 得到的新 Widget ,所以写在 State 的数据就得以复⽤了。
setState ,其实是调⽤了 markNeedsBuild , markNeedsBuild 内部 会标记 element 为 diry ,然后在下⼀帧 WidgetsBinding.drawFrame 才会被绘 制,这可以也看出 setState 并不是⽴即⽣效的。
InheritedWidget
共享的是 Widget ,只是这个 Widget 是⼀个
ProxyWidget ,它⾃⼰本身并不绘制什么,但共享这个 Widget 内保存有的值, 却达到了共享状态的⽬的。用于子节点向祖先节点获取数据的机制,子节点状态变更,向上上报通过发送通知的方式
Provider
VirtualDisplay
VirtualDisplay 类似于⼀个虚拟显示区域,需要结合 DisplayManager ⼀起 调⽤,⼀般在副屏显示或者录屏场景下会⽤到。 VirtualDisplay 会将虚拟 显示区域的内容渲染在⼀个 Surface 上。
在 iOS 平台上就不使⽤类似 VirtualDisplay 的⽅法,⽽是通过将 Flutter UI 分为两个透明纹理来完成组合:⼀个在 iOS 平台视图之下,⼀个在其上⾯。
Layer
Widget -> Element ->RenderObject -> Layer 这样的变化过程,⽽其中 Layer 的组成由
RenderObject 中的 isRepaintBoundary 标志位决定。
Layer绘制
先是创建了 PictureRecorder ; 然后使⽤ PictureRecorder 创建了 Canvas ; 之后使⽤ Canvas 绘制蓝⾊⼩⽅块; 结束绘制后通过 SceneBuilder 的 pushOffset 和 addPicture 加载了绘制的 内容;
通过 window.render 绘制出画⾯。
Scene 和 Layer 之间的苟且
Layer分为ContainerLayer和非ContainerLayer
ContainerLayer 是可以具备⼦节点,也就是带有 append ⽅法,⼤致可以分为: 位移类( OffsetLayer / TransformLayer ); 透明类( OpacityLayer ) 裁剪类( ClipRectLayer / ClipRRectLayer / ClipPathLayer );
阴影类 ( PhysicalModelLayer )
Layer 是如何更新?这就涉及了 Layer 内部的 markNeedsAddToScene 和
updateSubtreeNeedsAddToScene 这两个⽅法
当_needsAddToScene 等于 true 时,对应 Layer 的 addToScene 才会被调⽤;⽽ 当 Layer 的 _needsAddToScene 为 false 且 _engineLayer 不为空时就触发
Layer 的复⽤。
runApp 的时候创建了 RenderView ,并且 RenderView 内部的
compositeFrame 就是通过 _window.render 来提交 Layer 的绘制。
Flutter Framework 的 Layer 在绘制之前,需要经历
SceneBuinlder 的处理得到 EngineLayer ,其实 Flutter Framework 中的 Layer
可以理解为 SceneBuinlder 的对象封装,⽽ EngineLayer 才是真正的 Engine 图 层 ,在之后得到的 Scene 会被提交 Engine 绘制。
Dart
var 的语法糖和 dynamic、各类操作符、⽀持操作符重载、⽅法当做参数传递
async await / async* yield
Mixins
mixin是为了解决继承方面的问题而引入的机制,Dart为了支持多重继承,引入了mixin关键字,它最大的特殊处在于:mixin定义的类不能有构造方法
isolate
可以理解为iOS中的RunLoop,主线程默认开启RunLoop循环,子线程需要自己手动添加
每个isolate包含一个事件循环以及两个事件队列,event loop事件循环,以及event queue和microtask queue事件队列,event和microtask队列有点类似iOS的source0和source1。
event queue:负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件。
microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。
编译流程
gen_snapshot是dart编译器,采用了tree shaking(类似依赖树逻辑,可生成最小包,也因而在Flutter中禁止了dart支持的反射特性)等技术,用于生成汇编形式的机器代码,再通过xcrun等编译工具链生成最终的App.framework。换句话说,所有的dart代码,包括业务代码,三方package代码,它们所依赖的flutter框架代码,最终将会变成App.framework。
ChangeNotifier
StreamBuilder
MediaQuery
MediaQuery 是⼀个 InheritedWidget ,它会往下共享对应的
MediaQueryData ,在 MediaQueryData 中保存了各种设备的信息,⽐如 size
、 devicePixelRatio 、 textScaleFactor 、 viewPadding 以及 viewInsets 等。
Navigator
其实是⼀个 StatefulWidget ,当 MaterialApp 被更新时,可以看到在 NavigatorState 的
didUpdateWidget 回调中会调⽤ _history ⾥所有路由的 changedExternalState()
⽅法。
MediaQueryData.fromWindow(WidgetsBinding.instance.window) ,之后⼜恰好在有键
盘的⻚⾯打开后触发了 MaterialApp 的更新,导致了 PageRoute 重新 builder , 使得没有键盘的 Scaffold 使⽤了弹出键盘的 viewInsets.bottom 。 所以这⾥只需要将 MediaQueryData.fromWindow 换成 MediaQuery.of(context) 就可 以解决问题,⽽当在没有 context 或者需要直接使⽤ MediaQueryData.fromWindow
时,那⼀定要搭配上 WidgetsBindingObserver.didChangeMetrics 配合更新。
Widget :就是我们平常写的控件, Flutter 宇宙中万物皆 Widget ,它们 都是不可变⼀帧,同时也是被⼈吐槽很多的嵌套模式,当然换个⻆度,事实上 你把他当作 Widget 配置⽂件来写或者就好理解了。
Element :它是 BuildContext 的实现类, Widget 实现跨帧保存的 state就是存放在这⾥,同时它也充当了 Widget 和 RenderObject 之间的桥梁。
RenderObject :它才是真正⼲活(layout、paint)等,同时它才是真实的“dom” 。
Layer :⼀整块的重绘区域(isRepaintBoundary),决定重绘的影响区域。
skia 在绘制的时候, saveLayer 是⽐较消耗性能的,⽐如透明合 成、 clipRRect 等等都会可能需要 saveLayer 的调⽤, ⽽ saveLayer 会 清空GPU绘制的缓存,导致性能上的损耗,所以开发过程中如果掉帧严重, 可以针对这⼀块进⾏优化。
Flutter Framework 使用 Dart 语言开发,所以 App 进程中需要一个 Dart 运行
环境(VM),和 Android Art 一样,Flutter 也对 Dart 源码做了 AOT 编译,直接将
Dart 源码编译成了本地字节码,没有了解释执行的过程,提升执行性能。这里重点
关注 Dart VM 内存分配 (Allocate) 和回收 (GC) 相关的部分。
和 Java 显著不同的是 Dart 的”线程”(Isolate) 是不共享内存的,各自的堆
(Heap) 和栈 (Stack) 都是隔离的,并且是各自独立 GC 的,彼此之间通过消息通道
来通信。Dart 天然不存在数据竞争和变量状态同步的问题,整个 Flutter Framework Widget 的渲染过程都运行在一个 isolate 中。
老年代 (Old Generation): 在新生代的 GC 中“幸存”下来的对象,它们会被
转移到老年代中。老年代存放生命力周期较长,内存较大的对象。老年代通常
比新生代要大很多。老年代的 GC 回收采用“标记 - 清除”算法,分成标记和
清除两个阶段。在标记阶段会触发停顿 (stop the world),多线程并发的完成
对垃圾对象的标记,降低标记阶段耗时。在清理阶段,由 GC 线程负责清理回
收对象,和应用线程同时执行,不影响应用运行。
原生通信
Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:
BasicMessageChannel:用于传递字符串和半结构化的信息。
MethodChannel:用于传递方法调用(method invocation)。
EventChannel: 用于数据流(event streams)的通信。
Dart 类型
在dart中的一切皆是对象,包括数字、布尔值、函数等,它们和Java一样都继承于Object, 所以它们的默认值也就是null. 在dart主要有: 布尔类型bool、数字类型num(数字类型又分为int,double,并且两者父类都是num)、字符串类型String、集合类型(List, Set, Map)、Runes类和Symbols类型(后两个用的并不太多)
flutter怎么调用native
1.创建插件
flutter create –template=plugin XXXX
2.打开Android 和iOS 工程,添加原生调用的类或插件
3.编写交互接口
onMethodCall(MethodCall call, Result result)
MethodCall call:请求本身
Result result:结果处理方法
如:
if (call.method.equals(“calculate”))
int a = call.argument(“a”);
int b = call.argument(“b”);
int r = a + b;
result.success(“” + r);
Flutter AOT和JIT
Embedder
Platform Runner
UI Runner
GPU Runner
IO Runner
学习流程
Flutter设计原理
Dart语法
Flutter UI
页面还原
Widget
页面布局
依赖管理
生命周期
状态管理
路由导航
动画效果
屏幕适配
用户交互
点击判断
手势操作
线程模型
原理解析
事件
UI
开发模式
混合开发
独立开发
组件封装
基础组件
业务组件