flutter基础知识

优势

使用flutter可以满足公司业务快速迭代,创造不少价值,flutter确实有一定优势,主要体现在这⼏个⽅⾯:
1.跨平台:增加代码复用,降低开发成本
2.性能:Flutter直接将Dart代码编译为本地代码运行,再调用skia绘图引擎代码,和原生一样,这就少了像reactnative和weex等先转为原声控件,再系统渲染的步骤
3.热重载:修改完代码后 Ctrl+S 就能实时展现在真机界面上,不需要重新安装 apk 包,提高开发效率

unknown_filename|600

但是flutter也有一些缺点
1.脱离不开原生,一些功能不支持,需要原生开发
2.适配问题,可能会有各种各样的适配问题
3.基础库不完善

widget 树相当于配置,元素树相当于实例对象。widget 相当于 json,元素树相当于 json 解析后的 bean。

W(A)和 W(B)交换后调用 setState 框架发生了啥?
将自身元素对象标记为脏元素并放到脏元素数组中,期间会触发 Vsync 信号,等待系统更新脏元素数组中的元素。
整个过程会递归执行,因为 build 方法中是嵌套关系,会一层层遍历来执行如下过程

最终所有 Widget 都转化为 RenderObject 对象, 它们堆叠出我们想要的画面

  • Flutter 中存在 Widget 、 Element 、RenderObject 、Layer 四棵树,其中 Widget Element 是多对一的关系
  • Element 中持有Widget 和 RenderObject , 而 Element RenderObject 是一一对应的关系

最终页面的 Layout、Paint 等都会发生在 Widget 所对应的 RenderObject 子类

Flutter 只要求系统提供的 “Canvas”,然后开发者通过 Widget 生成 RenderObject “直接” 通过引擎绘制到屏幕上。

BuildContext 就是 widget 对应的 Element,所以我们可以通过 context 在 StatelessWidget 和 StatefulWidget 的 build 方法中直接访问 Element 对象

简述 Widgets、RenderObjects 和 Elements 的关系

  • Widget :仅用于存储渲染所需要的信息。
  • Element :才是这颗巨大的控件树上的实体。
  • RenderObject :负责管理布局、绘制等操作。

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 不会被重建,只是更新而已

key

在使用 Flutter 时,我们经常会遇到一个叫做 Key 的东西。Key 是 Flutter 中几乎所有 widget 都具有的属性。

Flutter 将 Key 描述为 Widget、Element 和 SemanticNodes 的标识符。这是什么意思呢?这意味着 Key 是分配给 Widget 的唯一标识,通过 key 可以与其他 Widget 区分开来。对于 Widget 在 Widget 树中改变位置的情况,Key 帮助保留它们的状态。说明 Key 大多数情况下对于有状态的 Widget 而言更有用,而对于无状态的 Widget 则不太需要。

flutter 动态化

Flutter动态化方案—源码解析(基于1.22以上版本) - 掘金 (juejin.cn)
https://juejin.cn/post/7189533148022046778?#heading-7

分包

flutter create –template=package  /Users/tal/flutteProjects/hello
flutter packages pub publish –dry-run
flutter packages pub publish –server=https://pub.dartlang.org

hello:
    path: /Users/tal/flutteProjects/hello 

flutter build apk –split-per-abi
fat-aar-android :build aar后没有打出release
assembleRelease  编译不过,报错

屏幕适配

  1. 使用屏幕尺寸的相对值:避免直接使用固定的像素值来定义尺寸,而是使用相对于屏幕尺寸的比例值。可以使用 MediaQuery. of (context). size 获取屏幕的宽度和高度,然后根据需求进行比例计算。
  2. 使用布局容器:使用诸如 Container、Expanded、Flexible 等布局容器来自动适应父容器的大小。这样可以确保界面在不同屏幕尺寸下的自适应性。
  3. 使用响应式布局:使用 Flutter 提供的响应式布局框架,如 LayoutBuilder 和 FractionallySizedBox,来根据屏幕尺寸动态调整布局。
  4. 使用自适应字体大小:根据屏幕尺寸和设备像素比例,使用 MediaQuery. of (context). textScaleFactor 来调整字体的大小,以确保在不同设备上字体的可读性和一致性。
  5. 使用屏幕方向适配:根据屏幕的方向(横向或纵向),调整布局和元素的摆放方式,以提供更好的用户体验。
  6. 使用 Flutter 插件:有一些 Flutter 插件和库可用于简化屏幕适配的工作,如 flutter_screenutil、sizer 等,它们提供了更方便的方法和工具来进行屏幕适配。

