likes
comments
collection
share

Flutter 问题集锦

作者站长头像
站长
· 阅读数 14

dart的作用域

Dart 没有 「public」「private」等关键字,默认就是公开的,私有变量使用 下划线 _开头。

Dart中var与dynamic的区别

使用var来声明变量,dart会在编译阶段自动推导出类型。而dynamic不在编译期间做类型检查而是在运行期间做类型校验。

Flutter 问题集锦

var 不能重新赋值为其他类型 。 dynamic可以重新赋值为其他类型,Object可以重新赋值为其他类型, String(固定类型)也不能重新赋值为其他类型

const和final的区别

使用过程中从来不会被修改的变量, 可以使用 final 或 const, 而不是 var 或者其他类型, Final 变量的值只能被设置一次; Const 变量在编译时就已经固定 (Const 变量 是隐式 Final 的类型.) 最高级 final 变量或类变量在第一次使用时被初始化。

你们之前的代码是空安全的吗?讲一下什么是空安全。相关操作符有哪些? late关键字

?. ?? ?=

dart 扩展有了解过吗?如何使用?之前有没有在某些场景中用到过。

如果一个flutter插件需要集成进两个App中,其中界面相同但是有些文案不同。比如说界面title:一个叫单词本,一个叫单词列表,一个是单词详情》 一个是进入详情》,假如类似这种情况我们应该怎么处理?

dynamic类型的变量可以进行扩展吗?不可以

extension StringExtension on String {
  int toInt() {
    return int.parse(this);
  }
}

dart中有接口吗?

dart中没有接口,类就是接口,dart中子类必须实现接口中的所有方法,哪怕是非抽象的方法?

Flutter 级联运算符(..)是什么?有什么好处?

  1. 级联运算符。

它减少了变量的定义并且可以让你在一个对象上连续调用多个对象上的方法。

querySelector('#confirm') // 获取对象。
  ..text = 'Confirm' // 调用成员变量。
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

等价于

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
  1. 级联运算符可以嵌套吗。

级联运算符可以嵌套。

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

什么是命名构造函数?它的作用是什么?如何使用?

使用命名构造函数可为一个类实现多个构造函数, 也可以使用命名构造函数来更清晰的表明函数意图:

class Point {
  num x, y;

  Point(this.x, this.y);

  // 命名构造函数
  Point.origin() {
    x = 0;
    y = 0;
  }
}

什么重定向构造函数?

有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。 重定向构造函数的函数体为空, 构造函数的调用在冒号 (:) 之后。

class Point {
  num x, y;

  // 类的主构造函数。
  Point(this.x, this.y);

  // 指向主构造函数
  Point.alongXAxis(num x) : this(x, 0);
}

断言(不用问)

// 确认变量值不为空。
assert(text != null);

// 确认变量值小于100。
assert(number < 100);

// 确认 URL 是否是 https 类型。
assert(urlString.startsWith('https'));

如果 assert 语句中的布尔条件为 false , 那么正常的程序执行流程会被中断。

