isolate可以理解为是概念上Thread线程,Dart 是单线程的并且 Flutter 依赖于 Dart,在Dart中并不是叫Thread ,而是有个专门名词叫 isolate,也可以直接理解为是 Dart 中的线程。
isolate和Thread都是会去创建一个OSThread,也就是说最终都是委托创建操作系统层面的线程。isolate与线程的区别就是线程与线程之间是共享内存的,而isolate和 isolate之间是内存不共享的。 两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。
优点
热重载(Hot Reload),利用Android Studio直接一个ctrl+s就可以保存并重载,模拟器立马就可以看见效果,相比原生冗长的编译过程强很多;
一切皆为Widget的理念,对于Flutter来说,手机应用里的所有东西都是Widget,通过可组合的空间集合、丰富的动画库以及分层课扩展的架构实现了富有感染力的灵活界面设计;
借助可移植的GPU加速的渲染引擎以及高性能本地代码运行时以达到跨平台设备的高质量用户体验。简单来说就是:最终结果就是利用Flutter构建的应用在运行效率上会和原生应用差不多。
缺点
不支持热更新;
三方库有限,需要自己造轮子;
Dart语言编写,增加了学习难度,并且学习了Dart之后无其他用处,相比JS和Java来说。
BasicMessageChannel:
基本的消息通道,用于传递字符串和半结构化的信息。它是一个面向对象的模型,允许在 Flutter 和其它平台之间进行简单而可靠的通信。
MethodChannel:
方法通道,用于传递方法调用以及相应的参数和返回值。它可以让 Flutter 应用程序和原生平台之间进行双向通信,即从 Flutter 向原生平台请求执行某个方法,同时也可以从原生平台向 Flutter 发送消息。
EventChannel:用于数据流(event streams)的通信。
三种Channel之间相互独立,各有用途,但在设计上非常相似。每种Channel 均有三个重要的成员变量:String 类型的 name,代表 Channel 的名字,也是其唯一的标识符;BinaryMessenger 类型的 messager,代表消息信使,是消息的发送与接收的工具;MessageCodec 类型或 MethodCodec 类型的 Codec,代表消息的编解码器。
它们的通信工具都是BinaryMessager。BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当初始化一个 Channel,并向该 Channel注册处理消息的 Handler 时,实际上会生成一个与之对应的 BinaryMessageHandler,并 以 channel name 为 Key,注册到 BinaryMessenger 中。 当Flutter端将消息发送到BinaryMessenger时,BinaryMessenger会根据其入参Channel找到对应的BinaryMessageHandler,并交由其处理。
BinaryMessenger 并不知道 Channel 的 存 在, 它 只 和 BinaryMessageHandler 打交 道。 而 Channel 和 BinaryMessageHandler 则 是 一 一 对 应 的。由 于 Channel 从 BinaryMessageHandler 接收到的消息是二进制格式数据,无法直接使用,故 Channel 会将该二进制消息通过 Codec(消息编解码器)解码为能识别的消息,并传递给 Handler 处理。
当 Handler 处理完消息之后,会通过回调函数返回 result,并将 result 通过编解码器编码为二进制格式数据,通过 BinaryMessenger 发送回 Flutter 端。
删除未使用的资源:
在项目中,可能会有很多没有被使用到的资源文件,这些资源文件会占据 APK 的空间,并且在启动时也会增加资源解析的时间。因此,我们需要通过移除未使用的资源来减小 APK 的大小。
优化图片资源:
图片是 APK 中最占用空间的部分之一,因此对图片进行压缩和优化是非常重要的。Flutter 自带了一个用于图片压缩的工具,我们可以使用它来压缩图片并减小 APK 的大小。
使用代码混淆:
代码混淆是一种将代码转换成难以阅读和理解的形式的技术,可以隐藏应用程序的业务逻辑和算法细节,从而保护应用程序免受恶意攻击。同时,代码混淆还可以有效地减小 APK 的大小。
拆分包:
将功能模块拆分成多个 APK 包,只有用户需要使用某个功能模块时才会下载相应的 APK 包。这样可以减小 APK 的体积,提高用户下载的速度,并且可以根据用户的需求动态更新模块。
使用 AOT 编译:
默认情况下,Flutter 会使用 JIT 编译方式生成 dex 文件,这种方式可以使开发过程更加快速和高效。但是在生产环境中,我们可以使用 AOT(Ahead of Time)编译方式,将 Dart 代码编译成本地机器代码,从而提高应用程序的启动速度和运行效率。
createState:创建 State 对象时调用。
initState:初始化 State 对象时调用,通常在此方法中进行数据初始化、订阅事件等操作。
didChangeDependencies:当 State 对象所依赖的 InheritedWidget 发生改变时调用,主要用于处理 InheritedWidget 的 build 方法被调用后 State 中使用到该 Widget 的数据已经发生改变的情况。
build:构建 UI 时调用,用于返回需要渲染的 Widget。
didUpdateWidget:当父组件发生改变或者调用 setState 方法时,会触发子组件的 didUpdateWidget 方法。此方法提供了旧的 Widget 参数,可以通过比较新旧参数的不同来执行必要的操作,如数据更新、动画播放等。
setState:用于修改 State 对象中的数据,并触发 rebuild。
deactivate:State 对象从树中移除时调用。
dispose:State 对象被永久地从内存中移除时调用,通常在此方法中进行资源释放、事件取消订阅等操作。
didUpdateWidget 方法在父组件调用 setState 方法或者父组件重新构建时被调用。当父组件传递给子组件的 props 发生变化时,就会引起子组件的重新渲染,这个时候就会触发 didUpdateWidget 方法。在这个方法中,可以通过比较新旧参数的不同来执行必要的操作,如数据更新、动画播放等。需要注意的是,在 didUpdateWidget 中不能调用 setState 方法,否则会导致死循环。
Dart 默认都是公开的,没有private public等属性,私有变量使用 下划线 _开头
Dart是值传递。每次调用函数,传递过去的都是对象的内存地址,而不是这个对象的复制。
Dart中,一切都是对象,所有的对象都是继承自Object
Dart支持顶层方法,如main方法,可以在方法内部创建方法
Dart支持顶层变量,也支持类变量或对象变量
Dart是单线程模型,其中用官方语言描述:Dart在单线程中是以消息循环机制来运行的,其中包含两个任务队列,MicroTask Queue 微任务队列和 event Queue事件队列。事件循环的运行遵循规则如下,入口为mian()方法执行完成后,消息循环机制就启动了,首先按照先进先出的顺序逐个执行微任务队列中任务,当所有微任务队列执行完成后开始执行事件队列中任务,事件任务队列执行完成后再去执行微任务,如此循环往复。
main()–》MicroTask Queue --〉》Event Queue
一般通过两种方式:scheduleMicrotask和 Timer.run以event loop的方式异步执行
scheduleMicrotask((){
print('a microtask');
});
Timer.run((){
print('a event queue ');
});
Dart中执行一个异步任务使用Future来处理,在Dart的每一个isolate线程中,执行的优先级是main()–MicroTask-- Event Queue
ioslate可以理解为我们java中的的Thread线程,但是不同于线程的地方在于isolate之间内存是不共享的,两个isolate之间完成是独立的执行线,每个isolate都有自己的事件循环,他们之间只能通过发送消息通信。
Dart在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是我们以上说的微任务队列和事件队列,Future默认情况是往事件队列里插入一个事件,当有空余时间的时候就去执行,当执行完毕后会回调Future.then()方法。
我们也可以通过Future.microtask方法往微任务中插入任务,这样就可以提高它的执行效率。
Stream和Future一样都是用来处理异步的工具
其中Future表示稍后获得的一个数据,只能表示一次异步获得的数据
Stream表示多次异步获得的数据,比如页面上的按钮可被用户点击多次,按钮上的一次点击事件就是一个Stream
Dart中统一使用Stream处理异步事件流
Stream 用来处理连续的异步操作,Stream 是一个抽象类,用于表示一序列异步数据的源。它是一种产生连续事件的方式,可以生成数据事件或者错误事件,以及流结束时的完成事件。
异步模式是StreamMicrotask
有两种订阅模式 单订阅和多订阅 single 和 broadcast
Stream默认处于单订阅模式,所以同一个stream上的listen和其它大多数方法只能调用一次,调用第二次就会报错,但Stream可以通过transform方法返回另一个stream进行连续调用,通过Stream.asBroadcastStream()可以将一个单订阅模式的Stream转换成一个多订阅模式的Stream,isBroadcast属性可以判断当前处于哪种模式下。
大致分为四个阶段
1.初始化createState 和 initState;
2.组件创建didChangeDependencies 和build;
3.触发build(因为didChangeDependencies、setState 或者
didUpdateWidget而引发build );
4.销毁deactivate 和 dispose;
didChangeDependencies
widget树中,若节点的父级结构中的层级 或 父级结构中的任一节点的widget类型有变化,节点会调用didChangeDependencies;若仅仅是父级结构某一节点的widget的某些属性值变化,节点不会调用didChangeDependencies
didUpdateWidget
widget树中,若节点调用setState方法,节点本身不会触发didUpdateWidget,此节点的子节点 会 调用didUpdateWidget
局部刷新 StatefulBuilder & FutureBuilder & StreamBuilder
StatefulBuilder & FutureBuilder & StreamBuilder都是通过状态构建器来实现局部刷新的功能
StatefulBuilder(
builder: (BuildContext context,
void Function(void Function()) setState) {});
FutureBuilder(future: mockNetworkData(),
builder: (BuildContext context, AsyncSnapshot snapshot){});
StreamBuilder(stream: counter(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot){})
首先什么是Widget
widget在flutter里基本是一些UI组件有两种类型的widget,分别是statefulWidget 和 statelessWidget两种,statelessWidget不会自己重新构建自己,但是statefulWidget会。
1、组合类:StatelessWidget和StatefulWidget
2、代理类:inheritedwidget、ParentDataWidget
inheritedwidget一般用于状态共享,如Theme 、Localizations 、 MediaQuery 等,都是通过它实现共享状态,这样我们可以通过 context 去获取共享的状态,比如 ThemeData theme = Theme.of(context);
3、绘制类:RenderObjectWidget
RenderObject 的布局相关方法调用顺序是 : layout -> performResize -> performLayout -> markNeedsPaint
主要是为了解决多个部件之间的交互和部件自身状态的维护。
1、Widget: 在Flutter中,几乎所有东西都是Widget。将一个Widget想象为一个可视化的组件(或与应用可视化方面交互的组件),当你需要构建与布局直接或间接相关的任何内容时,你正在使用Widget。
2、Widget树: Widget以树结构进行组织。包含其他Widget的widget被称为父Widget(或widget容器)。包含在父widget中的widget被称为子Widget。
3、Context: 仅仅是已创建的所有Widget树结构中的某个Widget的位置引用。简而言之,将context作为widget树的一部分,其中context所对应的widget被添加到此树中。一个context只从属于一个widget,它和widget一样是链接在一起的,并且会形成一个context树。
4、State: 定义了StatefulWidget实例的行为,它包含了用于”交互/干预“Widget信息的行为和布局。应用于State的任何更改都会强制重建Widget。
Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:
BasicMessageChannel:用于传递字符串和半结构化的信息。
MethodChannel:用于传递方法调用。Flutter主动调用Native的方法,并获取相应的返回值。
EventChannel:用于数据流(event streams)的通信。
Flutter 中通过 PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了 Presentation + VirtualDisplay + Surface 等实现的,
大致原理:
使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManager 的 createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surface 的 id 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来,实时控件截图渲染显示技术。
main函数是类似于java语言的程序运行入口函数
runApp函数是渲染根widget树的函数
一般情况下runApp函数会在main函数里执行
Hot Reload比Hot Restart快,Hot Reload会编译我们文件里新加的代码并发送给dart虚拟机,dart会更新widgets来改变UI,而Hot Restart会让dart 虚拟机重新编译应用。另一方面也是因为这样, Hot Reload会保留之前的state,而Hot Restart回你重置所有的state回到初始值。
async 是和await搭配使用,await只在async函数中出现,在async函数可以没有await活着有多个await;
await的出现会把await之前和之后的代码分为两个部分,await并不像字面意思所表示的程序运行到这里就阻塞,而是立刻结束当前函数执行并返回一个future,函数内剩余代码通过调度异步执行。
key是Widgets,Elements和SemanticsNodes的标识符。
key有LocalKey 和 GlobalKey两种。
LocalKey 如果要修改集合中的控件的顺序或数量。GlobalKey允许 Widget 在应用中的任何位置更改父级而不会丢失 State。
profile model 是用来评估app性能的,profile model 和release mode是相似的,只有保留了一些需要评估app性能的debug功能。在模拟器上profile model是不可用的。
怎么做到只在debug mode运行代码?
foundation有一个静态的变量kReleaseMode来表示是否是release mode。
await for是不断获取stream流中的数据,然后执行循环体中的操作。它一般用在直到stream什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞。
Widget :仅用于存储渲染所需要的信息。
RenderObject :负责管理布局、绘制等操作。
Element :才是这颗巨大的控件树上的实体。
Widget会被inflate(填充)到Element,并由Element管理底层渲染树。Widget并不会直接管理状态及渲染,而是通过State这个对象来管理状态。Flutter创建Element的可见树,相对于Widget来说,是可变的,通常界面开发中,我们不用直接操作Element,而是由框架层实现内部逻辑。就如一个UI视图树中,可能包含有多个TextWidget(Widget被使用多次),但是放在内部视图树的视角,这些TextWidget都是填充到一个个独立的Element中。Element会持有renderObject和widget的实例。记住,Widget 只是一个配置,RenderObject 负责管理布局、绘制等操作。
在第一次创建 Widget 的时候,会对应创建一个 Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 不会被重建,只是更新而已。
WidgetTree:存放渲染内容、它只是一个配置数据结构,创建是非常轻量的,在页面刷新的过程中随时会重建
Element 是分离 WidgetTree 和真正的渲染对象的中间层, WidgetTree 用来描述对应的Element 属性,同时持有Widget和RenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构。
RenderObject (渲染树)用于应用界面的布局和绘制,负责真正的渲染,保存了元素的大小,布局等信息,实例化一个 RenderObject 是非常耗能的
当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。
Flutter只关心向 GPU提供视图数据,GPU的 VSync信号同步到 UI线程,UI线程使用 Dart来构建抽象的视图结构,这份数据结构在 GPU线程进行图层合成,视图数据提供给 Skia引擎渲染为 GPU数据,这些数据通过 OpenGL提供给 GPU
Debug模式下使用JIT编译模式,即Just in time(即时编译),Release下使用AOT模式,即Ahead of time(提前编译)。JIT模式因为需要边运行边编译,所以会占用运行时内存,导致卡顿现象,但是有动态编译效果对于开发者来说非常方便调试。AOT模式提前编译不会占用运行时内存,相对来说运行流畅,但是会导致编译时间增加。
首先mixin是一个定义类的关键字。直译出来是混入,混合的意思 Dart为了支持多重继承,引入了mixin关键字,它最大的特殊处在于: mixin定义的类不能有构造方法,这样可以避免继承多个类而产生的父类构造方法冲突。
继承(关键字 extends)、混入 mixins (关键字 with)、接口实现(关键字 implements)。这三者可以同时存在,前后顺序是extends -> mixins -> implements。
Flutter中的继承是单继承,子类重写超类的方法要用@Override,子类调用超类的方法要用super。
在Flutter中,Mixins是一种在多个类层次结构中复用类代码的方法。mixins的对象是类,mixins绝不是继承,也不是接口,而是一种全新的特性,可以mixins多个类,mixins的使用需要满足一定条件。
使用mixins的条件:
mixins类只能继承自object;mixins类不能有构造函数;一个类可以mixins多个mixins类;可以mixins多个类,不破坏Flutter的单继承
Flutter Engine层会创建一个Isolate,并且Dart代码默认就运行在这个主Isolate上。必要时可以使用spawnUri和spawn两种方式来创建新的Isolate,在Flutter中,新创建的Isolate由Flutter进行统一的管理。
事实上,Flutter Engine自己不创建和管理线程,Flutter Engine线程的创建和管理是Embeder负责的,Embeder指的是将引擎移植到平台的中间层代码。
Flutter 中存在的四大线程:分别为 UI Runner、GPU Runner、IO Runner, Platform Runner (原生主线程) ,
在 Flutter 中可以通过 isolate 或者 compute 执行真正的跨线程异步操作。
1、如果一段代码不会被中断,那么就直接使用正常的同步执行就行。
2、如果代码段可以独立运行而不会影响应用程序的流畅性,建议使用 Future (需要花费几毫秒时间)
3、如果繁重的处理可能要花一些时间才能完成,而且会影响应用程序的流畅性,建议使用 isolate (需要几百毫秒)
下面列出一些使用 isolate 的具体场景:
1、JSON解析: 解码JSON,这是HttpRequest的结果,可能需要一些时间,可以使用封装好的 isolate 的 compute 顶层方法。
2、加解密: 加解密过程比较耗时
3、图片处理: 比如裁剪图片比较耗时
4、从网络中加载大图