未来可能要准备Flutter的面试机会,所以在网上找了一圈与Flutter相关的面试题,提前准备下。
Dart 语言综合了动态语言和静态语言的特性,动态语言指的是可以例如 dynamic 可以在运行用不同的类型数据给变量赋值,var 可以在运行时确定真正的数据类型,而不用在声明的时候就确定了将来要赋值的数据类型。同时,Dart 也是单线程的,跟 JS 类似,通过事件驱动模型来运行整个代码的执行过程。下面提出一些有意思的东西或者概念。
Dart 属于强类型语言,但是可以用 var 来声明变量,Dart 会自动推到出数据类型,var 实际上是编译期的“语法躺”。dynamic 表示动态类型,被编译后,实际上是一个 object 类型的,在编译期间不进行任何的类型检查,而是在运行期间进行类型检查。
Dart 中 数组和 List 是一样的。
Dart 中 number【num 类型】分为 int 和 double, 没有 float 类型。
Dart 中级连操作符可以方便配置逻辑,如下代码:
event
..id = 1
..name = ""
..actor = "";
赋值操作符,比较有意思的赋值操作符:
AA ?? "1000" //表示如果AA 为空 ,返回1000
AA ?? = "1000" //表示如果 AA 为空,给AA 设置成1000
AA ~/1000 //AA对1000整除
Dart 方法可以设置参数默认值和指定名称
比如:getRepositoryDetailDao(String userName, reposName, {branch = "master" }){} 方法,这里 branch 不设置的话,默认是是 "master"。参数类型可以指定也可以不指定。调用效果:getRepositoryDetailDao("bbb", "bbb", branch: "dev"); 。
Dart 没有关键字 public、private 等修饰,_ 下划线直接代表 private,但是有 @protected 注解。
Dart 中的多构造方法,可以通过命名方法实现。默认构造方法只能有一个,而通过 Model.empty() 方法可以创建一个空参数的类,其实方法名称随你喜欢,而变量初始化值时,只需要通过 ths.name 在构造方法中指定即可:
class ModelA {
String name;
String tag;
//默认构造方法,赋值给name和tag
ModelA(this.name, this.tag);
//返回一个空的ModelA
ModelA.empty();
//返回一个设置了name的ModelA
ModelA.forName(this.name);
}
Dart 中所有的基础类型、类等都继承 Object,默认值是 NULL,自带 getter 和 setter,而如果是 final 或者 const 的话,那么它只有一个 getter 方法,Oject 都支持 getter、setter 重写:
@override
Size get preferredSize {
return Size.fromHeight(kTabHeight + indicatorWeight);
}
assert 只在检查模式时有效,在开发过程中,assert(unicorn == null); 只有条件为真的时候才正常,否则直接抛出异常,一般用在开发过程中,某些地方不应该出现什么状态的判断。
如下面重载 operator 后对类进行 +/- 操作
class Vector{
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x+v.x, y+v.y);
Vector operator -(Vector v) => Vector(x-v.x, y-v.y);
@override
bool operator ==(Object o) {
if (!(o is Vector)){
return false;
}
Vector oo = o;
return x == oo.x && y == oo.y;
}
//重写 == 必须重写 hashCode
@override
int get hashCode => x+y;
}
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v+w == Vector(4,5));
assert(v-w == Vector(0,1));
支持重载的操作符
Dart 中没有接口,类都可以作为接口【使用 abstract 关键字】,把某个类当接口使用实现时,只需要使用 implements ,然后重写父类方法即可。Dart 中支持 mixins, 按照出现顺序应该为 extends, mixins, implements。
Dart 中可以通过 Zone 表示指定代码执行的环境,类似于一个沙盒概念。在 Flutter 中 C++ 运行Dart 也是在 _runMainZoned 内执行 runZoned 方法启动,而我们也可以通过 Zone,在运行环境内捕获全局异常等信息。
runZoned(() {
runApp(FlutterReduxApp());
}, onError: (Object obj, StackTrace stack) {
print(obj);
print(stack);
});
同时你可以给 runZoned 注册方法,在需要时执行回调,如下代码所示,这样的在一个 Zone 内任何地方,只要能够获取 onData 这个 ZoneUnaryCallback, 就都可以调用到 handleData
//zone.dart
ZoneUnaryCallback registerUnaryCallback(R callback(T arg));
//zoned.dart
import 'dart:async';
//最终需要处理的地方
handleData(result){
print("handleData VVVVVVVVVVVV");
print(result);
}
//返回得到一个ZoneUnaryCallback
var onData = Zone.current.registerUnaryCallback(handleData);
//zoned_page.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_widgets/zoned.dart';
class ZonedPage extends StatefulWidget {
const ZonedPage({Key key}) : super(key: key);
@override
_ZonedPageState createState() => _ZonedPageState();
}
class _ZonedPageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Zoned'),),
body: Container(
child: Center(
child: MaterialButton(
onPressed: (){
Zone.current.runUnary(onData, 2);
},
child: Text('点击发送消息给Zone处理'),
),
),
),
);
}
}
如上代码可以给我们以启发,如果我们在 App 中如果需要统一处理某件事件,那么就可以这样发送消息给 Zone 的 ZoneUnaryCallback 回调给处理。
异步逻辑可以通过 scheduleMicrotask 可以插入异步执行方法:
Zone.current.scheduleMicrotask((){
print("异步执行任务逻辑....");
});
思考:但是这种微任务又是干什么用的呢?和普通的事件又有什么区别呢??
Future 简单来说就是对 Zone 的封装使用。比如 Future.microtask 中主要是执行了 Zone 的 scheduleMicrotask,而 result._complete 最后调用的是 _zone.runUnary 等等。
factory Future.microtask(FutureOr computation()) {
_Future result = new _Future();
scheduleMicrotask(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
Dart 中可以通过 async /await 或者 Future 定义异步操作,而事实上 async/await 也只是语法糖,最终还是通过编译器转为 Future 。
Stream 也是对 Zone 的另外一种封装使用。Dart 中另外一种异步操作,async*/yield 或者 Stream 可定义 Stream 异步,async*/yield 也是一种语法糖,最终还是通过编译器转为 Stream、Stream 还支持同步操作。
一、 Stram 中主要有 Stream、StreamController、StreamSink 和 StreamSubscription 四个关键对象,大致可以总结为:
1. StreamController:如类名描述,用于整个 Stream 过程的控制,提供各类接口用于创建各种事件流。
2. StreamSink: 一般作为事件的接入口,如提供 add,addStream 等。
3. Stream:事件源本身,一般可用于监听事件或者对事件进行转换,如 listen、where。
4. StreamSubscription: 事件订阅后的对象,表面上用于管理订阅过等各种操作,如 cancel、pause, 同时在内部也是事件的中转关键。
二、一般通过 StreamController 创建 Stream;通过 StreamSink 添加事件;通过 Stream 监听事件;通过 StreamSubscription 管理订阅。
三、Stream 中支持各种变化,比如 map、expad、where、take等操作,同时支持转换为Futrue。
await for 的解释:
As every Dart programmer knows, the for-in loop plays well with iterables. Similarly, the await-for loop is designed to play well with streams. Given a stream, one can loop over its values: Every time an element is added to the stream, the loop body is run. After each iteration, the function enclosing the loop suspends until the next element is available or the stream is done. Just like await expressions, await-for loops can only appear inside asynchronous functions.
main(List args) async {
//await for 能够不断获取 stream 流中的数据,然后执行循环体中的操作
Stream stream = Stream.fromIterable(['Android','iOS','Flutter', 'Kotlin']);
print("开始准备循环...");
await for (String s in stream){
print(s);
}
print("循环结束了...");
}
输出结果:
开始准备循环...
Android
iOS
Flutter
Kotlin
循环结束了...
await for 和 listen 的作用很相似,都是获取流中数据然后输出,但是正如 await for 所示,如果 stream 没有传递完成,就会一直阻塞在这个位置上,上面循环结束了是最后输出的,下面给一个listen 的实例,一看就懂。
main(List args) {
print("循环开始了");
Stream stream = Stream.fromIterable(['Android', 'Kotlin', 'Flutter', 'iOS']);
stream.listen((event) { print(event);});
print("循环结束了");
}
输出结果:
循环开始了
循环结束了
Android
Kotlin
Flutter
iOS
所以 await for 一般都用在直到 stream 什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞,造成类似 Android ANR 的问题。
Flutter 和 React Native 不同主要在于 Flutter UI 是直接通过 skia 渲染的,而React Native 是将 js中的控件转化为原生控件,通过原生去渲染的。
Flutter 中存在 Widget、Element、RenderObject、Layer 四颗树,其中 Widget 与 Element 是一对多的关系。Widget 只是一种UI描述,并不具备任何的绘制能力。
Element 中持有 Widget 和 RenderObject,而 Element 与 RenderObject 是一对一的关系【除去 Element 不存在 RenderObject 的情况,如 Component 是不具备 RenderObject】
当 RenderObject 的 isRepaintBoundry 为 true 时,那么这个区域形成一个Layer, 所以不是每个RenderObject 都具备 Layer 的,因为这受 isRepaintBoundry 的影响。
Flutter 中 Widget 是不可变,每次保持一帧,如果发生改变是通过 State 实现跨帧状态保存,而真实完成布局和绘制数据的 RenderObject, Element 充当两者的桥梁,State 就是保存在 Element中。【跨帧状态保存的意思就是在不同帧变化的时候保留数据】
Flutter 中的 BuildContext 只是接口,而 Element 实现了它。
Flutter 中 setState 其实是调用了 markNeedsBuild, 该方法内部标记此 Element 为 dirty, 然后在下一帧 WidgetBinding.drawFrame 才会被绘制【需要等待Vsync 信号】,这可以看出 setState 并不是立即生效的。
Flutter 中 RenderObject 在 attach /layout 之后会通过 markNeedsPaint(); 使得页面重绘,流程大概如下:
通过 isRepaintBoundry 往上确定了更新区域,通过 requestVisualUpdate方法触发更新往下绘制。正常情况 RenderObject 的布局相关方法调用顺序是:layout--->performReszie-->performLayout----> markNeedsPaint,但是用户一般不会直接调用 layout,而是通过 markNeedsLayout, 具体流程如下:
Flutter 中一般 json 数据从 String 转为 Oject 的过程都需要先经过 Map 类型。
Flutter 中 InheritedWidget 一般用于状态共享,如 Theme、Localizations、MediaQuery 等,都是通过它实现共享状态,这样我们就可以通过 context 去获取共享的状态,比如 ThemeData theme = Theme.of(context);
在 Element 的 inheritFromWidgetOfExactType 方法实现里,有一个 Map
_inheritedWidgets 的对象。 _inheritedWidgets 一般情况下是空的,只有当父控件是 InheritedWidget 或者本身是 InheritedWidgets 时才会有被初始化,而当父控件是 InheritedWidget 时,这个 Map 会被一级一级往下传递与合并 。
所以当我们通过 context 调用 inheritFromWidgetOfExactType 时,就可以往上查找到父控件的 Widget 。
Flutter 中默认主要通过 runtimeType 和 key判断更新:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
Flutter 中的生命周期
1. initState()
表示当前 State 将和一个 BuildContext 产生关联,但是此时BuildContext 没有完全装载完成,如果你需要在该方法中获取 BuildContext, 可以 new Future.delayed(const Duration(seconds:0, (){})); 一下。更准确说现在应该是用 ScheduleBinding 。
2. didChangeDependencies()
didChangeDependencies() 在 initState() 之后调用,当 State 对象的依赖关系发生变化时,该方法被调用,初始化时也会调用。【State对象的依赖关系发生变化时是指的 Widget 发生变化?】
3. deactive()
deactive() 当 State 被暂时从视图树中移除时,会调用这个方法,同时页面切换时,也会调用【新版本中已经消失】。
4. dispose()
dispose() Widget 销毁了,在调用这个方法之前,总会调用 deactive()。
5. didUpdateWidget()
didUpdateWidget 当 Widget 状态发生变化时,会调用。
通过 StreamBuilder 和 FutureBuilder 我们可以快速使用 Stream 和 Future 快速构建我们的异步控件。
Flutter 中 runApp 启动入口其实是一个 WidgetsFlutterBinding,它主要是通过 BindingBase 的子类GestureBinding、ServiceBinding、ScheduleBinding、PaintingBinding、SemanticsBinding、RenderBinding、WidgetsBinding 等,通过 mixins 的组合而成的。
Flutter 中的 Dart 的线程是以事件循环和消息队列的形式存在,包含两个任务队列,一个是 microtask 内部队列,一个是 event 外部队列,而 microtask 的优先级又高于 event 。
因为 microtask 的优先级又高于 event,同时会阻塞 event 队列,所以如果 microtask 太多就可能会对触摸、绘制等外部事件造成阻塞卡顿。
思考:那么哪些算是 microtask ,哪些是 event 呢?
Flutter 中存在四大线程,分别是 UI Runner、GPU Runner、IO Runner 、Platform Runnber (原生主线程),同时在Flutter 中可以通过 isolate 或者 compute 执行真正的跨线程异步操作。
Flutter 中可以通过 PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了Presentation + VirtualDisplay + Surface 等实现的,大致原理就是:
使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManager 的createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surface 的id 通知给 Dart , 让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来。实时控件截图渲染显示技术。
Flutter 的 Debug 下是 JIT 模式,release 下是AOT模式。【JIT是每次运行的时候编译成机器码,AOT是在安装的时候就编译成机器码】
Flutter 中可以通过 mixins AutomaticKeepAliveClientMixin,然后重写 wantKeepAlive 保持住页面,记得在被保持的页面 build 中调用 super.build 。(因为 mixins 特性)
Flutter 手势事件主要是通过竞技判断的:
主要有 hitTest 把所有需要处理的控件对应的 RenderObject,从 child 到 parent 全部组合成列表,从最里面一直添加到最外层。然后从队列头到 child 开始 for 循环执行 handleEvent 方法,执行 handleEvent 的过程不会被拦截打断。
一般情况下 Down 事件不会决出胜利者,大部分时候是在 MOVE 或者 UP 的时候才会决出胜利者。竞技场关闭时只有一个的就直接胜出响应,没有胜利者就拿排在队列第一个强制胜利响应。
同时还有 didExceedDeadline 处理按住时的 Down 事件额外处理,同时手势处理一般在 GestureRecognizer 的子类进行。
Flutter 中 ListView 滑动其实都是通过 ViewPort 中的 child 布局来实现显示的。
常见状态管理的:目前有 scope_model、flutter_redux、fish_redux、bloc+Stream 等几种模式。
Flutter 中可以通过 Platform Channel 让 Dart 代码和原生代码通信的:
1. BasicMessageChannel: 用于传递字符串和半结构化的信息。
2. MethodChannel : 用于传递方法调用(method invocation)
3. EventChannel: 用于数据流(event streams)的通信。
同时 Platform Channel 并非是线程安全的,更多详细可查阅咸鱼技术的
其中基础数据类型映射如下:
Android 中 Flutter 默认启动时会在 FlutterActivityDelegate.java 中读取 AndroidManifest.xml 内meta-data 标签,其中 io.flutter.app.android.SplashScreenUntilFirstFrame 标志位如果为 true,就会启动 Splash 画面效果(类似iOS 的启动页面)。
启动时原生代码会读取 android.R.attr.windowBackground 得到指定的 Drawable,用于显示启动闪屏效果,之后并且通过 flutterView.addFirstFrameListener,在 onFirstFrame 中移除闪屏。
参考:
可以算是Flutter面试凉凉经吧 - 掘金
Flutter 面试知识点集锦 - 掘金