Dart&Flutter基础

150 阅读15分钟

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

  1. LocalKey: 分为ValueKey,ObjectKey 和 UniqueKey。在当前Widget层级下,设置widget唯一属性。

    • Valuekey:value可以是任意类型,用value表示Widget的key。
    • ObjectKey:与ValueKey类似,ValueKey是value相等,key值相等,ObjectKey引用相同,那么key值相等。
    • UniqueKey:是独一无二的,Build函数中,UniqueKey也会被重新创建。大部分情况下不会用到。
  2. 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 的生命周期

大致分为四个阶段:

  1. 初始化createState和initState,
  2. 组件创建didChangeDependencies和build
  3. 触发build(因为didChangeDependencies、setState、或者didUpdateWidget而引发build)
  4. 销毁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树中需要使用数据(状态)的地方

image.png

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、网络等问题。