Flutter面试宝典2021-持续更新

1:Flutter中的生命周期

  • initState():Widget 初始化当前 State,在当前方法中是不能获取到 Context 的,如想获取,可以试试 Future.delayed()
  • didChangeDependencies():在 initState() 后调用,State对象依赖关系发生变化的时候也会调用。
  • deactivate():当 State 被暂时从视图树中移除时会调用这个方法,页面切换时也会调用该方法,和Android里的 onPause 差不多。
  • dispose():Widget 销毁时调用。
  • didUpdateWidget:Widget 状态发生变化的时候调用。

2:Dart是不是单线程模型?是如何运行的?

Dart 是单线程模型,运行的的流程如下图。


简单来说,Dart 在单线程中是以消息循环机制来运行的,包含两个任务队列,一个是“微任务队列” MicroTask Queue,另一个叫做“事件队列” Event Queue。
当Flutter应用启动后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,当所有微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务,如此循环往复,生生不息。
在 Dart 每一个 isolate 当中,执行优先级为 : Main > MicroTask > EventQueue

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

Dart 是单线程的,不存在多线程,那如何进行多任务并行的呢?其实,Dart的多线程和前端的多线程有很多的相似之处。Flutter的多线程主要依赖Dart的并发编程、异步和事件驱动机制。


简单的说,在Dart中,一个Isolate对象其实就是一个isolate执行环境的引用,一般来说我们都是通过当前的isolate去控制其他的isolate完成彼此之间的交互,而当我们想要创建一个新的Isolate可以使用Isolate.spawn方法获取返回的一个新的isolate对象,两个isolate之间使用SendPort相互发送消息,而isolate中也存在了一个与之对应的ReceivePort接受消息用来处理,但是我们需要注意的是,ReceivePort和SendPort在每个isolate都有一对,只有同一个isolate中的ReceivePort才能接受到当前类的SendPort发送的消息并且处理。

4:Future、async 和 await

Future 是异步编程的解决方案,Future 是基于观察者模式的,它有 3 种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)可以用 then 方法指定成功状态的回调函数 then 方法还可以接受一个可选命名参数,参数的名称是 onError,即失败状态的回调函数 Future 实例的函数中抛出了异常,被 onError 回调函数捕获到,并且可以看出 then 方法返回的还是一个 Future 对象,所以其实我们还可以利用 Future 对象的 cathError 进行链式调用从而捕获异常

在Dart1.9中加入了async和await关键字,有了这两个关键字,我们可以更简洁的编写异步代码,而不需要调用Future相关的API
将 async 关键字作为方法声明的后缀时,具有如下意义

  • 被修饰的方法会将一个 Future 对象作为返回值
  • 该方法会同步执行其中的方法的代码直到第一个 await 关键字,然后它暂停该方法其他部分的执行;
  • 一旦由 await 关键字引用的 Future 任务执行完成,await的下一行代码将立即执行。

async 不是并行执行,它是遵循Dart 事件循环规则来执行的,它仅仅是一个语法糖,简化Future API的使用。

5:Dart异步编程中的 Stream数据流?

在Dart中,Stream 和 Future 一样,都是用来处理异步编程的工具。它们的区别在于,Stream 可以接收多个异步结果,而Future 只有一个。
Stream 的创建可以使用 Stream.fromFuture,也可以使用 StreamController 来创建和控制。还有一个注意点是:普通的 Stream 只可以有一个订阅者,如果想要多订阅的话,要使用 asBroadcastStream()。

6:Stream 有哪两种订阅模式?分别是怎么调用的?

Stream有两种订阅模式:单订阅(single) 和 多订阅(broadcast)。单订阅就是只能有一个订阅者,而广播是可以有多个订阅者。这就有点类似于消息服务(Message Service)的处理模式。单订阅类似于点对点,在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。而广播类似于发布订阅模式,可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。

Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。但 Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。通过 Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