assert 的第一个参数可以是解析为布尔值的任何表达式。 如果表达式结果为 true , 则断言成功,并继续执行。 如果表达式结果为 false , 则断言失败,并抛出异常 ([AssertionError]

简单说一下在flutter里async和await?

await的出现会把await之前和之后的代码分为两部分,await并不像字面意思所表示的程序运行到这里就阻塞了,而是立刻结束当前函数的执行并返回一个Future,函数内剩余代码通过调度异步执行。 async是和await搭配使用的,await只在async函数中出现。在async 函数里可以没有await或者有多个await。

安卓手机有四核和八核,我们怎么利用这些多核cpu?(对Isolate的理解?)

isolate是Dart对actor并发模式的实现。 isolate是有自己的内存和单线程控制的运行实体。isolate本身的意思是“隔离”,因为isolate之间的内存在逻辑上是隔离的。isolate中的代码是按顺序执行的,任何Dart程序的并发都是运行多个isolate的结果。因为Dart没有共享内存的并发,没有竞争的可能性所以不需要锁,也就不用担心死锁的问题。

那么应该在什么时候使用Future,什么时候使用Isolate呢?

  • 方法执行耗时在几毫秒或者几十毫秒以下的,我们应该用Future
  • 方法执行耗时在百毫秒以上的我们可以采用Isolate 另外我们还可以按照一些场景分类来区分是应该使用Future还是Isolate
  • 网络请求
  • 文件操作
  • json解析
  • 加密解密等

dart异步

dart是单线程模型还是多线程模型,单线程怎么做到异步效果的? (消息队列)

  1. Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。
  2. 入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,当所有微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务,如此循环往复,生生不息。

如图所示:

Flutter 问题集锦

在flutter里streams是什么?有几种streams?有什么场景用到它?

Stream 用来处理连续的异步操作,Stream 是一个抽象类,用于表示一序列异步数据的源。它是一种产生连续事件的方式,可以生成数据事件或者错误事件,以及流结束时的完成事件 Stream 分单订阅流和广播流。 网络状态的监控

future和steam有什么不一样?

在 Flutter 中有两种处理异步操作的方式 Future 和 Stream,Future 用于处理单个异步操作,Stream 用来处理连续的异步操作。

future可以创建一个在微任务队列中运行的异步任务吗?

Future.sync 同步执行,然后调度到microtask queue来完成自己,也就是一个阻塞任务,会阻塞当前线程,sync执行完成了之后代码才会走到下一行

使用过Bloc吗,说一下Bloc的使用过程

bloc模式解析

使用过Getx吗?你们状态管理是怎么做的?说一下Getx能干什么?

getx文档

(mixin)如果说,现在有这样一个需求:(假设有三种动物,猫,鱼,鸭子。有三种行为,游泳,吃饭,爬树。其中他们都会吃饭,鱼和鸭子会游泳,只有猫会爬树。这种类结构在dart中应该怎样设计? 假如他们有公共的方法实现呢?)

这不就是将implement替换成了with,abstract class替换成了mixin嘛,也太简单了。但仔细一看,我们发现mixin里有方法的具体实现,这样可以避免接口的方法必须在子类实现从而导致的代码冗余(Java 8通过关键字default也可以做到这一点)问题。简而言之,mixin相对于接口能够更好的避免代码冗余,使代码更加集中。

//行走
mixin Walker{
  void walk(){...}
}
//游泳行为
mixin Swim{
  void swim(){...}
}
//飞翔行为,由于这个是抽象方法,所以必须在要实现,不能调用super.flying()
mixin Flying {
  void flying();
}
//海豚可以游泳
class Dolphin extends Mammal with Swim{
  @override
  void swim() {
    super.swim();
  }
}
//蝙蝠可以飞、行走
class Bat extends Mammal with Flying,Walk{
  @override
  void flying() {...}
  //覆盖Walk类中的walk方法
  @override
  void walk() {
    super.walk();
  }
}
//猫可以行走,这里没有重写Walk中的方法
class Cat extends Mammal with Walk{}
//鸽子可以行走、飞
class Dove extends Bird with Flying,Walk{

  @override
  void flying() {...}
}
//鸭子可以行走、飞及游泳
class Duck extends Bird with Walk,Flying,Swim{
  @override
  void flying() {...}

  @override
  void walk() {...}
}
//鲨鱼可以游泳
class Shark extends Fish with Swim{...}
//飞鱼可以飞及游泳
class FlyingFish extends Fish with Flying,Swim{
  @override
  void flying() {...}
}

Flutter 组件的生命周期

StatelessWidget

生命周期只有一个,就是build

  • buid
build 是用来创建 Widget 的,但因为 build 在每次界面刷新的时候都会调用,
所以不要在 build 里写业务逻辑,可以把业务逻辑写到你的 StatelessWidget 的构造函数里。

StatefullWidget

StatefulWidget 的生命周期比较复杂,依次为:

  • createState

createState 是 StatefulWidget 里创建 State 的方法,当要创建新的 StatefulWidget 的时候,会立即执行 createState,而且只执行一次,createState 必须要实现:

  • initState

前面的 createState 是在创建 StatefulWidget 的时候会调用,initState 是 StatefulWidget 创建完后调用的第一个方法,而且只执行一次,类似于 Android 的 onCreate、iOS 的 viewDidLoad(),所以在这里 View 并没有渲染,但是这时 StatefulWidget 已经被加载到渲染树里了,这时 StatefulWidget 的 mount的值会变为 true,直到 dispose调用的时候才会变为 false。可以在 initState里做一些初始化的操作。 在 override initState的时候必须要调用 super.initState():

  • didChangeDependencies

当 StatefulWidget 第一次创建的时候,didChangeDependencies方法会在 initState方法之后立即调用,之后当 StatefulWidget 刷新的时候,就不会调用了,除非你的 StatefulWidget 依赖的 InheritedWidget 发生变化之后,didChangeDependencies才会调用,所以 didChangeDependencies有可能会被调用多次。

这个函数会紧跟在initState之后调用,并且可以调用BuildContext.inheritFromWidgetOfExactType,那么BuildContext.inheritFromWidgetOfExactType的使用场景是什么呢?最经典的应用场景是

  • build

在 StatefulWidget 第一次创建的时候,build方法会在 didChangeDependencies方法之后立即调用,另外一种会调用 build方法的场景是,每当 UI 需要重新渲染的时候(setState触发),build都会被调用,所以 build会被多次调用,然后 返回要渲染的 Widget。千万不要在 build里做除了创建 Widget 之外的操作,因为这个会影响 UI 的渲染效率

  • addPostFrameCallback

addPostFrameCallback是 StatefulWidge 渲染结束的回调,只会被调用一次,之后 StatefulWidget 需要刷新 UI 也不会被调用,addPostFrameCallback的使用方法是在 initState里添加回调:

import 'package:flutter/scheduler.dart';
@override
void initState() {
  super.initState();
  SchedulerBinding.instance.addPostFrameCallback((_) => {});
}
  • didUpdateWidget 祖先节点rebuild widget时调用 .当组件的状态改变的时候就会调用didUpdateWidget.(可能会调用多次)

理论上setState的时候会调用,但我实际操作的时候发现只是做setState的操作的时候没有调用这个方法。而在我改变代码hot reload时候会调用 didUpdateWidget 并执行 build…

实际上这里flutter框架会创建一个新的Widget,绑定本State,并在这个函数中传递老的Widget。这个函数一般用于比较新、老Widget,看看哪些属性改变了,并对State做一些调整。

需要注意的是,涉及到controller的变更,需要在这个函数中移除老的controller的监听,并创建新controller的监听。

  • deactivate

当要将 State 对象从渲染树中移除的时候,就会调用 deactivate生命周期,这标志着 StatefulWidget 将要销毁,但是有时候 State 不会被销毁,而是重新插入到渲染树种。

  • dispose

当 View 不需要再显示,从渲染树中移除的时候,State 就会永久的从渲染树中移除,就会调用 dispose生命周期,这时候就可以在 dispose里做一些取消监听、动画的操作,和 initState是相反的。

如何监听FlutterApp是在前台还是后台?

AppLifecycleState 就是 App 的生命周期,有:

  • resumed
  • inactive
  • paused
  • suspending

如果想要知道 Flutter App 的生命周期,例如 Flutter 是在前台还是在后台,就需要使用到 WidgetsBindingObserver了,使用方法如下:

1.State 的类 mix WidgetsBindingObserver:

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
    ...
}

