2024 Flutter面试题一

Dart部分

String扩展一个方法
  1. 使用关键字extension ... on为String定义一个扩展类
  2. 在为扩展类添加一个 新的方法
  3. String类型对象调用这个扩展的方法
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);
  });
}
image.png
dart是单继承还是多继承?

单继承

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

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

mixin混入有什么特点
  1. 作为mixins的类只能继承自Object,不能继承其他类
  2. 作为mixins的类不能有构造函数
  3. 一个类可以mixins多个mixins类
  4. mixins绝不是继承,也不是接口,而是一种全新的特性
// 类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
  • 使用场景:视频的编码转码,需要非常高的CPU计算
  • 在dart中,一个Isolate对象其实就是一个Isolate执行环境的引用,一般来说我们都是通过当前的Isolate去控制其他的Isolate完成彼此之间的交互,而当我们想要创建一个新的Isolate可以使用Isolate.spawn方法获取一个新的Isolate对象,两个Isolate之间使用SendPort相互发送消息,而Isolate中也存在了一个与之对应ReceivePort接收消息用来处理,但是我们需要注意的是SendPortReceivePort在每一个Isolate都有一对,只有同一个Isolate中的ReceivePort才能接受当前类的SendPort发送的消息并且处理。
  • Isolate可以把它理解为Dart中的线程。但它又不同于线程,更恰当的说应该是微线程。它与线程最大的区别就是不能共享内存,因此也不存在锁竞争问题,两个Isolate完全是两条独立的执行线,且每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。
    创建Isolate的两种方式:Isolate.spawn() 和 compute()
    compute的使用还是有些限制,它没有办法多次返回结果,也没有办法持续性的传值计算,每次调用,相当于新建一个隔离,如果调用过多的话反而会适得其反。
@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;
        otherSendPort.send(receivePort.sendPort());
      }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
  }


// 方式二
// 注册主 isolate 的 SendPort
  ReceivePort mainReceivePort = ReceivePort();
  mainReceivePort.listen((state){
    // 获取上次执行任务的时间戳
    final lastExecutedTime = SpUtil().getStandLastTime(); // 从本地存储或数据库获取
    final now = DateTime.now().millisecondsSinceEpoch;
    final interval = now - lastExecutedTime;
    if (kDebugMode) {
      print("Native called background interval: $interval");
    }
    // 判断是否达到 1 小时
    if (interval >= 3600000) {
      // 执行你的任务
      /// 检查步数是否在变化  没有变化需要站立提醒
      GlobalEvent().emit(GlobalName.wearStandCheck);
      // 更新上次执行时间
      SpUtil().setStandLastTime(now); // 将 now 保存到本地存储或数据库
    }

  });
  IsolateNameServer.registerPortWithName(mainReceivePort.sendPort, 'main_isolate');

  Workmanager().initialize(
      callbackDispatcher, // The top level function, aka callbackDispatcher
      isInDebugMode: false // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
  );
  /// Android系统限制 15分钟执行一次
  Workmanager().registerPeriodicTask(
    "periodic-task-identifier",
    "simplePeriodicTask",
    inputData: <String, dynamic>{
      'key': 'value',
    },
    // When no frequency is provided the default 15 minutes is set.
    // Minimum frequency is 15 min. Android will automatically change your frequency to 15 min if you have configured a lower frequency.
    frequency: const Duration(minutes: 15),
  );
await for如何使用?

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

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)

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


20190227094157995.png

image.png
Widget、Element、RenderObject三者关系是什么?

Widget包含业务代码,widget树更庞大;Element是对widget的抽取,只包含build函数,去除业务代码。

简述

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。Element树与Widget树一一对应,每个Element负责管理一个Widget的配置和生命周期。
  • Widget 具有不可变性,但 Element 却是可变的。Element 树将 Widget 树的变化做了抽象,可以只将真正需要修改的部分同步到真实的 RenderObject 树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。
flutter生命周期,setstate会执行哪些生命周期?
image.png
image.png
  • setState()执行后,会执行build()
  • 父Widget使用了InheritedWidget管理状态,子Widget使用了状态数据,set State时,子组件的didChangeDependencies会调用。

https://juejin.cn/post/7348680291935862818?searchId=20240410133635350846A909709A89034C

  • 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

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 对象的生命周期方法
class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    // 在 State 对象被插入树中时调用,这个方法只会被调用一次。
  }
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 在 initState 之后调用,表示 State 对象的依赖关系发生变化。
    // state对象依赖发生改变会调用
  }
  @override
  Widget build(BuildContext context) {
    // 在此构建 Widget 树
    return Container();
  }
  @override
  void didUpdateWidget(MyWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 当父 Widget 重建时,canUpdate 返回true时调用。
    // 如果父 Widget 重建时需要重新配置子 Widget,则会调用此方法。
  }
  @override
  void deactivate() {
    // 在此处理 State 对象从树中被移除的操作
    super.deactivate();
  }

  @override
  void dispose() {
    // 当 State 对象被永久从树中移除时调用
    super.dispose();
  }
}

// 打开页面执行:initState、didChangeDependencies
// 关闭页面执行:deactivate、dispose

StatelessWidget 的生命周期方法
class MyWidget extends StatefulWidget {
  @override
  Widget build(BuildContext context) {
    return Widget;
  }
}

StatelessWidget组件如何监听数据变化?

  1. context.watch<Counter>
  2. ChangeNotifierProvider + Consumer

Element的生命周期

initial 初始化
active 激活状态
inactive 未激活状态
defunct 失效状态

如何监听页面paused和resume状态?

利用RouteObserverMaterialApp中注册navigatorObservers,然后在页面中注册监听routeObserver.subscribe(this, ModalRoute.of(context)!),同时混入RouteAware,重写didPopNext 和 didPushNext,可实现对页面的监听。

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)的通信。

// 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;
  }
}
Flutter中Widget的分类有哪些?Widget状态有哪些?

Widget的分类有三类。

  1. 组合类Widget,通过继承StatelessWidget和StatefulWidget的类。
  2. 代理类Widget,如功能组件InheritedWidget。Theme、MediaQuery正是基于InheritedWidget实现的。
  3. 绘制类Widget,通过RenderObjectWidget实现的Widget,如Align、Padding、ConstrainedBox等。
    Widget状态有: StatelessWidget 和 StatefulWidget

Android部分

实现app保活有哪些方式?
计步器在app进程被杀这段时间如何计步?

补充

https://zhuanlan.zhihu.com/p/102193331

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容