7:Flutter如何与Android、iOS通信?

Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:
BasicMessageChannel :用于传递字符串和半结构化的信息。
MethodChannel :用于传递方法调用(method invocation)。
EventChannel : 用于数据流(event streams)的通信。
同时 Platform Channel 并非是线程安全的 ,更多详细可查阅闲鱼技术的 《深入理解Flutter Platform Channel》

8:Flutter中嵌套原生View的方法?

你的类 extends PlatformView
你的类 extends PlatformViewFactory
之后再通过registerViewFactory注册你的View,并设置一个ID。
我们在Flutter端,创建一个AndroidView,并通过上面的ID就可以获取到我们创建的androidView,并使用。

9:Widgets、RenderObjects 和 Elements的关系

首先看一下这几个对象的含义及作用。

  • Widget :仅用于存储渲染所需要的信息。
  • RenderObject :负责管理布局、绘制等操作。
  • Element :才是这颗巨大的控件树上的实体。

Widget会被inflate(填充)到Element,并由Element管理底层渲染树。Widget并不会直接管理状态及渲染,而是通过State这个对象来管理状态。Flutter创建Element的可见树,相对于Widget来说,是可变的,通常界面开发中,我们不用直接操作Element,而是由框架层实现内部逻辑。就如一个UI视图树中,可能包含有多个TextWidget(Widget被使用多次),但是放在内部视图树的视角,这些TextWidget都是填充到一个个独立的Element中。Element会持有renderObject和widget的实例。记住,Widget 只是一个配置,RenderObject 负责管理布局、绘制等操作。在第一次创建 Widget 的时候,会对应创建一个 Element, 然后将该元素插入树中。如果之后 Widget 发生了变化,则将其与旧的 Widget 进行比较,并且相应地更新 Element。重要的是,Element 不会被重建,只是更新而已。

10:Flutter中如何自定义Widget?

首先自定义Widget有两种方式,分别是组合控件和绘制
这里只讲CustomPainter 大致流程

  • 新建类继承于CustomPainter实现paint()和shouldRepaint()方法
  • 在paint方法中绘制你想要的内容
  • 借助于 CustomPaint Widget来构建自己的Widget

shouldRepaint()控制绘制的重绘,主要用于动画、刷新等功能

11:如何获取Widget的尺寸?

想要获取widget的尺寸,必须要等widget的layout结束之后才能取到,目前有三种方式

  • 通过BuildContext获取
 var size = context?.findRenderObject()?.paintBounds?.size;

过早获取可能为空,需要延迟获取。

  • 通过GlobalKey获取
GlobalKey _globalKey = GlobalKey();
 var size = _globalKey.currentContext
                  ?.findRenderObject()
                  ?.paintBounds
                  ?.size;
  • 通过SizeChangedLayoutNotifier获取

使用SizeChangedLayoutNotifier方式,Widget会在layout结束之后会发出一个LayoutChangedNotification通知,我们只需要接收这个通知,即可获取尺寸信息

12:Flutter中进行性能监控和如何进行优化?

  • 性能图层(Performance Overlay)
  • Profile
  • DevTools

13:Flutter中的手势事件传递、事件分发、事件冲突竞争,滑动流畅等等

  • AbsorbPointer 是吸收拦截,absorbing 为true时,拦截下面的监听。
  • IgnorePointer 是忽略拦截,为true时,周围控件都拦截,但是stack布局后面的可以得到监听事件。
  • 每个控件都可以加 Listener 监听,里面的方法可以实现手势,用controller滑动,从而达到拦截等操作。
  • GestureDetector 经常用来添加点击,双击,滑动,长按等操作,其实也可以定义拦截,滑动等事件。但是其他控件响应了,会直接cancel掉。

14:什么是Key?什么时候需要Key?Key的种类有哪些?

key的作用是:控制weidget树上的widget是否被替换(刷新)

  • 当你想要跨widget树保留状态时 , 应该使用key.
  • 当修改相同类型的widget集合时, 要将key放在要保留的widget的树的顶部.
  • 并且要根据widget中存储的数据类型选择对应的key.

KEY的分类

  • LocalKey 在具有相同父元素的Elements中,键必须唯一。
  • GlobalKey 在整个APP中唯一的键