2.在 State 的 initState里添加监听:

@override
  void initState(){
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

3.在 State 的 dispose里移除监听:

 @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    WidgetsBinding.instance.removeObserver(this);
  }
  1. 在 State 里 override didChangeAppLifecycleState
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  super.didChangeAppLifecycleState(state);
  if (state == AppLifecycleState.paused) {
    // went to Background
  }
  if (state == AppLifecycleState.resumed) {
    // came back to Foreground
  }
}

Flutter如何与安卓或IOS通信?

Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:

BasicMessageChannel:用于传递字符串和半结构化的信息。 MethodChannel:用于传递方法调用。Flutter主动调用Native的方法,并获取相应的返回值。 EventChannel:用于数据流(event streams)的通信。

main()和runApp()函数在flutter的作用分别是什么?有什么关系吗?

  • main函数是类似于java语言的程序运行入口函数
  • runApp函数是渲染根widget树的函数
  • 一般情况下runApp函数会在main函数里执行

(提高)Hot Restart 和 Hot Reload 有什么区别吗?

Hot Reload比Hot Restart快,Hot Reload会编译我们文件里新加的代码并发送给dart虚拟机,dart会更新widgets来改变UI,而Hot Restart会让dart 虚拟机重新编译应用。另一方面也是因为这样, Hot Reload会保留之前的state,而Hot Restart回你重置所有的state回到初始值。

