flutter常见面试题(欢迎私信投稿——更新到10)
1、谈谈Flutter中的Future、async和await
Future对象表示异步操作的结果,我们通常通过then()来处理返回的结果
async用于标明函数是一个异步函数,其返回值类型是Future类型
await用来等待耗时操作的返回结果,这个操作会阻塞到后面的代码
isolate异步并行多个任务,Future是异步串行多个任务
2、介绍Widget、State、Context 概念
Widget:在Flutter中,几乎所有东西都是Widget。将一个Widget想象为一个可视化的组件(或与应用可视化方面交互的组件),当你需要构建与布局直接或间接相关的任何内容时,你正在使用Widget。
Widget树:Widget以树结构进行组织。包含其他Widget的widget被称为父Widget(或widget容器)。包含在父widget中的widget被称为子Widget。
Context:仅仅是已创建的所有Widget树结构中的某个Widget的位置引用。简而言之,将context作为widget树的一部分,其中context所对应的widget被添加到此树中。一个context只从属于一个widget,它和widget一样是链接在一起的,并且会形成一个context树。
State:定义了StatefulWidget实例的行为,它包含了用于”交互/干预“Widget信息的行为和布局。应用于State的任何更改都会强制重建Widget。
3、举几个提升flutter性能的方法
在 Flutter 中,提升性能有多种方法:
- 减少不必要的Widget重建
- 使用const关键字:对于不会改变的Widget,用 const 修饰。像 const Text('固定文本') ,这样Flutter就知道该Widget无需重建,能节省资源,因为它被视为编译时常量,在多处复用也不产生额外开销 。
- 使用StatefulWidget恰当:无状态改变需求时,别用 StatefulWidget 。例如简单展示静态文本, StatelessWidget 足够,避免多余的状态管理开销。另外,拆分 StatefulWidget 为更细粒度的组件,只让真正需要更新状态的部分成为 Stateful ,限制重建范围。
- 使用Key精准控制:当Widget树里相同类型Widget位置更替,给它们添加唯一 Key 。如 ListView 动态增删item,合适的 Key 能让Flutter精准定位变化,而非大规模重建。
- 优化布局
- 避免嵌套过深:多层嵌套的 Column 、 Row 、 Padding 等组件会拖慢渲染。可以用 Flex 组件灵活排版,减少嵌套层级;或是使用 CustomMultiChildLayout 来自定义布局,提升布局效率。
- 使用SizedBox.shrink():当想让某个Widget占最小空间,优先用它替代 Container 设置 width/height 为0, SizedBox.shrink() 没有额外绘制负担。
- 图片处理
- 压缩图片资源:上传前在本地压缩图片,或使用网络图片时,选择合适尺寸,防止加载超大图拖慢加载速度与内存占用。
- 使用合适的图片缓存:Flutter默认有图片缓存机制,也可以借助第三方库如 cached_network_image ,合理设置缓存时长与大小,减少重复网络请求。
- 异步操作优化
- 高效处理网络请求:使用如 dio 这类网络库时,批量发起请求、合理设置超时时间,利用异步编程避免阻塞主线程。在等待网络响应时,主线程还能处理UI交互,维持流畅性。
- 后台任务调度:耗时的计算任务放到后台线程执行,Flutter里能借助 compute 函数,把CPU密集型运算放到独立的Isolate(类似线程但更安全)处理,处理完再更新UI。
- 内存管理
- 及时释放资源:监听 State 的 dispose 生命周期方法,及时清理定时器、流订阅、动画控制器等,防止内存泄漏。比如取消网络请求、关闭文件流,让不再使用的对象能被垃圾回收。
- 优化数据存储:大量数据存储在内存时,考虑分页加载,而非一次性加载海量数据。像长列表场景,只加载屏幕内及附近少量item,滚动时动态更新。
4、Widget的StatelessWidget和StatefulWidget两种状态组件类
StatelessWidget: 创建以后不去关心任何变化,在下次构建之前都不会改变。它们除了依赖于自身的配置信息(在父节点构建时提供)外不再依赖于任何其他信息。比如典型的Text、Row、Column、Container等,都是StatelessWidget。它的生命周期简单:初始化、通过build()渲染。
StatefulWidget: 在生命周期内,这种类型的Widget所持有的数据可能会发生变化,这样的数据被称为State,这些拥有动态内部数据的Widget被称为StatefulWidget。比如复选框、Button等。State会与Context永久的建立关联并且不会改变Context。 当state与context关联时,state被视为已挂载。StatefulWidget由两部分组成,在初始化时必须要在createState()时初始化一个与之相关的State对象。
5、StatefulWidget 的生命周期
Flutter的Widget分为StatelessWidget和StatefulWidget两种。其中,StatelessWidget是无状态的,StatefulWidget是有状态的,因此实际使用时,更多的是StatefulWidget。StatefulWidget的生命周期如下。initState():Widget 初始化当前 State,在当前方法中是不能获取到 Context 的,如想获取,可以试试 Future.delayed()
didChangeDependencies():在 initState() 后调用,State对象依赖关系发生变化的时候也会调用。
deactivate():当 State 被暂时从视图树中移除时会调用这个方法,页面切换时也会调用该方法,和Android里的 onPause 差不多。
dispose():Widget 销毁时调用。
didUpdateWidget:Widget 状态发生变化的时候调用。
6、Flutter 是如何与原生Android、iOS进行通信的?
Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:BasicMessageChannel :用于传递字符串和半结构化的信息。
MethodChannel :用于传递方法调用(method invocation)。
EventChannel : 用于数据流(event streams)的通信。
7、简述Flutter 的热重载?
Flutter 的热重载是基于 JIT 编译模式的代码增量同步。由于 JIT 属于动态编译,能够把 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。热重载的流程可以分为 5 步,包括:扫描工程改动、增量编译、推送更新、代码合并、Widget 重建。Flutter 在接收到代码变更后,并不会让 App 重新启动执行,而只会触发 Widget 树的重新绘制,因此可以保持改动前的状态,大大缩短了从代码修改到看到修改产生的变化之间所需要的时间。
另一方面,由于涉及到状态的保存与恢复,涉及状态兼容与状态初始化的场景,热重载是无法支持的,如改动前后 Widget 状态无法兼容、全局变量与静态属性的更改、main 方法里的更改、initState 方法里的更改、枚举和泛型的更改等。
可以发现,热重载提高了调试 UI 的效率,非常适合写界面样式这样需要反复查看修改效果的场景。但由于其状态保存的机制所限,热重载本身也有一些无法支持的边界。
8、Stream数据流?
在Dart中,Stream 和 Future 一样,都是用来处理异步编程的工具。它们的区别在于,Stream 可以接收多个异步结果,而Future 只有一个。
Stream 的创建可以使用 Stream.fromFuture,也可以使用 StreamController 来创建和控制。还有一个注意点是:普通的 Stream 只可以有一个订阅者,如果想要多订阅的话,要使用 asBroadcastStream()。
9、runApp()和main()有什么区别?
main()是任何Dart或Flutter应用程序的入口点。这是应用程序启动时执行的第一个函数。
runApp()是由Flutter框架提供的函数。它的主要目的是将Flutter应用程序的根小部件附加到屏幕上。
final是运行时常量,值在运行时赋值;const是编译期常量,值在编译时赋值;
10、Fultter如何发起网络请求?常用那些库?
使用的http库(Dart原生)
步骤一:添加依赖
在pubspec.yaml文件中添加http库的依赖。http是 Dart 语言的一个网络请求库,在 Flutter 项目中也可以很好地使用。
步骤二:发起请求
例如,发起一个简单的 GET 请求来获取一些数据。首先需要导入http库:
在上述代码中,Uri.parse()方法用于将一个字符串形式的 URL 转换为Uri对象,这是http库中请求方法所要求的参数格式。await关键字用于等待异步操作(网络请求)完成。response.statusCode用于获取请求的状态码,response.body用于获取服务器返回的数据内容。
dio库
步骤一:添加依赖
在pubspec.yaml文件中添加dio库的依赖。dio是一个强大的 Dart HTTP 请求库,支持多种请求方式、拦截器等功能。
同样需要运行flutter pub get来获取该依赖。
步骤二:发起请求
首先导入dio库:
Dio类的实例用于管理请求。dio.get()方法用于发起 GET 请求,它会返回一个包含服务器响应数据的对象。如果请求过程中出现错误,会被catch块捕获并打印错误信息。
常用网络请求库
http库:
特点:它是 Dart 原生的网络请求库,简单易用,对于基本的网络请求操作(如 GET、POST 等)很方便。它的 API 相对简洁,学习成本较低,适合初学者或者只需要进行简单网络通信的场景。
适用场景:简单的数据获取,例如从一个提供公开 API 的服务器获取一些配置信息、新闻列表等。或者用于向服务器发送简单的表单数据,如用户登录(POST 请求用户名和密码)等基础操作。
- dio库
特点:功能强大,支持多种请求方法(GET、POST、PUT、DELETE 等)。它具有拦截器功能,可以在请求发送前和响应返回后进行统一的处理,比如添加请求头、处理错误、日志记录等。dio还支持文件上传和下载,并且可以方便地配置请求的超时时间等参数。
适用场景:适用于复杂的网络请求场景,比如需要在多个请求中添加统一的认证头信息,或者需要对请求和响应进行详细的日志记录。在进行文件上传(如用户头像上传)和下载(如下载应用更新文件)时也非常方便。同时,对于大型项目,需要对网络请求进行统一管理和错误处理时,dio是一个很好的选择。
- Chopper库
特点:Chopper是一个基于 Dart 的 HTTP 客户端生成器,它使用代码生成来提供类型安全的 API。它允许通过定义服务接口来生成网络请求相关的代码,这样可以减少手动编写网络请求代码的工作量,并且在编译时可以发现一些错误。
适用场景:适合在大型项目中,对代码的规范性和可维护性要求较高的场景。例如,当有多个不同的 API 端点需要调用,并且希望通过接口的方式来清晰地定义每个端点的请求和响应格式,Chopper可以帮助生成类型安全的网络请求代码,提高代码的可读性和可维护性。