面试题

Dart

  1. Dart 当中的 「..」表示什么意思?
    Dart 当中的 「..」意思是 「级联操作符」,为了方便配置而使用。「..」和「.」不同的是调用「..」后返回的相当于是 this,而「.」返回的则是该方法返回的值。
  2. Dart 的作用域
    Dart 没有 「public」「private」等关键字,默认就是公开的,私有变量使用下划线 _开头。
  3. Dart 是不是单线程模型?是如何运行的?
    Dart 是单线程模型,Dart 在单线程中是以消息循环机制来运行的,包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。

当Flutter应用启动后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,当所有微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务,如此循环往复,生生不息。

  1. Dart 是如何实现多任务并行的?
  • Dart 是单线程的,不存在多线程,那如何进行多任务并行的呢?其实,Dart 的多线程和前端的多线程有很多的相似之处。Flutter 的多线程主要依赖 Dart 的并发编程、异步和事件驱动机制
  • Dart 提供了 Isolate 的概念,它是独立的执行单元,类似于轻量级线程。Isolate 允许在 Dart 进程中创建多个并行的执行环境,每个 Isolate 都有自己的堆内存和事件循环,并且可以执行独立的任务。
  • 在 Dart 中,一个 Isolate 对象其实就是一个 isolate 执行环境的引用,一般来说我们都是通过当前的 isolate 去控制其他的 isolate 完成彼此之间的交互,而当我们想要创建一个新的 Isolate 可以使用 Isolate. spawn 方法获取返回的一个新的 isolate 对象,两个 isolate 之间使用 SendPort 相互发送消息,而 isolate 中也存在了一个与之对应的 ReceivePort 接受消息用来处理,但是我们需要注意的是,ReceivePort 和 SendPort 在每个 isolate 都有一对,只有同一个 isolate 中的 ReceivePort 才能接受到当前类的 SendPort 发送的消息并且处理。
    image.png|600

说一下 Dart 异步编程中的 Future 关键字?
Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。
在 Java 并发编程开发中,经常会使用 Future 来处理异步或者延迟处理任务等操作。而在 Dart 中,执行一个异步任务同样也可以使用 Future 来处理。在 Dart 的每一个 Isolate 当中,执行的优先级为 : Main > MicroTask > EventQueue。

Isolate

在 Dart 中,它的线程概念被称为 Isolate。它与我们之前理解的 Thread 概念有所不同,各个 isolate 之间是无法共享内存空间,isolate 之间有自己的 event loop

我们只能通过 Port 传递消息,然后在另一个 isolate 中处理然后将结果传递回来,这样我们的 UI 线程就有更多余力处理 pipeline,而不会被卡住。将非常耗时的任务添加到事件队列后,会拖慢整个事件循环的处理,甚至是阻塞。可见基于事件循环的异步模型仍然是有很大缺点的,这时候我们就需要 Isolate,这个单词的中文意思是隔离。

简单说,可以把它理解为 Dart 中的线程。但它又不同于线程,更恰当的说应该是微线程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个 Isolate 完全是两条独立的执行线,且每个 Isolate 都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程

说一下 mixin 机制?
mixin 是 Dart 2.1 加入的特性,以前版本通常使用 abstract class 代替。简单来说,mixin 是为了解决继承方面的问题而引入的机制,Dart 为了支持多重继承,引入了 mixin 关键字,它最大的特殊处在于: mixin 定义的类不能有构造方法,这样可以避免继承多个类而产生的父类构造方法冲突。
mixins 的对象是类,mixins 绝不是继承,也不是接口,而是一种全新的特性,可以 mixins 多个类,mixins 的使用需要满足一定条件。

