Flutter经典面试题[持续更新]

226 阅读7分钟

如何理解Flutter的三棵树

Widget树

由开发者直接绘制的由Widget组成的配置树。一旦创建就是不可变的,只会频繁地重建,并且由重建决定Element和RenderObject的创建和更新

Element树

是Widget的实例化,负责管理Widget和RenderObject,处理UI的更新和生命周期

RenderObject树

由RenderObject组成的树,负责处理实际的布局和渲染

如何理解Flutter中的context

Context是Flutter中Widget的核心,是BuildContext的实例,代表了当前Widget在Widget树中的位置,

  1. 确定Widget在Widget树中的位置
  2. 访问资源和服务
  3. 构建Widget
  4. 查找祖先Widget
  5. 判断Widget的生命周期

解释 Stateful Widget Lifecycle ?

  • 创建阶段createState -> initState -> didChangeDependencies -> build
  • 更新阶段didUpdateWidget -> build
  • 销毁阶段deactivate -> dispose

createState()

当Widget被插入到Widget树中时被调用,会创建一个与之关联的state对象

initState()

在state对象被创建后,并且build方法被调用之前,用于进行初始化状态等一次性动作

didChangeDependencies()

  • 在initState之后立即调用
  • 当State对象依赖的InheritedWidget发生变化时调用

用于处理依赖InheritedWidget的数据或者依赖于上下文的操作

build()

每当State对象需要重新构建UI的时候调用,会构建并且返回描述UI的widget树

didUpdateWidget()

当父组件重建并且传入一个新的widget时,但State对象保持不变,主要用于响应Widget的更新,可能会根据新的Widget更新状态

setState()

根据新的State对象重新构建UI,触发build方法重新构建UI

deactivate()

当State对象从树中移除的时候调用,用于清理资源和保存状态

dispose()

当State对象从树中永久移除时,用于释放资源

InheritedWidget是什么

是一个特殊的Widget,用于在Widget树中传递数据。允许任意子Widget访问他祖先Widget中的数据,并且当InheritedWidget的数据发生改变时,依赖于这个数据的子Widget会自动创建,从而实现状态管理和数据共享

class MyTest extends InheritedWidget{
  final int data;
  const MyTest(this.data, {super.key, required super.child});
  // of方法 通过context从最近的context中获取MyTest实例
  // 如有就返回
  // 如果没有就返回null
  static MyTest? of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyTest>();
  }
  // 通过数据判断是否应该通知子Widget
  @override
  bool updateShouldNotify(covariant MyTest oldWidget) {
   return oldWidget.data!=data;
  }

为什么build方法要在State上而不是在stf上

  1. 将状态管理与UI构建分开:

    1. Widget是不可变的,所以每次stf重建时都会重新创建一个实例,如果build方法放在stf里,那么每次重建都会重新调用build,造成性能浪费
    2. State是持久的,stf重建时不会被销毁,而是会被保留。在build方法里能使用这些状态重构UI,如果是局部更新,会根据diff算法更新部分,优化绘制性能
  2. State有自己的生命周期,开发者可以适当时机处理操作,如果build方法在stf中,那就无法协同操作了。

  3. 数据与UI分离的开发思维

Flutter中有几种key

主要用于决定Widget是否会被刷新

LocalKey

用于在局部Widget树中标识某一个Widget

  • ValueKey

    使用基础数据作为标志的key,一般用在列表多一点

  • ObjectKey

    使用对象作为标志的key,一般用于需要用对象类型作为标志的场景

  • UniqueKey

    保证了key的唯一性,一般用于需要确保该对象唯一的情况下

GlobalKey

全局唯一的key,可以在整个Widget树中唯一标记一个Widget,可以通过该key获取到对应Widget的State对象

GetX

Get.put和 Get.find

通过_insert方法进行插入到缓存的一个map中

通过find方法从map中获取到对应的实例

Obs实现原理

包装成rx类,内部持有对应的数据以及提供了监听和更新的机制

  1. Obx控件中包含了RxNotifier对象,在initState回调里进行监听,当数据有变化时会调用_updateTree方法进行更新
  2. Obs封装了一个可观察的对象,当值发生变化时,会通过update方法通知所有依赖它的组件或监听器

GetBuilder实现原理