(提高)介绍下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。

有没有做过混合开发?混合开发单引擎和多引擎有什么区别。你感觉单引擎和引擎组哪种方式更好?

`FlutterEngineGroup` 和 `FlutterEngine`

混合开发中如何对aar库进行依赖的?

有没有改动过官方的dart库,改动过后怎么依赖的?有没有搭建过dart私服。

Expended组件使用时会有什么限制吗?

讲一下你在Flutter中经常使用的有哪些组件?

Flutter GestureDetector 手势检测类用过吗?有时候明明设置了GestureDetector,但是却并不是全部范围可点,只有中间部分有文案的地方能点击,请问是为什么如何解决?

如果child不为null 则默认为 HitTestBehavior.deferToChild 如果child为null则默认为HitTestBehavior.translucent
HitTestBehavior.deferToChild:只有当前容器中的child被点击时才会响应点击事件  
HitTestBehavior.opaque:点击整个区域都会响应点击事件,但是点击事件不可穿透向下传递,
注释翻译:阻止视觉上位于其后方的目标接收事件。  
HitTestBehavior.translucent:同样是点击整个区域都会响应点击事件,和opaque的区别是点击事件是否可以向下传递,
注释翻译:半透明目标既可以在其范围内接受事件,也可以允许视觉上位于其后方的目标接收事件

Flutter ListView如何滚动到指定position,列表滚动原生是怎样的,如何监听listview滚动到了哪个Item?假如Item是不固定高度的呢?

Flutter布局约束规则是什么样子的?子类想要多少就要多少吗?过程是怎么样的?

首先,上层 widget 向下层 widget 传递约束条件;  
然后,下层 widget 向上层 widget 传递大小信息。  
最后,上层 widget 决定下层 widget 的位置。
Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      minWidth: 70,
      minHeight: 70,
      maxWidth: 150,
      maxHeight: 150,
    ),
    child: Container(color: red, width: 100, height: 100),
  ),
)

布局约束

key (兄弟组件唯一)

如果两个小部件的runtimeType和key属性 分别为operator==,则新小部件通过更新底层元素(即,通过使用新小部件调用 Element.update 来替换旧小部件。否则,旧元素从树中移除,新的小部件被膨胀成一个元素,新元素被插入到树中。

使用 key 可以控制框架在 widget 重建时与哪些其他 widget 进行匹配。默认情况下,框架根据它们的 [`runtimeType`]
以及它们的显示顺序来匹配。使用 key 时,框架要求两个 widget 具有相同的和 `runtimeType`

Key 在构建相同类型 widget 的多个实例时很有用。例如,`ShoppingList` widget,
它只构建刚刚好足够的 `ShoppingListItem` 实例来填充其可见区域:

-   如果没有 key,当前构建中的第一个条目将始终与前一个构建中的第一个条目同步,在语义上,
列表中的第一个条目如果滚动出屏幕,那么它应该不会再在窗口中可见。
-   通过给列表中的每个条目分配为“语义” key,无限列表可以更高效,因为框架将通过相匹配的语义 key 来同步条目,
并因此具有相似(或相同)的可视外观。此外,语义上同步条目意味着在有状态子 widget 中,保留的状态将附加到相同的语义条目上,而不是附加到相同数字位置上的条目。

全局key

全局唯一

有没有尝试过用Flutter生成web项目,你觉得用flutter来取代前端,目前还有哪方面的问题?

初次加载太慢,首页需要加载很大的文件才能显示出来。

如何Flutter实现毛玻璃效果

//会对下层所有的子组件起过滤效果
Offstage(
  offstage: !widget.isBlur,
  child: BackdropFilter(
    filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3),
    child: Container(
      width: 200,
      height: 25,
      color: Colors.white24,
    ),
  ),
)