Flutter

flutter新引擎impller与skia的区别

Flutter框架的核心引擎是由Google开发的,这个引擎在最新的版本中引入了一个名为impller的新渲染引擎,这个引擎与现有的skia渲染引擎相比,在性能、可扩展性和灵活性方面都有所提高。下面就深入探讨一下这两个渲染引擎之间的区别。

  1. 性能

在性能方面,impller渲染引擎有着更高的性能表现。实际测试结果表明,在绘制图案或动画时,impller引擎的性能可以达到skia引擎的两倍。这是因为impller引擎使用了一种名为“记录再现”的技术,该技术可以根据之前绘制过的结果来预测下一步要绘制的内容,从而提高渲染速度。此外,impller引擎还使用了一些智能技术,比如动态剪切和分层技术,这些技术可以让Flutter应用在不同设备上执行更加流畅和快速。

  1. 可扩展性

在可扩展性方面,impller渲染引擎可以更好地应对不同的场景。传统的skia引擎采用单线程模型,限制了它的可扩展性和可用性。而impller引擎采用多线程模型,可以更好地利用现代硬件的多核处理能力,从而实现更高的性能和更好的可扩展性。

此外,impller引擎还可以实现动态代理,这意味着可以根据具体的场景选择合适的渲染器。例如,可以使用CPU渲染器来处理图形,也可以使用GPU渲染器来绘制3D图像。

  1. 灵活性

在灵活性方面,impller渲染引擎可以更加灵活地应对不同的情况。比如,在处理不同的字体文件时,impller引擎可以更好地适应各种不同的字体格式,从而实现更好的字体渲染效果。

此外,impller引擎还可以更加智能地处理不同的场景和事件。比如,在处理鼠标点击等事件时,impller引擎可以根据具体情况进行处理,从而实现更加灵活的交互效果。

总结

综上所述,impller渲染引擎与skia渲染引擎相比,在性能、可扩展性和灵活性方面都有所提高。特别是,在性能方面,impller引擎的性能表现要好于skia引擎。此外,impller引擎还增加了一些新的功能,如动态代理和记录再现技术,这些功能可以让Flutter应用程序在不同设备上执行得更加流畅和快速。考虑到这些因素,可以预计未来,impller引擎将会更加普遍和流行。

分层

Flutter 框架自下而上分为 Embedder、Engine 和 Framework 三层。

  • Embedder 是操作系统适配层,实现了渲染 Surface 设置,线程设置,以及平台插件等平台相关特性的适配;
  • Engine 层负责图形绘制、文字排版和提供 Dart 运行时,Engine 层具有独立虚拟机,正是由于它的存在,Flutter 程序才能运行在不同的平台上,实现跨平台运行;
  • Framework 层则是使用 Dart 编写的一套基础视图库,包含了动画、图形绘制和手势识别等功能,是使用频率最高的一层。
    image.png|600

FrameWork 层和 Engine 层,以及它们的作用

  • Flutter的FrameWork层是用Drat编写的框架(SDK),它实现了一套基础库,包含Material(Android风格UI)和Cupertino(iOS风格)的UI界面,下面是通用的Widgets(组件),之后是一些动画、绘制、渲染、手势库等。这个纯 Dart实现的 SDK被封装为了一个叫作 dart:ui的 Dart库。我们在使用 Flutter写 App的时候,直接导入这个库即可使用组件等功能。
  • Flutter 的 Engine 层是 Skia 2D 的绘图引擎库,其前身是一个向量绘图软件,Chrome 和 Android 均采用 Skia 作为绘图引擎。Skia 提供了非常友好的 API,并且在图形转换、文字渲染、位图渲染方面都提供了友好、高效的表现。Skia 是跨平台的,所以可以被嵌入到 Flutter 的 iOS SDK 中,而不用去研究 iOS 闭源的 Core Graphics / Core Animation。Android 自带了 Skia,所以 Flutter Android SDK 要比 iOS SDK 小很多。