GetBuilder 是 GetX 中基于 GetXController 和 StatefulWidget 的状态管理方式。 它通过 update() 方法手动触发 UI 更新,适用于需要精确控制更新的场景。 与 Obs 相比,GetBuilder 更加轻量,适合简单的状态管理需求。

  1. GetBuilder** 通过观察者模式实现状态更新**:它监听 Controller 的状态变化,当 Controller 调用 update() 时,通知 GetBuilder 刷新 UI。
  2. 基于 StatefulWidget 和 ****setStateGetBuilder 内部使用 StatefulWidget,通过 setState 触发局部 UI 更新,确保高效渲染。
  3. 局部更新,性能优化GetBuilder 只更新其包裹的 UI 部分,避免全局刷新,提升应用性能。

Flutter渲染流程

  1. 在Dart framework中构建Widget树。
  2. 调用rendering库,将Widget树转换为Element树。
  3. 再将Element树转换为RenderObject树,进行布局和绘制。
  4. 通过Skia引擎,将RenderObject树转换为GPU命令,并提交给GPU执行。
  5. GPU执行这些命令,最终将渲染结果显示在屏幕上。

Flutter屏幕适配

原生自己适配

通过MediaQuery获取屏幕的宽高,自己去做适配

final mediaQueryData = MediaQuery.of(context);
final screenWidth = mediaQueryData.size.width;
final screenHeight = mediaQueryData.size.height;

使用第三方库

flutter_screenutil: ^5.9.3

在根入口包裹App,给定设计稿尺寸

 return ScreenUtilInit(
        designSize: const Size(750, 1334),
        builder: (context, child) {
          return GetMaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
              useMaterial3: true,
            ),
            home: const MyHomePage(title: 'Flutter Demo Home Page'),
          );
        });

后续使用 .w以及.h拓展,在对应的num拓展里进行了屏幕适配

  double get w => ScreenUtil().setWidth(this);

  double setWidth(num width) => width * scaleWidth;

  double get scaleWidth => !_enableScaleWH() ? 1 : screenWidth / _uiSize.width;

与原生通信的三种渠道

BasicMessageChannel

基础数据传递,可双向通信

MethodChannel

用于传递方法调用,可以有返回值,适用于一次性的通信,可双向通信

EventChannel

用于数据流通信,支持原生侧主动向flutter发送数据流

String StringBuilder StringBuff有什么区别

特性StringStringBuilderStringBuff
可变性不可变可变可变
线程安全性安全不安全安全
性能有同步开销

extends和implements和Mixins的区别

特性extendsimplementsMixins
作用创建一个类继承另一个类的属性和方法实现一个接口,强制类实现接口中定义的所有方法和属性将代码片段注入到类中,实现代码复用
多继承不支持,只能继承一个父类支持,可以实现多个接口支持,可以混入多个 Mixin
使用场景用于扩展已有类的功能用于实现接口,强制类遵循特定规范用于将代码片段注入到多个类中,实现代码复用

setState的作用

调用 setState() 会将当前组件标记为“脏”(Dirty),表示需要重新构建,Flutter 框架会在下一帧渲染时,调用 build() 方法重新构建组件,Flutter 使用高效的 Diff 算法,只更新发生变化的部分,而不是整个 UI

diff算法

  1. 构建新的 Widget 树

调用 build() 方法生成新的 Widget 树。

  1. 比较新旧 Widget 树

从根节点开始,递归比较新旧 Widget 树的每个节点。

比较的依据:

类型:如果 Widget 的类型不同,则移除旧 Widget 并创建新 Widget。

Key:如果 Widget 的 Key 不同,则移除旧 Widget 并创建新 Widget。

属性:如果 Widget 的类型和 Key 相同,但属性不同,则更新对应的 RenderObject。

  1. 更新 Element 树

根据比较结果,更新 Element 树:

如果 Widget 相同,则复用对应的 Element。

如果 Widget 不同,则移除旧的 Element 并创建新的 Element。

  1. 更新 RenderObject 树

根据 Element 树的变化,更新 RenderObject 树:

更新布局、绘制和合成等操作。

如何获取控件的大小和位置?

  1. 使用Key拿到上下文取得findRenderObject拿内容的尺寸数据;
  2. 使用context取得findRenderObject拿内容的尺寸数据;