1.Dart
1.1 简述Dart语言特性
Dart是面向对象语言,所有对象继承Object,Dart属于强类型语言。
1.2 Dart中var与dynamic,Object的区别
var来声明变量,Dart会自动推导出数据类型,dynamic表示动态类型,类似TS中的any,而dynamic不在编译期间做类型检查而是在运行期间做类型校验。Object类是Dart中所有类的基类,包括int、String、List和null等,Object通用类型可以存储任意类型,适合泛型等应用场景。
1.3 Dart是值传递还是引用传递?
在 Dart 中,除了可空类型(Nullable types)和基本数据类型(如 int、double、bool 等)是按值传递外,其他都是按引用传递。
1.4const与final的区别
在Dart中,const和final都可以用来声明常量,但它们有本质的区别: const:编译常量,必须在声明时初始化,且其值在编译时已知。使用const修饰的变量在编译时就确定了其值,因此可以优化性能。 final:运行时常量,可以在运行时赋值,但只能赋值一次。使用final修饰的变量可以在运行时确定其值,但其优化效果不如const。
1.5 Dart 当中的 「..」表示什么意思?
Dart 当中的 「..」意思是 「级联操作符」,为了方便配置而使用。
「..」和「.」不同的是 调用「..」后返回的相当于是 this,而「.」返回的则是该方法返回的值 。
1.6 Dart中??与??=的区别
两者都是dart中的操作符,??表示如果为空则返回,??=表示如果为空则赋值。
1.7 Dart assert(data!=null,'no data')是什么意思?
assert 是 Dart 语言中用于调试和验证代码的重要工具,主要用于在开发阶段检查某个条件是否为真。如果条件为假,assert 会抛出一个 Asserti 异常,帮助开发者快速识别和修复问题。
1.8 说一下 mixin
为了实现多继承,引入混入的概念,它没有构造方法,可以避免多继承而产生的父类构造方法冲突。多个混入用with连接。
1.9 Dart 是不是单线程模型?是如何运行的
Dart 是单线程模型,以消息循环机制来运行,包含两个任务队列,一个是“微任务队列”microtask queue 另一个是“事件队列”event queue”,消息机制启动,按照先进先出的顺序执行微任务队列的任务,当所有微任务队列中的任务都执行完了后,再执行事件队列中的任务,执行完了之后再执行微任务队列,循环往复。
1.10 Flutter main future mirotask 的执行顺序?
普通代码都是同步执行的,结束后会开始检查microtask中是否有任务,若有则执行,执行完继续检查microtask,直到microtask列队为空。最后会去执行event队列(future)。
1.11 怎么开启异步编程。
把任务放进微任务队列或者事件队列。
可以调用scheduleMicrotask来让代码以微任务的方式异步执行
scheduleMicrotask((){
print('a microtask');
});
可以调用Timer.run来让代码以Event loop的方式异步执行
Timer.run((){
print('a event loop’);
});
1.12 说一下Future?
Future 类似JS中的Promise,Future其实就是往事件队列中插入任务,
1.13 flutter里的async 和 await?
async/await 只是 Future 的语法糖**,它不会改变 Future 本身的行为,而是让异步代码像同步代码一样编写。本质是 状态机 + Future.then(), await 不会阻塞 UI 线程
1.14 Future和Isolate有什么区别
future是异步编程,调用本身立即返回,iso是并发编程,类似iOS中的多线程,多线程通信分为共享数据通信,和消息发送传递,Flutter和鸿蒙中多线程中按照消息发送传递,不存在共享数据的问题,也就不需要用锁,不存在死锁的问题。
1.15 Stream
Stream有两种订阅模式:单订阅(single)和多订阅(broadcast类似广播发布订阅模式),Stream默认为单订阅模式,同一个stream上的listen只能调用一次,但Stream可以通过transform()方法(返回另一个Stream)进行连续调用,通过Stream.asBroadcastStream()可以将一个单订阅模式转为一个多订阅模式的Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。
1.16 Future 和 steam 使用区别
Stream和Future一样,都是用来处理异步的工具, Future表示稍后获取一个数据,所有的异步操作的返回值都用Future来表示。Future只能表示一次异步获得的数据。
Stream表示异步获得多次的数据,表示一系列的异步数据流,比如界面上的按钮可能会被多次点击,onClick就是一个Stream。
1.17 await for 如何使用?
await for是不断获取stream流中的数据,然后执行循环体中的操作。它一般用在直到stream什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞。
1.18 什么是event loop,它和isolate的关系是什么?
Dart代码运行在一个独立的isolate线程上,每一个线程都有一个event loop,用于管理异步任务的运行,这些任务来自微任务和事件队列任务。
1.19 Flutter线程管理模型
Flutter 存在四大线程: UI Runner、GPU Runner、IO Runner、Platform Runner(原生主线程)。在Flutter中可以通过isolate或者compute执行真正的跨线程异步操作。
1.20 怎么理解Isolate?
isolate是Dart对actor并发模式的实现,isolate是有自己的内存和单线程控制的运行实体。isolate本身是“隔离”的意思,isolate间是内存隔离的,Dart没有共享内存的并发,所以也不需要锁,不用担心死锁的问题。
1.Flutter
2.1 Flutter是什么?它与其他移动开发框架有什么不同?
Flutter是谷歌的移动UI框架,采用Dart语言开发,支持跨平台操作,采用组件化开发,响应式的框架自动更新UI,可以在不重新编译的情况下实时查看代码更改的效果,提高代码开发效率和代码质量。Flutter直接调用Skia绘制UI,无需原生桥接,相比React Native采用桥接机制调用原生组件,依赖平台自身的UI组件库,性能上略胜一筹,React Native使用JS和TS开发。
2.2 Flutter中的Widget使用const注解的主要原因是为了提高性能和减少重复构建。
当Widget被标记为const时,Flutter会构建一个常量实例,这个实例在应用的生命周期内不会被重新构建,从而避免了频繁的构建和销毁操作,减少了性能开销。
2.3.Flutter的FrameWork和Engine层,以及它们的作用
Flutter的FrameWork层是Dart编写的框架(SDK),实现了一套基础库,包含Material和Cupertino的UI界面。 Flutter的Engine层是Skia 2D的绘图引擎库,Skia是跨平台的。
2.4.main() 和 runApp()函数在flutter的作用分别是什么?有什么关系吗?
main函数是入口函数,runApp函数是渲染根widget树的函数。runApp函数在main函数里面执行。
2.5.介绍下Widget、State、Context概念
Widget:在Flutter中,一切皆组件,widget分为三类
组合类ComponentWidget:如Container、Scaffold、MaterialAPP、StatelessWidget、StatefulWidget。组合类是我们开发过程中用的最多的组件。
代理类组件ProxyWidget:InheritedWidget,功能型组件,它可以高效快捷的实现共享数据的跨组件传递。如常见的Theme、MediaQuery就是InheritedWidget的应用。
绘制类组件RenderObjectWidget:屏幕上看到的UI几乎都会通过RenderObjectWidget实现,可以进行界面的布局和绘制。如Align、Padding、ConstrainedBox等通过继承RenderObjectWidget,并重写createRenderObject方法来创建。
Context:context作为widget树的一部分,其中context所对应的widget被添加到此树中。一个context对应一个widget,会形成一个context树。
State:定义StatefulWidget实例的行为,state的修改都会强制重建Widget。
2.6.什么是BuildContext,它有什么用?
BuildContext是Element树中的Widget的元素,通常使用BuildContext来获取主题(theme)或者另一个Widget的引用,比如你想展示一个dialog,那么你需要获取scaffold的引用,可以通过Scaffold.of(context)来得到它,其中context就是上下文信息。
2.7.Widget、Element、RenderObject三者之间的关系
类似前端的三棵树,Widget对应前端的HTML,Element对应虚拟DOM,RenderObject对应真实DOM。
Flutter的Widgets的灵感来自React,用来构建界面UI。Widgets使用配置和状态。当Widget发生状态改变,Widget重新build它。
Element是一个Widget的实例,同时持有Widget和RenderObject,存放上下问信息,通过它来遍历视图树,支撑UI结构。
RenderObject(渲染树)用于应用界面的布局和绘制,负责真正的渲染,保存元素的大小、布局等信息,实例化一个RenderObject是非常耗时的。
2.8.Key有哪些Key
Key从整体上来说,分为两种,即:LocalKey,GlobalKey
-
LocalKey: 分为ValueKey,ObjectKey 和 UniqueKey。在当前Widget层级下,设置widget唯一属性。
- Valuekey:value可以是任意类型,用value表示Widget的key。
- ObjectKey:与ValueKey类似,ValueKey是value相等,key值相等,ObjectKey引用相同,那么key值相等。
- UniqueKey:是独一无二的,Build函数中,UniqueKey也会被重新创建。大部分情况下不会用到。
-
GlobalKey:在全局APP中,具有唯一性。需要在定义在Build函数之外,否则每次都会重新创建GlobalKey,另一个作用就是类似前端的Ref用于打标识,可以获取当前widget的相关信息。
2.9.Flutter的渲染流程
当应用启动Flutter会遍历并创建所有的widget形成WidgetTree,通过调用Widget上的createElement()方法创建每个Element对象,形成Element Tree。最后调用Element的CreateRenderObject()方法创建每个渲染的对象,形成一个Render Tree。
2.10 StatelessWidget 和 StatefulWidget的区别是什么?
StatelessWidget没有状态,是静态的组件,StatefulWidget有状态,当状态值,UI更新。
2.11 setState在那种场景下可能会失效?
setState只在StatefulWidget中有效,StatelessWidget不持有任何状态,仅作为静态组件,在Stateless中调用setState不仅无效,而且导致编译错误。 setState在已销毁掉的Widget中使用将失效,不能在widget构建过程中,导致框架行为不可预测,甚至引发循环重建的问题,
2.12 setState做了哪些工作?是如何更新UI的?
setState是Flutter中用于状态管理的核心方法,调用setState时,Flutter会标记当前State对象为 脏数据,表示该对象需要重新构建,在setState方法内部,对状态进行修改,这些修改不会立即触发UI的变化,而是等待setState方法执行完毕后,再通知框架状态已更改。框架在监测到状态变化后,会重新build。build方法返回一个新的Widget树,并与久的Widget树进行对比,这个过程称为diffing,目的是找到变化的部分,高效更新。避免不必要的重绘。
2.13 flutter 中StatefulWidget 的生命周期
大致分为四个阶段:
- 初始化createState和initState,
- 组件创建didChangeDependencies和build
- 触发build(因为didChangeDependencies、setState、或者didUpdateWidget而引发build)
- 销毁deactivate和dispose
initState():Widget初始化当前State,在当前方法中是不能获取到Context的,如果想获取,可以使用Future.delayed()
didChangeDependencies():在initState()后调用,State对象依赖关系发生变化的时候也会调用。widget树中,若节点的父级结构中的层级 或 父级结构中的任一节点的widget类型有变化,节点会调用didChangeDependencies;若仅仅是父级结构某一节点的widget的某些属性值变化,节点不会调用didChangeDependencies
deactivate():当State被暂时从视图树中移除时会调用这个方法,页面切换时也会调用该方法。
dispose():Widget销毁时调用
didUpdateWidget:Widget状态发生变化的时候调用。(widget树中,若节点调用setState方法,节点本身不会触发didUpdateWidget,此节点的子节点 会 调用didUpdateWidget)
2.14 局部刷新 StatefulBuilder & FutureBuilder & StreamBuilder
StatefulBuilder & FutureBuilder & StreamBuilder都是通过状态构建器来实现局部刷新的功能
//构建状态改变的Widget
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){})
2.15 什么是InheritedWidget及其在Flutter中的用途?
InheritedWidget和React中的context功能类似,可实现跨组件数据的传递,定义一个共享数据的InheritedWidget,需要继承InheritedWidget。 定义一个of方法,该方法通过context查找最近的InheritedWidget。updateShouldNotify方法是对比新旧InheritedWidget,是否需要对更新相关依赖的Widget。
inheritedWidget,context功能类似,可以实现跨组件数据的传递,祖孙节点通信,在祖先节点触发setState,刷新数据,在后代节点,也会刷新UI。
2.16 Flutter 状态管理
Provider是目前官方推荐的全局状态管理工具,类似Vue中VueX,React中的Redux。分为三个概念:
- ChangeNotifier:真正数据(状态)存放的地方,set方法中调用
- ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier
- Consumer:Widget树中需要使用数据(状态)的地方
Provider 是封装了InheritedWidget的状态管理工具。使用ChangeNotifier声明状态用于共享数据。ChangeNotifierProvider提供状态,Consumer 或Provider.of获取状态并更新UI。
ChangeNotifierProvider本身继承于ListenableProvider ,用于监听状态数据变化,当状态数据发生变化,调用ChangeNotifierProvider 的update的方法,ChangeNotifierProvider内部会重新构建InheritedWidget,而依赖该InheritedWidget的子孙Widget就会更新。
2.17 Flutter中的controller到底是什么
Controller是为了解决多个组件传值,在状态提升引发整个大的widget重绘的问题,controller继承Changenotifier,Changenotifier继承listenable。set方法中调用notifiyListeners,在需要更新UI的地方,设置ListenableBuilder监听listenable(也就是controller)的变化,重新绘制UI。
2.18 Flutter如何进行本地存储和缓存数据?
Flutter官方推荐sharedpreferences,类似于RN中的AsyncStorage。Android上它是基于SharedPreferences。iOS中基于SUserDefault。
shared_preferences异步持久化的key-value存储系统;支持int, double, bool, string 与 stringList类型的数据存储;
2.19 Flutter屏幕适配
前端开发中,针对不同的屏幕常见的适配方案有下面几种:
- rem:给根标签(HTML)设置一个字体大小
- vw、vh:将屏幕分成100等份,一个1vw相当于1%的大小。
- rpx 是小程序中的适配方案,将750px作为设计稿,1rpx=屏幕宽度/750,
- 媒体查询,查询屏幕的大小,
2.20 WidgetsApp和MaterialApp的区别什么?
WidgetsApp提供了基础的导航能力,和widgets库一起,包含了很多Flutter使用的基础widget。
MaterialAPP遵循Material设计风格,可以在任何平台或设备上为应用保持统一的风格,material库提供了更多的Widget。也可以使用CupertinoApp来构建iOS风格的应用程序。
2.21 Flutter中的路由(Route)是什么?如何在应用程序中实现路由导航?
Flutter路由管理有两个类:Route和Navigator。
- 基本路由导航用Navigator管理路由堆栈,实现页面的推送push和弹出pop,
- 命名路由通过在
MaterialApp中定义的路由表,使用Navigator.pushNamed跳转到动态页面DynamicPage,并通过参数传递消息。 - 动态路由通过
onGenerateRoute回调根据传递的参数动态创建DynamicPage页面。
2.22 可以嵌套使用Scaffold吗,为什么或者为什么不?
Scaffold也是个widget,因此你可以把它放在任何widget可以放置的地方。通过嵌套Scaffold,你可以对抽屉(drawers)、卡片(snack bars)、底页(bottom sheets)进行分层。
2.23 为什么我的容器无论设置宽高多少都是占整个的宽高?怎么解决?
原因: 第一:父组件的约束,在Flutter中,容器的尺寸受父组件的越苏影响。如果父组件是一个Expanded或者SizeBox.expand,子容器会占满可用空间。 第二:容器的属性。如果容器设置width和height,但父组件的约束更大,容器仍然会扩展到父组件的尺寸。
解决方法:
修改父组件。避免使用Expanded、SizeBox.expand,使用SizeBox、ConstrainedBox等设置明确尺寸的父组件。
使用Constraints。通过设置Constraints来限制容器尺寸,例如BoxConstraints
Alignment。确保父组件使用合适的Alignment,如Center或Align,使子容器在父组件内具有正确的大小和位置。
2.24 怎么减少Widget的重新构建?
当状态值发生改变时,将重新刷新UI,但是重新构建那些不需要改变的UI是性能浪费。 我们将采取以下方法来减少不必要的Widget重建。
- 将大的Widget拆分成小的Widget。
- 使用const构造函数,减少不必要的Widget构建
2.25 Flutter 如何与 Android iOS 通信?
Flutter通过PlatformChannel与原生交互,PlatformChannel分为三种:
- BasicMessageChannel:用于传递字符串和半结构化的信息。
- MethodChannel:在flutter进行序列化,将数据发送到native,在native编写交互,然后回传序列化数据到flutter。
- EventChannel:从native发送stream数据流到flutter,这对监控传感器数据的场景很有用。
2.26 dio 拦截器
dio是flutter中HTTP请求库,Dio拦截器可以在请求发送前或响应会返回后对请求和响应进行处理。 使用interceptors.add()方法添加拦截器: 请求拦截:设置Authorization 令牌、Content-Type,确保服务器能正确识别请求的来源和内容类型。 响应拦截:统一处理错误,比如显示错误信息。
2.27 Flutter JSON解析与复杂模型转换。
简单的model,用序列化json转map通过dart:convert 库中的jsonDecode()方法,把json字符串转成Map 复杂的模型转换:利用JSON TO Dart 和手动转换。
2.28 Flutter 调试办法
官方可视化调试工具DevTools,类似前端开发网页调试工具,检查UI组件布局和状态,查看CPU、网络等问题。