Flutter 中 Widget 的生命周期

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Dart的一些重要概念?

  • 面向对象:在Dart中,一切都是对象,所有的对象都是继承自Object
  • Dart是强类型语言,但可以用vardynamic来声明一个变量,Dart会自动推断其数据类型,var 实际上是编译期的“语法糖”。dynamic 表示动态类型, 被编译后,实际是一个 object 类型,在编译期间不进行任何的类型检查,而是在运行期进行类型检查。
  • 空类型安全,使用类型后面+定义可空类型,一个变量只要定义时是不可空的,那么在运行期就一定能保证是非空的,由编译器保证
  • Dart支持顶层方法,如main方法,可以在方法内部创建方法
  • Dart支持顶层变量,也支持类变量或对象变量
  • Dart没有 public protected private 等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的

Dart 是值传递还是引用传递?

  • Dart是面向对象的编程语言,传递效果和Java一样,dart是值传递我们每次调用函数,传递过去的都是对象的内存地址,而不是这个对象的复制
  • 首先分清楚Java中的值传递和引用传递,对于基本数据类型,实参传给形参的是变量的副本,互不干扰,对于非基本类型的引用对象,作为实参传给形参是传递的引用对象的地址值的副本,此时实参和形参的地址值指向同一个引用对象的堆内存地址,因此形参修改了指向的引用对象的内容,也就是修改了实参所指向的对象内容。Dart一切都是对象,因此Dart也是传递的对象的地址值。

Widget 和 element 和 RenderObject 之间的关系?

  • Widget 是用户界面的一部分,并且是不可变的。
  • Element 是在树中特定位置 Widget 的实例
  • RenderObject 是渲染树中的一个对象,它的层次结构是渲染库的核心。

它们对应 3 棵树,Widget 和 Element 是一一对应,Element 和 RenderObject 不是一一对应。

Widget 会被 inflate(填充)到 Element,并由 Element 管理底层渲染树。Widget 并不会直接管理状态及渲染,而是通过 State 这个对象来管理状态。Flutter 创建 Element 的可见树,相对于 Widget 来说,是可变的。通常在界面开发中,我们不直接操作 Element,而是由框架层实现内部逻辑。例如,在一个 UI 视图树中,可能包含多个 TextWidgetWidget 被使用多次),但是从内部视图树的视角看,这些 TextWidget 都被填充到各自独立的 Element 中。Element 会持有 renderObjectwidget 的实例。请记住,Widget 只是一个配置,而 RenderObject 负责管理布局、绘制等操作。

在第一次创建 Widget 时,会相应地创建一个 Element,然后将该元素插入树中。如果之后 Widget 发生变化,系统会将其与旧的 Widget 进行比较,并相应地更新 Element。重要的是,Element 不会被重建,只是更新其状态。

Flutter 中的 Widget、State、Context 的核心概念?是为了解决什么问题?

  • Widget: 在Flutter中,几乎所有东西都是Widget。将一个Widget想象为一个可视化的组件(或与应用可视化方面交互的组件),当你需要构建与布局直接或间接相关的任何内容时,你正在使用Widget
  • Widget树: Widget 以树结构进行组织。包含其他 Widget 的 widget 被称为父 Widget (或 widget 容器)。包含在父 widget 中的 widget 被称为子 Widget。
  • Context: 在StatefulWidgetStatelessWidget中会分别创建对应的StatefulElementStatelessElement,在这两个Element中会调用Widget build() => state.build(this);Widget build() => widget.build(this); 方法,因此widget的build方法中的 Context 参数其实是对应的Element对象本身,它是Context的实现类
  • State: 定义了StatefulWidget实例的行为,它包含了用于”交互/干预“Widget信息的行为和布局。应用于State的任何更改都会强制重建Widget

这些状态的引入,主要是为了解决多个部件之间的交互和部件自身状态的维护。

一个Widget内部会创建一个Element对象,Element对象本身又会作为Context参数传给Widgetbuild函数,因此WidgetElementContext之间是一一对应的关系。

mixin extends implement 之间的关系?

将类 A mixins 到 B,B 可以使用 A 的属性和方法,B 就具备了 A 的功能,但是需要强调的是:

  1. mixins 的对象是类
  2. mixins 绝不是继承,也不是接口,而是一种全新的特性
  3. 可以mixins多个类
  4. mixins的使用需要满足一定条件

mixins 要用到的关键字 with

怎么来理解 with 呢?很简单:

  • 继承 -> extends
  • mixins -> with

继承和 mixins 是一样的,是语言的特性,withextends 是关键字。

