2024最新Flutter面试题分享(含答案解析)

2024-04-113,671阅读10分钟

Dart部分

String扩展一个方法
  1. 使用关键字extension ... on为String定义一个扩展类
  2. 在为扩展类添加一个 新的方法
  3. String类型对象调用这个扩展的方法

dart

体验AI代码助手

代码解读

复制代码

extension StringExt on String{ // 2. 扩展方法 int add(int x, int y){ return x + y; } } void mian(){ String str = 'hello'; // 3. 使用扩展类方法 int result = str.add(3, 7); debugPrint(result.toString()); } // 单元测试 import 'package:flutter_start/extTest.dart'; import 'package:flutter_test/flutter_test.dart'; void main(){ test('StringExt', (){ String ext = 'ext'; expect(ext.add(3, 7), 10); }); }

dart是单继承还是多继承?

单继承

dart如何达到多继承的效果?

Dart中使用Mixins,可以达到多继承的效果

mixin混入有什么特点
  1. 作为mixins的类只能继承自Object,不能继承其他类
  2. 作为mixins的类不能有构造函数
  3. 一个类可以mixins多个mixins类
  4. mixins绝不是继承,也不是接口,而是一种全新的特性

dart

体验AI代码助手

代码解读

复制代码

// 类D 继承A和B 关键字 with class D extends A with B{ } // mixin 的使用 class A { String info="this is A"; void printA(){ print("A"); } void run(){ print("A Run"); } } class B { void printB(){ print("B"); } void run(){ print("B Run"); } } class C extends Person with B,A{ C(String name, num age) : super(name, age); }

混入相同方法的多个混入,最终会执行哪一个?

后面的类中的方法将前面的类中相同的方法覆盖

dart运行机制是什么样的?

消息循环机制