Context

BuildContext 是一个将上下文信息与特定的 Widget 相关联的对象,与该 Widget 一起构成了 Flutter Widget 树的一部分。

  • 每个 BuildContext 对象只从属于一个 Widget。这意味着每个 Widget 都有一个与之关联的 BuildContext 对象,用于表示该 Widget 在 Widget 树中的位置和上下文信息
  • 查找父级 Widget:通过 BuildContext,可以访问当前 Widget 在 Widget 树中的父级 Widget,并进一步访问父级 Widget 的属性和方法。查找子级 Widget
  • 访问主题(Theme):BuildContext 可以用于获取当前 Widget 所在的主题数据,例如颜色、字体样式等。

StatefulWidget

![[Flutter分享#Stateful widgets]]

![[Flutter分享#生命周期]]

Flutter 的线程管理模型

默认情况下,Flutter Engine 层会创建一个 Isolate,并且 Dart 代码默认就运行在这个主 Isolate 上。必要时可以使用 spawnUri 和 spawn 两种方式来创建新的 Isolate,在 Flutter 中,新创建的 Isolate 由 Flutter 进行统一的管理。
事实上,Flutter Engine 自己不创建和管理线程,Flutter Engine 线程的创建和管理是 Embeder 负责的,Embeder 指的是将引擎移植到平台的中间层代码,Flutter Engine 层的架构示意图如下图所示。
image.png|600
在 Flutter 的架构中,Embeder 提供四个 Task Runner,分别是 Platform Task Runner、UI Task Runner Thread、GPU Task Runner 和 IO Task Runner,每个 Task Runner 负责不同的任务,Flutter Engine 不在乎 Task Runner 运行在哪个线程,但是它需要线程在整个生命周期里面保持稳定。
任务运行器(Task Runner)

Flutter 是如何与原生 Android、iOS 进行通信的?
Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:
BasicMessageChannel :用于传递字符串和半结构化的信息。
MethodChannel :用于传递方法调用(method invocation)。
EventChannel : 用于数据流(event streams)的通信。

Flutter 的热重载

Flutter 的热重载是基于 JIT 编译模式的代码增量同步。由于 JIT 属于动态编译,能够将 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。

热重载的流程可以分为 5 步,包括:扫描工程改动、增量编译、推送更新、代码合并、Widget 重建。Flutter 在接收到代码变更后,并不会让 App 重新启动执行,而只会触发 Widget 树的重新绘制,因此可以保持改动前的状态,大大缩短了从代码修改到看到修改产生的变化之间所需要的时间。

另一方面,由于涉及到状态的保存与恢复,涉及状态兼容与状态初始化的场景,热重载是无法支持的,如改动前后 Widget 状态无法兼容、全局变量与静态属性的更改、main 方法里的更改、initState 方法里的更改、枚举和泛型的更改等。

可以发现,热重载提高了调试 UI 的效率,非常适合写界面样式这样需要反复查看修改效果的场景。但由于其状态保存的机制所限,热重载本身也有一些无法支持的边界。

Flutter的绘制流程

在Flutter中,绘制流程主要涉及到以下几个步骤:

  1. 构建Widget树:Flutter应用程序通过构建一个由Widget组成的树来描述用户界面。Widget树是通过将各种Widget组合在一起来构建的,包括容器、文本、图像等。Flutter使用Widget树来表示应用程序的当前状态和UI结构。

  2. 测量和布局:在构建Widget树后,Flutter会执行测量和布局操作。这一步骤确定每个Widget的大小和位置。Flutter使用约束(constraints)来指定每个Widget的大小,并使用布局算法来确定它们在屏幕上的位置。

  3. 绘制:一旦完成测量和布局,Flutter就会进行绘制操作。绘制过程涉及将Widget转换为绘制命令,并将其发送到Flutter引擎进行渲染。Flutter引擎将使用Skia图形库将绘制命令转换为实际的像素,并在屏幕上显示。

  4. 更新:当应用程序状态发生变化时,Flutter会重复执行前面的步骤来更新界面。当状态改变时,Flutter会重新构建Widget树,并执行测量、布局和绘制操作,以反映新的状态和UI结构。

值得注意的是,Flutter的绘制流程是高效的,因为它使用了基于GPU的渲染技术,并支持部分更新。这意味着只有发生变化的部分会被重新绘制,而不是整个界面。

总结起来,Flutter的绘制流程包括构建Widget树、测量和布局、绘制以及更新。这些步骤协同工作,使得Flutter能够实现高性能、灵活和响应式的用户界面。

setState 是部分绘制还是全部绘制

在Flutter中,setState方法通常会触发部分绘制而不是全部绘制。

当调用setState方法时,Flutter会标记当前的Widget为”dirty”,表示该Widget需要重新绘制。然后,Flutter会在下一帧(即下一个绘制周期)中执行重绘操作。

在重绘过程中,Flutter会使用 diffing 算法来比较前后两次状态的差异,并只重新绘制发生变化的部分。这意味着,只有受到setState调用影响的部分会被重新绘制,而不是整个界面。

Stream

说一下 Dart 异步编程中的 Stream 数据流?
在 Dart 中,Stream 和 Future 一样,都是用来处理异步编程的工具。它们的区别在于,Stream 可以接收多个异步结果,而 Future 只有一个
Stream 的创建可以使用 Stream. fromFuture,也可以使用 StreamController 来创建和控制。还有一个注意点是:普通的 Stream 只可以有一个订阅者,如果想要多订阅的话,要使用 asBroadcastStream ()。

Stream 有哪两种订阅模式?分别是怎么调用的?
Stream 有两种订阅模式:单订阅 (single) 和多订阅(broadcast)。单订阅就是只能有一个订阅者,而广播是可以有多个订阅者。这就有点类似于消息服务(Message Service)的处理模式。
单订阅类似于点对点,在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。而广播类似于发布订阅模式,可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。

Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。但 Stream 可以通过 transform () 方法(返回另一个 Stream)进行连续调用。通过 Stream. asBroadcastStream () 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

await for 如何使用?
await for 是不断获取 stream 流中的数据,然后执行循环体中的操作。它一般用在直到 stream 什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞。

1
2
3
4
5
6
 Stream<String> stream = new Stream<String>.fromIterable (['不开心', '面试', '没', '过']);
main () async{
await for (String s in stream){
print (s);
}
}

用法

以下是使用 Flutter 中的 Stream 的基本步骤:

创建一个 StreamController:StreamController 是 Stream 的管理者,它负责生成和发送事件。你可以使用 StreamController 来创建一个 Stream,并在需要时向该 Stream 发送事件。

1
StreamController<String> _streamController = StreamController<String>();

将事件添加到 Stream 中:使用 StreamController 的 add 方法将事件添加到 Stream 中。

1
2
_streamController.add ('Event 1');
_streamController.add ('Event 2');

监听 Stream 中的事件:使用 Stream 的 listen 方法来监听 Stream 中的事件。当有新的事件到达时,将执行传递给 listen 方法的回调函数。

1
2
3
_streamController.stream.listen ((event) {
print ('Received event: $event');
});

关闭 StreamController:在不再需要使用 Stream 时,记得关闭 StreamController 以释放资源。

在Flutter中,你可以通过多次调用listen方法来实现多个订阅。每次调用listen方法时,都会创建一个新的订阅。在控制台中,你会看到两个订阅分别收到了相同的事件,并打印出相应的消息。

注意,每个订阅都是独立的,它们之间没有依赖关系。因此,它们可以独立处理事件,执行不同的操作。


flutter基础知识
http://peiniwan.github.io/2024/04/48f220a0587e.html
作者
六月的雨
发布于
2024年4月6日
许可协议