继承(关键字 extends)、混入 mixins (关键字 with)、接口实现(关键字 implements)。这三者可以同时存在,前后顺序是 extends -> mixins -> implements

Flutter 中的继承是单继承,子类重写超类的方法要用@Override,子类调用超类的方法要用super

mixin类似多继承但又不同于多继承,Dart自创大法,以多继承的方式使用多个类中的重复代码。

使用mixins的条件是什么?

因为mixins使用的条件,随着Dart版本一直在变,这里讲的是Dart2.1中使用mixins的条件:

  • mixins 类只能继承自 object
  • mixins 类不能有构造函数
  • 一个类可以 mixins 多个 mixins 类,不破坏 Flutter 的单继承

mixin 怎么指定异常类型?

on关键字可用于指定异常类型。 on只能用于被mixins标记的类,例如mixins X on A,意思是要mixins X的话,得先接口实现或者继承A。这里A可以是类,也可以是接口,但是在mixins的时候用法有区别。

on 一个类:

class A {
  void a(){
    print("a");
  }
}

mixin X on A {
  void x(){
    print("x");
  }
}

class mixinsX extends A with X {
}

on 的是一个接口: 得首先实现这个接口,然后再用mixin

class A {
  void a(){
    print("a");
  }
}

mixin X on A {
  void x(){
    print("x");
  }
}
class implA implements A {
  
  void a() {}
}
class mixinsX2 extends implA with X {
}

接口实现 (implements)

Flutter是没有interface的,但是Flutter中的每个类都是一个隐式的接口,这个接口包含类里的所有成员变量,以及定义的方法。

如果有一个类 A,你想让类 B 拥有 A 的API,但又不想拥有 A 里的实现,那么你就应该把 A 当做接口,类B implements 类 A

所以在Flutter中:

  1. class 就是 interface
  2. class被当做interface用时, class中的方法就是接口的方法,需要在子类里重新实现,在子类实现的时候要加 @override
  3. class被当做interface用时, class中的成员变量也需要在子类里重新实现。在成员变量前加 @override
  4. 实现接口可以有多个

感觉 Dart 中的接口实现就是拿骡子当马用。。

Flutter main future mirotask 的执行顺序?

普通代码都是同步执行的,结束后会开始检查microtask中是否有任务,若有则执行,执行完继续检查microtask,直到microtask列队为空。最后会去执行event队列(future)。

Future 和 Isolate 有什么区别?

future 是异步编程,调用本身立即返回,并在稍后的某个时候执行完成时再获得返回结果。在普通代码中可以使用 await 等待一个异步调用结束。

isolate是并发编程,Dart没有并发时的共享状态,所有Dart代码都在isolate中运行,包括最初的main()。每个isolate都有它自己的堆内存,意味着其中所有内存数据,包括全局数据,都仅对该isolate可见,它们之间的通信只能通过传递消息的机制完成,消息则通过端口(port)收发。isolate只是一个概念,具体取决于如何实现,比如在 Dart VM 中一个 isolate 可能会是一个线程,在Web中可能会是一个Web Worker。

Stream 与 Future是什么关系?

Stream 和 Future 是 Dart 异步处理的核心 API。

Future 表示稍后获得的一个数据,所有异步的操作的返回值都用 Future 来表示。但是 Future 只能表示一次异步获得的数据。

而 Stream 表示多次异步获得的数据。比如界面上的按钮可能会被用户点击多次,所以按钮上的点击事件(onClick)就是一个 Stream 。

简单地说,Future 将返回一个值,而 Stream 将返回多次值。Dart 中统一使用 Stream 处理异步事件流。Stream 和一般的集合类似,都是一组数据,只不过一个是异步推送,一个是同步拉取。

Stream 两种订阅模式?

Stream有两种订阅模式:单订阅(single)多订阅(broadcast)

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

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

Widget的两种类型是什么?

  • StatelessWidget: 一旦创建就不关心任何变化,在下次构建之前都不会改变。它们除了依赖于自身的配置信息(在父节点构建时提供)外不再依赖于任何其他信息。比如典型的Text、Row、Column、Container等,都是StatelessWidget。它的生命周期相当简单:初始化、通过build()渲染
  • StatefulWidget: 在生命周期内,该类Widget所持有的数据可能会发生变化,这样的数据被称为State,这些拥有动态内部数据的Widget被称为StatefulWidget。比如复选框、Button等。State会与Context相关联,并且此关联是永久性的,State对象将永远不会改变其Context,即使可以在树结构周围移动,也仍将与该context相关联。当statecontext关联时,state被视为已挂载。StatefulWidget由两部分组成,在初始化时必须要在createState()时初始化一个与之相关的State对象。