  1. 两个队列,微任务队列和事件队列。
  2. microtask queue 的优先级高于event queue。
  3. 在每一次事件循环中,Dart总是先去第一个microtask queue中查询是否有可执行的任务,如果没有,才会处理后续的event queue的流程。
如何向事件队列插入任务?

Future就是将任务插入到事件队列

向微任务队列插入任务

Future.microtask()
scheduleMicrotask()
Stream中的执行异步的模式就是scheduleMicrotask。因为microtask的优先级又高于event。所以,如果 microtask 太多就可能会对触摸、绘制等外部事件造成阻塞卡顿

Future和Stream有什么区别?
  1. Future中的任务会加入下一轮事件循环,而Stream中的任务则是加入微任务队列
  2. Future 用于表示单个运算的结果,而 Stream 则表示多个结果的序列。

Stream 有同步流和异步流之分。它们的区别在于同步流会在执行 add,addError 或 close 方法时立即向流的监听器 StreamSubscription 发送事件,而异步流总是在事件队列中的代码执行完成后在发送事件。`

Stream订阅模式有哪几种?

Stream分为Single Subscription和Broadcast两种类型, 前者只允许订阅(listen)一次,后者允许多次订阅。

单订阅在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。
广播订阅可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。

Stream单订阅,多次订阅会出现什么结果?

会报错,单订阅只能有一次订阅.
即使取消了第一个监听器,也不允许在单订阅流上设置其他的监听器。

Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。
通过 Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

dart是单线程还是多线程?

Dart是单线程模型

Dart是如何实现多任务并行的

主要依赖dart的并发编程(Isolate)、异步和事件驱动机制

Isolate
  • 在dart中,一个Isolate对象其实就是一个Isolate执行环境的引用,一般来说我们都是通过当前的Isolate去控制其他的Isolate完成彼此之间的交互,而当我们想要创建一个新的Isolate可以使用Isolate.spawn方法获取一个新的Isolate对象,两个Isolate之间使用SendPort相互发送消息,而Isolate中也存在了一个与之对应ReceivePort接收消息用来处理,但是我们需要注意的是SendPortReceivePort在每一个Isolate都有一对,只有同一个Isolate中的ReceivePort才能接受当前类的SendPort发送的消息并且处理。
  • Isolate可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。

dart

体验AI代码助手

代码解读

复制代码

@override void initState() async { super.initState(); // 主Isolate的ReceivePort ReceivePort receivePort = ReceivePort(); SendPort? otherSendPort; // 主Isolate接收到子Isolate中由主Isolate的SendPort发送过来的消息 receivePort.listen((message) { if(message is SendPort){ otherSendPort = message; }else{ // 处理消息 // ...... // 子Isolate的SendPort在主Isolate中向子Isolate发送消息 otherSendPort?.send('我是来自主Isolate的消息'); } }); // 创建子Isolate Isolate isolate = await Isolate.spawn((message) { // message 是主Isolate的SendPort // 在子Isolate中创建一个新的ReceivePort ReceivePort recPort = ReceivePort(); // 主Isolate的SendPort SendPort? mainSendPort; // 运用主Isolate的SendPort将子Isolate的SendPort发送给主Isolate message.send(recPort.sendPort); // 子Isolate监听接收到主Isolate那边发送的消息 谁发送?子Isolate的SendPort recPort.listen((msg) { if(msg is SendPort){ mainSendPort = msg; }else{ // 主Isolate的SendPort向主Isolate发送消息 mainSendPort?.send('我是来自子Isolate的消息'); } }); }, receivePort.sendPort);// 参数二 将主Isolate的SendPort传递给子Isolate }

await for如何使用?

await for是用来不断获取stream流中的数据,然后执行循环体中的操作。它一般用在直到stream什么时候完成,并且必须等待传递完成后才能使用,不然会阻塞。


dart

体验AI代码助手

代码解读

复制代码

Stream<String> stream = new Stream<String>.fromIterable(['1', '2', '3','4']); main() async{ await for(String s in stream){ print(s); } }

如何实现websocket稳定连接?
  • 定期发送心跳包,一般1秒一次
  • 捕获关闭连接事件并重连websocket
  • 实现断线重连

Flutter部分

A、B两个组件在setState前修改背景颜色,是否会修改成功?

Flutter 渲染流程是什么?(GPU)

Flutter只关心向 GPU提供视图数据,GPU的 VSync信号同步到 UI线程,UI线程使用 Dart来构建抽象的视图结构,这份数据结构在 GPU线程进行图层合成,视图数据提供给 Skia引擎渲染为 GPU数据,这些数据通过 OpenGL或者 Vulkan提供给 GPU。

Widget、Element、RenderObject三者关系是什么?
简述

widget是用于描述Element配置信息的,flutter中一切都是widget,尺寸、颜色、组件等都是widget
element是widget树上特定位置的实例
renderobject是渲染上的一个对象

依赖关系:Element树依赖Widget树,渲染树依赖Element树,最终的UI树是由独立的Element节点构成。

一个widget会创建一个element
一个element持有一个widget和render object,element
会对比widget的变化,将那写需要更新和重建的widget,同步到render object树,以最小的开销来渲染

一、它们是什么?
  • Widget:对一个Element配置的描述,刷新的过程中随时会重建。(不参与真正的渲染,widget的属性是不可以改变的,要想改变只能重新创建一个widget对象)
  • Element:表示一个Widget树中特定位置的实例,用于对比widget,找出需要更新和重建的widget,更新Element树和RenderObject树。
  • RenderObject:渲染树上的一个对象,用于界面的布局和绘制,负责真正的渲染,实例化一个 RenderObject 是非常耗能。
二、关系
  • 一个Widget会创建一个Element对象,是通过createElement()创建的。
  • 一个Element持有一个RenderObject和一个Widget。Widget 、Element都不负责最后的渲染,真正负责的渲染只有 RenderObject。
  • Widget 具有不可变性,但 Element 却是可变的。Element 树将 Widget 树的变化做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。
flutter生命周期,setstate会执行哪些生命周期?

setState()执行后,会执行build()

  • app的状态:AppLifecycleState
    inactive:活跃可见
    paused:关闭或者切换到后台时,不可见的状态
    hidden:后台运行状态
    resumed:切回到前台可见状态
    detached:关闭状态

  • Flutter SDK 3.13 之前的方式: with WidgetsBindingObserver
    initState()中注册 WidgetsBinding.instance.addObserver(this);
    dispose()移除 WidgetsBinding.instance.removeObserver(this);
    didChangeAppLifecycleState()回调中,检测app的状态

  • Flutter SDK 3.13 之后的方式:AppLifecycleListener


dart

体验AI代码助手

代码解读

复制代码

late final AppLifecycleListener _listener; @override void initState() { super.initState(); // Initialize the AppLifecycleListener class and pass callbacks _listener = AppLifecycleListener( onStateChange: _onStateChanged, ); } @override void dispose() { // Do not forget to dispose the listener _listener.dispose(); super.dispose(); } // Listen to the app lifecycle state changes void _onStateChanged(AppLifecycleState state) { switch (state) { case AppLifecycleState.detached: _onDetached(); case AppLifecycleState.resumed: _onResumed(); case AppLifecycleState.inactive: _onInactive(); case AppLifecycleState.hidden: _onHidden(); case AppLifecycleState.paused: _onPaused(); } }

StatefulWidget 的生命周期方法

createState:可以调用多次

State 对象的生命周期方法

dart

体验AI代码助手

代码解读

复制代码

class _MyWidgetState extends State<MyWidget> { @override void initState() { super.initState(); // 在 State 对象被插入树中时调用,这个方法只会被调用一次。 } @override void didChangeDependencies() { super.didChangeDependencies(); // 在 initState 之后调用,表示 State 对象的依赖关系发生变化。 } @override Widget build(BuildContext context) { // 在此构建 Widget 树 return Container(); } @override void didUpdateWidget(MyWidget oldWidget) { super.didUpdateWidget(oldWidget); // 当父 Widget 重建时调用。 // 如果父 Widget 重建时需要重新配置子 Widget,则会调用此方法。 } @override void deactivate() { // 在此处理 State 对象从树中被移除的操作 super.deactivate(); } @override void activate() { // 在此处理 State 对象被重新插入树里 super.activate(); } @override void dispose() { // 当 State 对象被永久从树中移除时调用 super.dispose(); } } // 打开页面执行:initState、didChangeDependencies // 关闭页面执行:deactivate、dispose

StatelessWidget 的生命周期方法

dart

体验AI代码助手

代码解读

复制代码

class MyWidget extends StatefulWidget { @override Widget build(BuildContext context) { return Widget; } }

setState()执行做了什么事?

setState()过程主要工作是记录所有的脏元素,会引起build函数执行,更新widget树、更新Element树和RenderObject树,最后重新渲染。

flutter中的key?
  • 作用:比较两个Widget是不是同一个Widget
  • 分类:LocaleKey、GlobalKey
  • LocaleKey:ValueKey、ObjectKey、UniqueKey。
statelesswidget和statefullwidget有什么区别?

StatelessWidget 没有要管理的内部状态.
无状态widget的build方法通常只会在以下三种情况调用:

  • 将widget插入树中时
  • 当widget的父级更改其配置时
  • 当它依赖的InheritedWidget发生变化时

StatefullWidget是可变状态的widget。 使用setState方法管理StatefulWidget的状态的改变。调用setState告诉Flutter框架,某个状态发生了变化,Flutter会重新运行build方法,以便应用程序可以应用最新状态。

在有状态类中编写一个按钮调用初始化生命周期(initState)方法,会发生什么?

报错,但不影响布局;会报生命周期创建错误;

如何获取控件的大小和位置?
  1. 使用Key拿到上下文取得findRenderObject拿内容的尺寸数据;
  2. 使用context取得findRenderObject拿内容的尺寸数据;
Flutter 是如何与原生Android、iOS进行通信的?

PlatformChannel
BasicMessageChannel :用于传递字符串和半结构化的信息。
MethodChannel :用于传递方法调用(method invocation)。
EventChannel : 用于数据流(event streams)的通信。


dart

体验AI代码助手

代码解读

复制代码

// 1. 创建java类。 实现FlutterPlugin和MethodCallHandler public class MsaOaidPlugin implements FlutterPlugin, MethodCallHandler{ @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { Log.e("---------","==========onAttachedToEngine"); channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "msa_oaid"); channel.setMethodCallHandler(this); this.context = flutterPluginBinding.getApplicationContext(); System.loadLibrary("msaoaidsec"); } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { Log.e("---------","==========onMethodCall"); if(call.method.equals("isSupport")){ new DemoHelper().getDeviceIds(context, new IIdentifierListener() { @Override public void onSupport(IdSupplier idSupplier) { result.success(idSupplier.isSupported()); } }); }else if(call.method.equals("getOaid")){ new DemoHelper().getDeviceIds(context, new IIdentifierListener() { @Override public void onSupport(IdSupplier idSupplier) { result.success(idSupplier.getOAID()); } }); }else{ result.notImplemented(); } } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { channel.setMethodCallHandler(null); Log.e("---------","==========onDetachedFromEngine"); } } // 2. flutter lib包下创建dart类 class MsaOaid { static const MethodChannel _channel = MethodChannel('msa_oaid'); static Future<bool> isSupport() async { final bool support = await _channel.invokeMethod('isSupport'); return support; } static Future<String?> getOaid() async { final String? oaid = await _channel.invokeMethod('getOaid'); return oaid; } }

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