GlobalKey有两种用途,它允许widget在App的任何位置更改父级而不会丢失状态,或者可以使用它们在Widget Tree完全不同的部分中访问有关另一个widget的信息.
比如要在两个不同的屏幕显示相同的widget,并保持所有相同的状态, 此时可以使用GlobalKey.
而LocalKey又有以下几个子类:

  • ValueKey 使用特定类型的值标识自己的键。
  • ObjectKey 从对象中获取其标识的键用作其值。
  • UniqueKey 仅等于自身的Key。
  • PageStorageKey ValueKey的子类, 它定义了PageStorage值的保存位置。

PageStorageKey是用来存储用户滚动位置的专用key,因此APP可以保留它以供后面使用.
我们要注意3点:
1.在具有相同父元素的Elements中,键必须唯一。相比之下,GlobalKeys在整个应用程序中必须唯一。
2.Key的子类应该是LocalKey或GlobalKey的子类.
3.GlobalKey更为昂贵,因此如果没有必要,请使用ValueKey, ObjectKey, 或者 UniqueKey.

15:什么是状态管理,你了解哪些状态管理框架?

首先状态其实是一个概念上的东西,区分全局状态和局部状态。
局部状态比如说一个控件中输入的信息,全局状态比如是登陆后从后台请求回来的 userId。
当全局状态越来越多,多个页面共享一个状态时,我们就需要管理它。
常用的状态管理有BLoC、Provider

16:Flutter中的动画

Animation

在Flutter中,实现动画的核心类是Animation,Widget可以直接将这些动画合并到自己的build方法中来读取他们当前值或者监听它们的状态变化。

  • addListener方法
    当动画的状态发生变化时,会通知所有监听器,通常,一个正在监听动画的state对象会调用自身的setState方法,将自身传入这些监听器的回调函数来通知 widget 系统需要根据新状态值进行重新构建。
  • addStatusListener
    通常情况下,动画会从 dismissed 状态开始,表示它处于变化区间的开始点。动画进行的下一状态可能是 forward(比如从 0.0 到 1.0)或者 reverse(比如从 1.0 到 0.0)。最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成 completed 状态。
AnimationController

Animation是一个抽象类,并不能用来直接创建对象实现动画的使用。
AnimationController是Animation的一个子类,实现动画通常我们需要创建AnimationController对象。
AnimationController会生成一系列的值,默认情况下值是0.0到1.0区间的值,除了上面的监听,获取动画的状态、值之外,AnimationController还提供了对动画的控制:

  • forward:向前执行动画
  • reverse:方向播放动画
  • stop:停止动画

AnimationController有一个必传的参数vsync,这里是为了监听vsync信号,当Flutter开发的应用程序不再接受同步信号时(比如锁屏或退到后台),那么继续执行动画会消耗性能。

CurvedAnimation

CurvedAnimation也是Animation的一个实现类,它的目的是为了给AnimationController增加动画曲线:

Tween

默认情况下,AnimationController动画生成的值所在区间是0.0到1.0,如果希望使用这个以外的值,或者其他的数据类型,就需要使用Tween。

class Tween extends Animatable {
  Tween({ this.begin, this.end });
}

Tween也有一些子类,比如ColorTween、BorderTween,可以针对动画或者边框来设置动画的值。
要使用Tween对象,需要调用Tween的animate()方法,传入一个Animation对象。

AnimatedWidget

17:mixin机制?

mixin 是Dart 2.1 加入的特性,以前版本通常使用abstract class代替。简单来说,mixin是为了解决继承方面的问题而引入的机制,Dart为了支持多重继承,引入了mixin关键字,它最大的特殊处在于:mixin定义的类不能有构造方法,这样可以避免继承多个类而产生的父类构造方法冲突。mixins的对象是类,mixins绝不是继承,也不是接口,而是一种全新的特性,可以mixins多个类,mixins的使用需要满足一定条件。

18:说一下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。

你可能感兴趣的:(Flutter面试宝典2021-持续更新)