State 对象的初始化流程?

  • initState() : 一旦State对象被创建,initState方法是第一个(构造函数之后)被调用的方法。可通过重写来执行额外的初始化,如初始化动画、控制器等。重写该方法时,应该首先调用super.initState()。在initState中,无法真正使用context,因为框架还没有完全将其与state关联。initState在该State对象的生命周期内将不会再次调用。
  • didChangeDependencies(): 这是第二个被调用的方法。在这一阶段,context已经可用。如果你的Widget链接到了一个InheritedWidget并且/或者你需要初始化一些listeners(基于context),通常会重写该方法。
  • build(BuildContext context): 此方法在didChangeDependencies()didUpdateWidget()之后被调用。每次State对象更新(或当InheritedWidget有新的通知时)都会调用该方法!我们一般都在build中来编写真正的功能代码。为了强制重建,可以在需要的时候调用setState((){...})方法。
  • dispose(): 此方法在Widget被移除时调用。可重写该方法来执行一些清理操作(如解除listeners),并在此之后立即调用super.dispose()

Widget 唯一标识Key有那几种?

在flutter中,每个widget都是被唯一标识的。这个唯一标识在buildrendering阶段由框架定义。该标识对应于可选的Key参数,如果省略,Flutter将会自动生成一个。

在flutter中,主要有两种类型的Key:

  • GlobalKey(确保生成的Key在整个应用中唯一,是很昂贵的,允许element在树周围移动或变更父节点而不会丢失状态)
  • LocalKey,LocalKey 包括:ValueKey、UniqueKey、ObjectKey

什么是Navigator? MaterialApp做了什么?

Navigator 是在 Flutter 中负责管理维护页面堆栈的导航器。MaterialApp 在需要的时候,会自动为我们创建Navigator。Navigator.of(context),会使用context来向上遍历Element树,找到 MaterialApp 提供的_NavigatorState再调用其push/pop方法完成导航操作。

widget 树的 root 节点

  • Flutter 应用的根组件是一个 RenderView,它的默认大小是当前设备屏幕大小。 renderView是一个 RenderObject,它是渲染树的根,而renderViewElementrenderView对应的Element对象。

main() --> runApp -->

WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();

其中 WidgetsFlutterBinding 混入了 RenderBinding -> initInstances() 初始化--> initRenderView() 创建 RenderView作为整个渲染树的根节点,传入的app随后被挂载的RenderView上面,当下一帧vsync信号来临时开始渲染。

dart 中 … 是什么

级联符号 .. 可以让你连续操作相同的对象,不单可以连续地调用函数,还可以连续地访问方法,这样做可以避免创建临时变量,从而写出更流畅的代码。

Zone

Dart 中可通过 Zone 表示指定代码执行的环境,类似一个沙盒概念,在 Flutter 中 C++ 运行 Dart 也是在 _runMainZoned 内执行 runZoned 方法启动,而我们也可以通过 Zone ,在运行环境内捕获全局异常等信息:

  runZoned(() {
    runApp(FlutterReduxApp());
  }, onError: (Object obj, StackTrace stack) {
    print(obj);
    print(stack);
  });

同时你可以给 runZoned 注册方法,在需要时执行回调,如下代码所示,这样的在一个 Zone 内任何地方,只要能获取 onData 这个 ZoneUnaryCallback,就都可以调用到 handleData

///最终需要处理的地方
handleData(result) {
  print("VVVVVVVVVVVVVVVVVVVVVVVVVVV");
  print(result);
}
///返回得到一个 ZoneUnaryCallback
var onData = Zone.current.registerUnaryCallback<dynamic, int>(handleData);
///执行 ZoneUnaryCallback 返回数据
Zone.current.runUnary(onData, 2);
// 异步逻辑可以通过 scheduleMicrotask 可以插入异步执行方法:
Zone.current.scheduleMicrotask((){
  //todo something
});

Future

Future 简单了说就是对 Zone 的封装使用。

比如 Future.microtask 中主要是执行了 ZonescheduleMicrotask ,而 result._complete 最后调用的是 _zone.runUnary 等等。

factory Future.microtask(FutureOr<T> computation()) {
    _Future<T> result = new _Future<T>();
    scheduleMicrotask(() {
      try {
        result._complete(computation());
      } catch (e, s) {
        _completeWithErrorCallback(result, e, s);
      }
    });
    return result;
  }

Dart 中可通过 async/await 或者 Future 定义异步操作,而事实上 async/await 也只是语法糖,最终还是通过编译器转为 Future

Stream