如果现在有一个单词列表页面,需要以混合开发的形式集成进两个App中。他们的文字内容,颜色,字体大小稍有差异。但界面整体一致。你会怎么实现文字,颜色,字体大小这个差异功能?

Flutter 中如何进行bug捕获与统计?如何发现线上问题? 自研还需要搭建平台?

flutter 异常捕获与上报

  1. dart同步异常用try catch捕获,异步异常用Flutter的catchError。
//使用try-catch捕获同步异常
try {
  throw StateError('This is a Dart exception');
}catch(e) {
  print(e);
}

  1. 异步异常如何捕获
//使用catchError捕获异步异常
Future.delayed(Duration(seconds: 1))
    .then((e) => throw StateError('This is a Dart exception in Future.'))
    .catchError((e)=>print(e));
  1. flutter组件异常用下面方式
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
  //自定义错误提示页面
  return Scaffold(
    body: Center(
      child: Text("Custom Error Widget"),
    )
  );
};
  1. 在没有使用try-catch、catchError语句的情况下,无论是同步异常还是异步异常,都可以使用Zone直接捕获到。 同时,如果需要集中捕获Flutter应用中未处理的异常,那么可以把main函数中的runApp语句也放置在Zone中,这样就可以在检测到代码运行异常时对捕获的异常信息进行统一处理.

对于Dart中出现的异常,同步异常使用的是try-catch,异步异常则使用的是catchError。如果想集中管理代码中的所有异常,那么可以Flutter提供的Zone.runZoned()方法。在Dart语言中,Zone表示一个代码执行的环境范围,其概念类似沙盒,不同沙盒之间是互相隔离的。如果想要处理沙盒中代码执行出现的异常,可以使用沙盒提供的onError回调函数来拦截那些在代码执行过程中未捕获的异常,如下所示。

//同步抛出异常
runZoned(() {
  throw StateError('This is a Dart exception.');
}, onError: (dynamic e, StackTrace stack) {
  print('Sync error caught by zone');
});

//异步抛出异常
runZoned(() {
  Future.delayed(Duration(seconds: 1))
      .then((e) => throw StateError('This is a Dart exception in Future.'));
}, onError: (dynamic e, StackTrace stack) {
  print('Async error aught by zone');
});
  1. 对于程序中出现的异常,通常只需要在Flutter应用程序的入口main.dart文件中,使用Flutter提供的FlutterError类集中处理即可,如下所示。
Future<Null> main() async {
  FlutterError.onError = (FlutterErrorDetails details) async {
    Zone.current.handleUncaughtError(details.exception, details.stack);
  };

  runZoned<Future<void>>(() async {
    runApp(MyApp());
  },  onError: (error, stackTrace) async {
    await _reportError(error, stackTrace);
  });
}

Future<Null> _reportError(dynamic error, dynamic stackTrace) async {
  print('catch error='+error);
}

debug情况下

Future<Null> main() async {
  FlutterError.onError = (FlutterErrorDetails details) async {
    if (isDebugMode) {
      FlutterError.dumpErrorToConsole(details);
    } else {
      Zone.current.handleUncaughtError(details.exception, details.stack);
    }
  };
   … //省略其他代码
}

bool get isDebugMode {
  bool inDebugMode = false;
  assert(inDebugMode = true);
  return inDebugMode;
}

Flutter多重嵌套对你代码开发来说有什么影响?查找问题时如何快速定位某一组件在代码中的位置?