Stream 也是有对 Zone 的另外一种封装使用。

Dart 中另外一种异步操作, async* / yield 或者 Stream 可定义 Stream 异步, async* / yield 也只是语法糖,最终还是通过编译器转为 StreamStream 还支持同步操作。

1)、Stream 中主要有 StreamStreamControllerStreamSinkStreamSubscription 四个关键对象,大致可以总结为:

  • StreamController :如类名描述,用于整个 Stream 过程的控制,提供各类接口用于创建各种事件流。
  • StreamSink :一般作为事件的入口,提供如 addaddStream 等。
  • Stream :事件源本身,一般可用于监听事件或者对事件进行转换,如 listenwhere
  • StreamSubscription :事件订阅后的对象,表面上用于管理订阅过等各类操作,如 cancelpause ,同时在内部也是事件的中转关键。

2)、一般通过 StreamController 创建 Stream;通过 StreamSink 添加事件;通过 Stream 监听事件;通过 StreamSubscription 管理订阅。

3)、Stream 中支持各种变化,比如 map 、expand 、where 、take 等操作,同时支持转换为 Future

Dart 单线程模型

在这里插入图片描述

Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。从图中可以发现,微任务队列的执行优先级高于事件队列。

现在我们来介绍一下Dart线程运行过程,如上图中所示,入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,事件任务执行完毕后程序便会退出,但是,在事件任务执行的过程中也可以插入新的微任务事件任务,在这种情况下,整个线程的执行过程便是一直在循环,不会退出,而 Flutter 中,主线程的执行过程正是如此,永不终止

在Dart中,所有的外部事件任务都在事件队列中,如 IO、计时器、点击、以及绘制事件等,而微任务通常来源于Dart内部,并且微任务非常少,之所以如此,是因为微任务队列优先级高,如果微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久,对于GUI应用来说最直观的表现就是比较卡,所以必须得保证微任务队列不会太长。

值得注意的是,我们可以通过 Future.microtask(…) 方法向微任务队列插入一个任务。

微任务队列有点类似于 Android 中 Choreographer 提交绘制任务之前发送的同步屏障消息(阻塞普通消息,优先执行异步消息),它们都是为系统的某些UI任务执行提供绿色通道,提高更高优先级的执行。

在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其他任务执行的。

Dart 多任务如何并行的?

刚才也说了,既然 Dart 不存在多线程,那如何进行多任务并行?

  • Dart 当中提供了一个 类似于新线程,但是不共享内存的独立运行的 worker - isolate

那他们是如何交互的?

  • 在dart中,一个isolate对象其实就是一个isolate执行环境的引用,一般来说我们都是通过当前的isolate去控制其他的isolate完成彼此之间的交互,而当我们想要创建一个新的Isolate可以使用Isolate.spawn方法获取返回的一个新的isolate对象,两个isolate之间使用SendPort相互发送消息,而isolate中也存在了一个与之对应的ReceivePort接受消息用来处理,但是我们需要注意的是,ReceivePortSendPort在每个isolate都有一对,只有同一个isolate中的ReceivePort才能接受到当前类的SendPort发送的消息并且处理。

Flutter与Native的通信机制

  • 应用中的 Flutter 部分通过 Platform Channel 向其宿主 (非 Dart 部分) 发送消息。
  • 宿主监听 Platform Channel 并接收消息。然后,它使用原生编程语言来调用任意数量的相关平台 API,并将响应发送回 Flutter

Flutter 端在调用方法的时候 MethodChannel 会负责响应,从平台一侧来讲,Android 系统上使用 MethodChannelAndroidiOS 系统使用 MethodChanneliOS 来接收和返回来自 MethodChannel 的方法调用。

Platform Channel 的三种分类:

在这里插入图片描述

这三种方式,无论是传递方法还是事件,本质上都是传递的数据。

具体使用哪种通信方式,要看使用场景,同一种功能可以通过不同的方式来实现。比如获取手机电量,网络变化等可以通过 EventChannel,也能用 MethodChannel。比如Flutter调用Native拍照可以用MethodChannel。 如果要通过Flutter控制一个原生的视频播放组件,则最好通过MethodChannel;如果要视频播放期间获取播放进度改变、当前网速变化等信息,则最好通过EventChannel

同时 Platform Channel 并非是线程安全的 。

在 Flutter 中嵌入原生 Android View

Platform view 允许将原生视图嵌入到 Flutter 应用中,所以您可以通过 Dart 将变换、裁剪和不透明度等效果应用到原生视图。

Flutter 支持两种集成模式:虚拟显示模式 (Virtual displays) 和混合集成模式 (Hybrid composition) 。

我们应根据具体情况来决定使用哪种模式:

  • 混合集成模式:会将原生的 android.view.View 附加到视图层次结构中。因此,键盘处理和无障碍功能是开箱即用的。在 Android 10 之前,此模式可能会大大降低 Flutter UI 的帧吞吐量 (FPS)。 minSdkVersion 最低为 19

  • 虚拟显示模式 会将 android.view.View 实例渲染为纹理,因此它不会嵌入到 Android Activity 的视图层次结构中。某些平台交互(例如键盘处理和辅助功能)可能无法正常工作。minSdkVersion 最低为 20

Flutter 中通过 PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了 Presentation + VirtualDisplay + Surface 等实现的,大致原理就是:

  • 使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManagercreateVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surfaceid 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来。

Flutter 和 React Native 区别

Flutter 和 React Native 不同主要在于 Flutter UI 是直接通过 skia 渲染的 ,而 React Native 是将 js 中的控件转化为原生控件,通过原生去渲染的。

一些基础概念

  • Flutter 中 Widget 不可变,每次保持在一帧,如果发生改变是通过 State 实现跨帧状态保存,而真实完成布局和绘制数组的是 RenderObjectElement 充当两者的桥梁State 就是保存在 Element 中
  • Flutter 中的 BuildContext 只是接口,而 Element 实现了它
  • Flutter 中 setState 其实是调用了 markNeedsBuild ,该方法内部标记此 Element 为 Dirty ,然后在下一帧 WidgetsBinding.drawFrame 才会被绘制,这可以看出 setState 并不是立即生效的
  • Flutter 中 RenderObjectattch/layout 之后会通过 markNeedsPaint(); 使得页面重绘。
  • RenderObjectisRepaintBoundarytrue 时,那么个区域形成一个 Layer,所以不是每个 RenderObject 都具有 Layer 的,因为这受 isRepaintBoundary 的影响。
  • 正常情况 RenderObject 的布局相关方法调用顺序是 : layout ->performResize ->performLayout ->markNeedsPaint , 但是用户一般不会直接调用 layout,而是通过 markNeedsLayout
  • Flutter 中一般 json 数据从 String 转为 Object 的过程中都需要先经过 Map 类型。
  • Flutter 中 InheritedWidget 一般用于状态共享,如Theme 、Localizations 、 MediaQuery 等,都是通过它实现共享状态,这样我们可以通过 context 去获取共享的状态,比如 ThemeData theme = Theme.of(context);
  • ElementinheritFromWidgetOfExactType 方法实现里,有一个 Map<Type, InheritedElement> _inheritedWidgets 的对象。_inheritedWidgets 一般情况下是空的,只有当父控件是 InheritedWidget 或者本身是 InheritedWidgets 时才会有被初始化,而当父控件是 InheritedWidget 时,这个 Map 会被一级一级往下传递与合并 。所以当我们通过 context 调用inheritFromWidgetOfExactType 时,就可以往上查找到父控件的 Widget

Flutter 中默认主要通过 runtimeTypekey 判断更新

static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

Flutter 的 Debug 下是 JIT 模式,release下是 AOT 模式。

  • 在应用程序开发过程中使用JIT编译,因为它可以通过动态编译代码来实现热重新加载和快速开发周期。
  • 完成开发并准备发布后,将使用AOT编译。然后将代码AOT编译为本机代码,从而实现应用程序的快速启动和高性能执行。Dart 是极少数可以同时编译 AOTJIT 的语言之一。

Flutter 中可以通过 mixins AutomaticKeepAliveClientMixin ,然后重写 wantKeepAlive 保持住页面,记得在被保持住的页面 build 中调用 super.build 。(因为 mixins 特性)。

Flutter 手势事件主要是通过竞技判断的:

  • 主要有 hitTest 把所有需要处理的控件对应的 RenderObject , 从 childparent 全部组合成列表,从最里面一直添加到最外层。
  • 然后从队列头的 child 开始 for 循环执行 handleEvent 方法,执行 handleEvent 的过程不会被拦截打断。
  • 一般情况下 Down 事件不会决出胜利者,大部分时候是在 MOVE 或者 UP 的时候才会决出胜利者。
  • 竞技场关闭时只有一个的就直接胜出响应,没有胜利者就拿排在队列第一个强制胜利响应。
  • 同时还有 didExceedDeadline 处理按住时的 Down 事件额外处理,同时手势处理一般在 GestureRecognizer 的子类进行。

Flutter 中 ListView 滑动其实都是通过改变 ViewPort 中的 child 布局来实现显示的。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