Dart部分
1. Dart 语言的特性?
· Productive(生产力高,Dart的语法清晰明了,工具简单但功能强大)
· Fast(执行速度快,Dart提供提前优化编译,以在移动设备和Web上获得可预测的高性能和快速启动。)
· Portable(易于移植,Dart可编译成ARM和X86代码,这样Dart移动应用程序可以在iOS、Android和其他地方运行)
· Approachable(容易上手,充分吸收了高级语言特性,如果你已经知道C++,C语言,或者Java,你可以在短短几天内用Dart来开发)
· Reactive(响应式编程)
2.Dart的一些重要概念?
· 在Dart中,一切都是对象,所有的对象都是继承自Object
· Dart是强类型语言,但可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型,dynamic类似c#
· 没有赋初值的变量都会有默认值null
· Dart支持顶层方法,如main方法,可以在方法内部创建方法
· Dart支持顶层变量,也支持类变量或对象变量
· Dart没有public protected private等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的
3.Dart 当中的 「..」表示什么意思?
· Dart 当中的 「..」意思是 「级联操作符」,为了方便配置而使用。
· 「..」和「.」不同的是 调用「..」后返回的相当于是 this,而「.」返回的则是该方法返回的值 。
· 2. Dart 的作用域
· Dart 没有 「public」「private」等关键字,默认就是公开的,私有变量使用 下划线 _开头。
· 3. Dart 是不是单线程模型?是如何运行的?
· Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。
· 入口函数 main() 执行完后,消息循环机制便启动了。首先会按照先进先出的顺序逐个执行微任务队列中的任务,当所有微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务,如此循环往复,生生不息。
4. Dart 多任务如何并行的?
· 刚才也说了,既然 Dart 不存在多线程,那如何进行多任务并行?
· Dart 当中提供了一个 类似于新线程,但是不共享内存的独立运行的 worker - isolate。
· 那他们是如何交互的?
· 在dart中,一个Isolate对象其实就是一个isolate执行环境的引用,一般来说我们都是通过当前的isolate去控制其他的isolate完成彼此之间的交互,而当我们想要创建一个新的Isolate可以使用Isolate.spawn方法获取返回的一个新的isolate对象,两个isolate之间使用SendPort相互发送消息,而isolate中也存在了一个与之对应的ReceivePort接受消息用来处理,但是我们需要注意的是,ReceivePort和SendPort在每个isolate都有一对,只有同一个isolate中的ReceivePort才能接受到当前类的SendPort发送的消息并且处理。
5.dart是值传递还是引用传递?
dart是值传递。我们每次调用函数,传递过去的都是对象的内存地址,而不是这个对象的复制。 先来看段代码
main(){
Test a = new Test(5);
print("a的初始值为:${a.value}");
setValue(a);
print("修改后a的值为: ${a.value}");
}
class Test{
int value = 1;
Test(int newValue){
this.value = newValue;
}
}
setValue(Test s){
print("修改value为100");
s.value = 100;
}
输出结果为:
a的初始值为:5
修改value为100
修改后a的值为:100
从这里可以看出是值传递,如果只是复制了一个对象,然后把这个新建的对象地址传递到函数里面的话,setValue()函数中的修改是不会影响到main函数中的a的,因为二者所引用的内存地址是不一样。
有些人可能会以以下代码反驳我:
main(){
int s = 6;
setValue(s);
print(s); //输出6,而不是7
}
class Test{
int value = 1;
Test(int newValue){
this.value = newValue;
}
}
setValue(int s){
s += 1;
}
你看,这输出的不是6吗,在dart中一切皆为对象,如果是引用传递,那为什么是6啊。
答案是这样的,在setValue()方法中,参数s实际上和我们初始化int s = 6的s不是一个对象,只是他们现在指的是同一块内存区域,然后在setValue()中调用s += 1的时候,这块内存区域的对象执行+1操作,然后在堆(类比java)中产生了一个新的对象,s再指向这个对象。所以s参数只是把main函数中的s的内存地址复制过去了,就比如java中的:
public class Test {
public static void main(String[] args) {
• Test a = new Test();
• Test b = a;
• b = new Test();
}
}
我们只要记住一点,参数是把内存地址传过去了,如果对这个内存地址上的对象修改,那么其他位置的引用该内存地址的变量值也会修改。千万要记住dart中一切都是对象。
6. Dart 属于是强类型语言 ,但可以用 var 来声明变量,Dart 会自推导出数据类型,var 实际上是编译期的“语法糖”。dynamic 表示动态类型, 被编译后,实际是一个 object 类型,在编译期间不进行任何的类型检查,而是在运行期进行类型检查。
7. Dart 中 if 等语句只支持 bool 类型,switch 支持 String 类型。
8.Dart 中数组和 List 是一样的。
9.Dart 中,Runes 代表符号文字 , 是 UTF-32 编码的字符串, 用于如 Runes input = new Runes('\u{1f596} \u{1f44d}');
10.Dart 支持闭包
11.Dart 中 number 类型分为 int 和 double ,没有 float 类型。
13.Dart 中 级联操作符 可以方便配置逻辑,如下代码:
event
..id = 1
..type = ""
..actor = "";
14. 说一下 Future?
· Future,字面意思「未来」,是用来处理异步的工具。
· 刚才也说过:
· Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。
· Future 默认情况下其实就是往「事件队列」里插入一个事件,当有空余时间的时候就去执行,当执行完毕后会回调 Future.then(v) 方法。
· 而我们也可以通过使用 Future.microtask 方法来向 「微任务队列」中插入一个任务,这样就会提高他执行的效率。
· 因为在 Dart 每一个 isolate 当中,执行优先级为 : Main > MicroTask > EventQueue
15. 说一下 Stream?
· Stream 和 Feature 一样,都是用来处理异步的工具。
· 但是 Stream 和 Feature 不同的地方是 Stream 可以接收多个异步结果,而Feature 只有一个。
· Stream 的创建可以使用 Stream.fromFuture,也可以使用 StreamController 来创建和控制。
· 还有一个注意点是:普通的 Stream 只可以有一个订阅者,如果想要多订阅的话,要使用 asBroadcastStream()。
16. 说一下 mixin?
· 关于什么是 mixin,引用 张风捷特烈 文章中的:
· 首先mixin是一个定义类的关键字。直译出来是混入,混合的意思 Dart为了支持多重继承,引入了mixin关键字,它最大的特殊处在于: mixin定义的类不能有构造方法,这样可以避免继承多个类而产生的父类构造方法冲突
·
16.Widget和element和RenderObject之间的关系
首先我详细说下当时的情景,面试官问我Widget和Element之间是不是一对多的关系,如果是增加一个Widget之后,这个关系又是什么。 这部分还是没有很好地答案,现在只是一个猜想,如果添加了一个widget,Element树遍历后面所有的Element看类型是否发生改变,有的话再重建RenderObject。Element和Widget之间应该还是一对一的关系,因为每个Widget的context都是独一无二的。等想好了再写上去吧。
17. widget树的root节点
还是没能理解面试官的意思。。有能够理解的同学请评论告知我一下。 现在理解了,面试官的意思应该指是runApp()方法中的那个的Widget。我当时也想说的,不过忘记这个方法名是啥了。。。
18.mixin extends implement之间的关系
· 继承(关键字 extends)
Flutter中的继承是单继承,构造函数不能继承,子类重写超类的方法,要用 @override 子类调用超类的方法,要用super Flutter中的子类可以访问父类中的所有变量和方法,因为Flutter中没有公有、私有的区别
·
混入 mixins (关键字 with) mixins Dart赋予的多继承特性,类似C#的多继承,而Java是不支持类的多继承 混合的对象是类,可以混合多个
·
·
接口实现(关键字 implements)
·
Flutter是没有interface的,但是Flutter中的每个类都是一个隐式的接口,这个接口包含类里的所有成员变量,以及定义的方法。 当class被当做interface用时,class中的成员变量也需要在子类里重新实现。在成员变量前加 @override
这三种关系可以同时存在,但是有前后顺序: extends -> mixins -> implements
19.Future和microtask执行顺序
MricroTask
Dart 中事件机制的实现 :Main isolate 中有一个Looper,但存在两个Queue:Event Queue 和 Microtask Queue 。
Dart 中事件的执行顺序:Main > MicroTask > EventQueue。
在 Main 中写代码将最先执行; 执行完 Main 中的代码,然后会检查并执行 Microtask Queue 中的任务, 通常使用 scheduleMicrotask 将事件添加到 MicroTask Queue 中; 最后执行 EventQueue 队列中的代码,通常使用 Future 向 EventQueue加入时间,也可以使用 async 和 await 向 EventQueue 加入事件。
Future提供链式调用
new Future (() => print('拆分任务_1'))
.then((i) => print('拆分任务_2'))
.then((i) => print('拆分任务_3'))
.whenComplete(()=>print('任务完成'));
多Future 和 多micTask 的执行顺序
void testScheduleMicrotatsk() {
scheduleMicrotask(() => print('Mission_1'));
//注释1
new Future.delayed(new Duration(seconds: 1), () => print('Mission_2'));
//注释2
new Future(() => print('Mission_3')).then((_) {
print('Mission_4');
scheduleMicrotask(() => print('Mission_5'));
}).then((_) => print('Mission_6'));
//注释3
new Future(() => print('Mission_7'))
.then((_) => new Future(() => print('Mission_8')))
.then((_) => print('Mission_9'));
//注释4
new Future(() => print('Mission_10'));
scheduleMicrotask(() => print('Mission_11'));
print('Mission_12');
}
输出结果
I/flutter (19025): Mission_12
I/flutter (19025): Mission_1
I/flutter (19025): Mission_11
I/flutter (19025): Mission_3
I/flutter (19025): Mission_4
I/flutter (19025): Mission_6
I/flutter (19025): Mission_5
I/flutter (19025): Mission_7
I/flutter (19025): Mission_10
I/flutter (19025): Mission_8
I/flutter (19025): Mission_9
Syncing files to device MIX 3...
I/flutter (19025): Mission_2
19.await for的使用方式
代码如下
String data;
getData() async {
data = await http.get(Uri.encodeFull(url), headers: {"Accept": "application/json"}); //延迟执行后赋值给data
}
· await关键字必须在async函数内部使用
· 调用async函数必须使用await关键字
· 返回默认是一个future对象
//定义了返回结果值为String类型
Future<String> getDatas(String category) async {
var request = await _httpClient.getUrl(Uri.parse(url));
var response = await request.close();
return await response.transform(utf8.decoder).join();
}
run() async{
int data = await getDatas('keji'); //因为类型不匹配,IDE会报错
}
Furture的作用是为了链式调用
//案例3
funA(){
...set an important variable... //设置变量
}
funB(){
...use the important variable... //使用变量
}
main(){
new Future.then(funA()).then(funB()); // 明确表现出了后者依赖前者设置的变量值
new Future.then(funA()).then((_) {new Future(funB())}); //还可以这样用
//链式调用,捕获异常
new Future.then(funA(),onError: (e) { handleError(e); }).then(funB(),onError: (e) { handleError(e); })
}
20.赋值操作符
比较有意思的赋值操作符有:
AA ?? "999" ///表示如果 AA 为空,返回999
AA ??= "999" ///表示如果 AA 为空,给 AA 设置成 999
AA ~/999 ///AA 对于 999 整除
21.可选方法参数
Dart 方法可以设置 参数默认值 和 指定名称 。
比如: getDetail(Sting userName, reposName, {branch = "master"}){} 方法,这里 branch 不设置的话,默认是 “master” 。参数类型 可以指定或者不指定。调用效果: getRepositoryDetailDao(“aaa", "bbbb", branch: "dev"); 。
22.作用域
Dart 没有关键词 public 、 private 等修饰符, _ 下横向直接代表 private ,但是有 @protected 注解 。
23.构造方法
Dart 中的多构造方法,可以通过命名方法实现。
默认构造方法只能有一个,而通过 Model.empty() 方法可以创建一个空参数的类,其实方法名称随你喜欢,而变量初始化值时,只需要通过 this.name 在构造方法中指定即可:
class ModelA {
String name;
String tag;
//默认构造方法,赋值给name和tag
ModelA(this.name, this.tag);
//返回一个空的ModelA
ModelA.empty();
//返回一个设置了name的ModelA
ModelA.forName(this.name);
}
24.getter setter 重写
Dart 中所有的基础类型、类等都继承 Object ,默认值是 NULL, 自带 getter 和 setter ,而如果是 final 或者 const 的话,那么它只有一个 getter 方法,Object 都支持 getter、setter 重写:
@override
Size get preferredSize {
return Size.fromHeight(kTabHeight + indicatorWeight);
}
25.Assert(断言)
assert 只在检查模式有效,在开发过程中,assert(unicorn == null); 只有条件为真才正常,否则直接抛出异常,一般用在开发过程中,某些地方不应该出现什么状态的判断。
26.重写运算符,如下所示重载 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);
···
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
支持重载的操作符 :
类、接口、继承
Dart 中没有接口,类都可以作为接口,把某个类当做接口实现时,只需要使用 implements ,然后复写父类方法即可。
Dart 中支持 mixins ,按照出现顺序应该为extends 、 mixins 、implements 。
Zone
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
///最终需要处理的地方
handleData(result) {
print("VVVVVVVVVVVVVVVVVVVVVVVVVVV");
print(result);
}
///返回得到一个 ZoneUnaryCallback
var onData = Zone.current.registerUnaryCallback<dynamic, int>(handleData);
///执行 ZoneUnaryCallback 返回数据
Zone.current.runUnary(onData, 2);
异步逻辑可以通过 scheduleMicrotask 可以插入异步执行方法:
Zone.current.scheduleMicrotask((){
//todo something
});
更多可参看 :《Flutter完整开发实战详解(十一、全面深入理解Stream)》
Future
Future 简单了说就是对 Zone 的封装使用。
比如 Future.microtask 中主要是执行了 Zone 的 scheduleMicrotask ,而 result._complete 最后调用的是 _zone.runUnary 等等。
factory Future.microtask(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
scheduleMicrotask(() {
try {
• result._complete(computation());
} catch (e, s) {
• _completeWithErrorCallback(result, e, s);
}
});
return result;
}
Dart 中可通过 async / await 或者 Future 定义异步操作,而事实上 async / await 也只是语法糖,最终还是通过编译器转为 Future 。
有兴趣看这里 :
Flutter完整开发实战详解(十一、全面深入理解Stream)
Stream
Stream 也是有对Zone 的另外一种封装使用。
Dart 中另外一种异步操作, async* / yield 或者 Stream 可定义 Stream 异步, async* / yield 也只是语法糖,最终还是通过编译器转为 Stream 。Stream 还支持同步操作。
1)、Stream 中主要有 Stream 、 StreamController 、StreamSink 和 StreamSubscription 四个关键对象,大致可以总结为:
StreamController :如类名描述,用于整个 Stream 过程的控制,提供各类接口用于创建各种事件流。
StreamSink :一般作为事件的入口,提供如 add , addStream 等。
Stream :事件源本身,一般可用于监听事件或者对事件进行转换,如 listen 、where 。
StreamSubscription :事件订阅后的对象,表面上用于管理订阅过等各类操作,如 cacenl 、pause ,同时在内部也是事件的中转关键。
2)、一般通过 StreamController 创建 Stream ;通过 StreamSink 添加事件;通过 Stream 监听事件;通过 StreamSubscription 管理订阅。
3)、Stream 中支持各种变化,比如map 、expand 、where 、take 等操作,同时支持转换为 Future 。
更多可参看 :《Flutter完整开发实战详解(十一、全面深入理解Stream)》
Flutter 部分
1. Flutter 是什么
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。
2. Flutter 特性有哪些?
快速开发(毫秒级热重载)
· 绚丽UI(内建漂亮的质感设计Material Design和Cupertino Widget和丰富平滑的动画效果和平台感知)
· 响应式(Reactive,用强大而灵活的API解决2D、动画、手势、效果等难题)
· 原生访问功能
· 堪比原生性能
3.基础知识
Flutter 和 React Native 不同主要在于 Flutter UI是直接通过 skia 渲染的 ,而 React Native 是将 js 中的控件转化为原生控件,通过原生去渲染的 ,相关更多可查看:《移动端跨平台开发的深度解析》。
Flutter 中存在 Widget 、 Element 、RenderObject 、Layer 四棵树,其中 Widget 与 Element 是一对多的关系 ,
Element 中持有 Widget 和 RenderObject , 而 Element 与 RenderObject 是一一对应的关系(除去 Element 不存在 RenderObject 的情况,如 ComponentElement 是不具备 RenderObject ) ,
当 RenderObject 的 isRepaintBoundary 为 true 时,那么个区域形成一个 Layer,所以不是每个 RenderObject 都具有 Layer 的,因为这受 isRepaintBoundary 的影响。
更多相关可查阅 《Flutter完整开发实战详解(九、 深入绘制原理)》
Flutter 中 Widget 不可变,每次保持在一帧,如果发生改变是通过 State 实现跨帧状态保存,而真实完成布局和绘制数组的是 RenderObject , Element 充当两者的桥梁, State 就是保存在 Element 中。
Flutter 中的 BuildContext 只是接口,而 Element 实现了它。
Flutter 中 setState 其实是调用了 markNeedsBuild ,该方法内部标记此Element 为 Dirty ,然后在下一帧 WidgetsBinding.drawFrame 才会被绘制,这可以看出 setState 并不是立即生效的。
Flutter 中 RenderObject 在 attch/layout 之后会通过 markNeedsPaint(); 使得页面重绘
通过isRepaintBoundary 往上确定了更新区域,通过 requestVisualUpdate 方法触发更新往下绘制。
正常情况 RenderObject 的布局相关方法调用顺序是 : layout -> performResize -> performLayout -> markNeedsPaint , 但是用户一般不会直接调用 layout,而是通过 markNeedsLayou
Flutter 中一般 json 数据从 String 转为 Object 的过程中都需要先经过 Map 类型。
Flutter 中 InheritedWidget 一般用于状态共享,如Theme 、Localizations 、 MediaQuery 等,都是通过它实现共享状态,这样我们可以通过 context 去获取共享的状态,比如 ThemeData theme = Theme.of(context);
在 Element 的 inheritFromWidgetOfExactType 方法实现里,有一个 Map<Type, InheritedElement> _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;
}
}
4. Flutter 中的生命周期
- initState() 表示当前 State 将和一个 BuildContext 产生关联,但是此时BuildContext 没有完全装载完成,如果你需要在该方法中获取 BuildContext ,可以 new Future.delayed(const Duration(seconds: 0, (){//context}); 一下。
- didChangeDependencies() 在 initState() 之后调用,当 State 对象的依赖关系发生变化时,该方法被调用,初始化时也会调用。
- deactivate() 当 State 被暂时从视图树中移除时,会调用这个方法,同时页面切换时,也会调用。
- dispose() Widget 销毁了,在调用这个方法之前,总会先调用 deactivate()。
- didUpdateWidge 当 widget 状态发生变化时,会调用。
通过 StreamBuilder 和 FutureBuilder 我们可以快速使用 Stream 和 Future 快速构建我们的异步控件: 《Flutter完整开发实战详解(十一、全面深入理解Stream)》
Flutter 中 runApp 启动入口其实是一个 WidgetsFlutterBinding ,它主要是通过 BindingBase 的子类 GestureBinding 、ServicesBinding 、 SchedulerBinding 、PaintingBinding 、SemanticsBinding 、 RendererBinding 、WidgetsBinding 等,通过 mixins 的组合而成的。
Flutter 中的 Dart 的线程是以事件循环和消息队列的形式存在,包含两个任务队列,一个是 microtask 内部队列,一个是 event 外部队列,而 microtask 的优先级又高于 event 。
因为 microtask 的优先级又高于 event, 同时会阻塞event 队列,所以如果 microtask 太多就可能会对触摸、绘制等外部事件造成阻塞卡顿哦。
Flutter 中存在四大线程,分别为 UI Runner 、 GPU Runner 、 IO Runner , Platform Runner (原生主线程) ,同时在 Flutter 中可以通过 isolate 或者 compute 执行真正的跨线程异步操作。
5.PlatformView
Flutter 中通过 PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了 Presentation + VirtualDisplay + Surface 等实现的,大致原理就是:
使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManager 的 createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surface 的 id 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来。em... 实时控件截图渲染显示技术。
Flutter 的 Debug 下是 JIT 模式,release下是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完整开发实战详解(十三、全面深入触摸和滑动原理)》
Flutter 中 ListView 滑动其实都是通过改变 ViewPort 中的 child 布局来实现显示的。
常用状态管理的:目前有 scope_model 、flutter_redux 、fish_redux 、bloc + Stream 等几种模式,具体可见 : 《Flutter完整开发实战详解(十二、全面深入理解状态管理设计)》
6.Platform Channel
Flutter 中可以通过 Platform Channel 让 Dart 代码和原生代码通信的:
· BasicMessageChannel :用于传递字符串和半结构化的信息。
· MethodChannel :用于传递方法调用(method invocation)。
· EventChanne l: 用于数据流(event streams)的通信。
同时 Platform Channel 并非是线程安全的 ,更多详细可查阅闲鱼技术的 《深入理解Flutter Platform Channel》
7.Android 启动页
Android 中 Flutter 默认启动时会在 FlutterActivityDelegate.java 中读取 AndroidManifset.xml 内 meta-data 标签,其中 io.flutter.app.android.SplashScreenUntilFirstFrame 标志位如果为 ture ,就会启动 Splash 画面效果(类似IOS的启动页面)。
启动时原生代码会读取 android.R.attr.windowBackground 得到指定的 Drawable , 用于显示启动闪屏效果,之后并且通过 flutterView.addFirstFrameListener,在onFirstFrame 中移除闪屏。
8. Flutter 和 Dart的关系是什么?
Flutter是一个使用Dart语言开发的跨平台移动UI框架,通过自建绘制引擎,能高性能、高保真地进行移动开发。Dart囊括了多数编程语言的优点,它更符合Flutter构建界面的方式。
9. Widget 和 element 和 RenderObject 之间的关系?
首先看一下这几个对象的含义及作用。
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 不会被重建,只是更新而已。
11. 使用mixins的条件是什么?
因为mixins使用的条件,随着Dart版本一直在变,这里讲的是Dart2.1中使用mixins的条件:
mixins类只能继承自object mixins类不能有构造函数 一个类可以mixins多个mixins类 可以mixins多个类,不破坏Flutter的单继承
12. mixin 怎么指定异常类型
on关键字可用于指定异常类型。 on只能用于被mixins标记的类,例如mixins X on A,意思是要mixins X的话,得先接口实现或者继承A。这里A可以是类,也可以是接口,但是在mixins的时候用法有区别.
on 一个类:
class A {
void a(){
print("a");
}
}
mixin X on A{
void x(){
print("x");
}
}
class mixinsX extends A with X{
}
on 的是一个接口: 得首先实现这个接口,然后再用mix
class A {
void a(){
print("a");
}
}
mixin X on A{
void x(){
print("x");
}
}
class implA implements A{
@override
void a() {}
}
class mixinsX2 extends implA with X{
}
13. Flutter main future mirotask 的执行顺序?
普通代码都是同步执行的,结束后会开始检查microtask中是否有任务,若有则执行,执行完继续检查microtask,直到microtask列队为空。最后会去执行event队列(future)。
14. Future和Isolate有什么区别?
future是异步编程,调用本身立即返回,并在稍后的某个时候执行完成时再获得返回结果。在普通代码中可以使用await 等待一个异步调用结束。
isolate是并发编程,Dartm有并发时的共享状态,所有Dart代码都在isolate中运行,包括最初的main()。每个isolate都有它自己的堆内存,意味着其中所有内存数据,包括全局数据,都仅对该isolate可见,它们之间的通信只能通过传递消息的机制完成,消息则通过端口(port)收发。isolate只是一个概念,具体取决于如何实现,比如在Dart VM中一个isolate可能会是一个线程,在Web中可能会是一个Web Worker。
15. Stream 与 Future是什么关系
Stream 和 Future 是 Dart 异步处理的核心 API。Future 表示稍后获得的一个数据,所有异步的操作的返回值都用 Future 来表示。但是 Future 只能表示一次异步获得的数据。而 Stream 表示多次异步获得的数据。比如界面上的按钮可能会被用户点击多次,所以按钮上的点击事件(onClick)就是一个 Stream 。简单地说,Future将返回一个值,而Stream将返回多次值。Dart 中统一使用 Stream 处理异步事件流。Stream 和一般的集合类似,都是一组数据,只不过一个是异步推送,一个是同步拉取。
16. Stream 两种订阅模式?
Stream有两种订阅模式:单订阅(single) 和 多订阅(broadcast) 。单订阅就是只能有一个订阅者,而广播是可以有多个订阅者。这就有点类似于消息服务(Message Service)的处理模式。单订阅类似于点对点,在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。而广播类似于发布订阅模式,可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。 Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。但 Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。通过 Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。
17. await for 如何使用?
await for是不断获取stream流中的数据,然后执行循环体中的操作。它一般用在直到stream什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞。
Stream<String> stream = new Stream<String>.fromIterable(['不开心', '面试', '没', '过']);
main() async{
print('上午被开水烫了脚');
await for(String s in stream){
print(s);
}
print('晚上还没吃饭');
}
18. Flutter中的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。
这些状态的引入,主要是为了解决多个部件之间的交互和部件自身状态的维护。
19. Widget的两种类型是什么?
StatelessWidget: 一旦创建就不关心任何变化,在下次构建之前都不会改变。它们除了依赖于自身的配置信息(在父节点构建时提供)外不再依赖于任何其他信息。比如典型的Text、Row、Column、Container等,都是StatelessWidget。它的生命周期相当简单:初始化、通过build()渲染。
StatefulWidget: 在生命周期内,该类Widget所持有的数据可能会发生变化,这样的数据被称为State,这些拥有动态内部数据的Widget被称为StatefulWidget。比如复选框、Button等。State会与Context相关联,并且此关联是永久性的,State对象将永远不会改变其Context,即使可以在树结构周围移动,也仍将与该context相关联。当state与context关联时,state被视为已挂载。StatefulWidget由两部分组成,在初始化时必须要在createState()时初始化一个与之相关的State对象。
20. State 对象的初始化流程
initState() : 一旦State对象被创建,initState方法是第一个(构造函数之后)被调用的方法。可通过重写来执行额外的初始化,如初始化动画、控制器等。重写该方法时,应该首先调用super.initState()。在initState中,无法真正使用context,因为框架还没有完全将其与state关联。initState在该State对象的生命周期内将不会再次调用。
didChangeDependencies(): 这是第二个被调用的方法。在这一阶段,context已经可用。如果你的Widget链接到了一个InheritedWidget并且/或者你需要初始化一些listeners(基于context),通常会重写该方法。
build(BuildContext context) : 此方法在didChangeDependencies()、didUpdateWidget()之后被调用。每次State对象更新(或当InheritedWidget有新的通知时)都会调用该方法!我们一般都在build中来编写真正的功能代码。为了强制重建,可以在需要的时候调用setState((){...})方法。
dispose() : 此方法在Widget被废弃时调用。可重写该方法来执行一些清理操作(如解除listeners),并在此之后立即调用super.dispose()。
21. Widget 唯一标识Key有那几种
在flutter中,每个widget都是被唯一标识的。这个唯一标识在build或rendering阶段由框架定义。该标识对应于可选的Key参数,如果省略,Flutter将会自动生成一个。
在flutter中,主要有4种类型的Key:GlobalKey(确保生成的Key在整个应用中唯一,是很昂贵的,允许element在树周围移动或变更父节点而不会丢失状态)、LocalKey、UniqueKey、ObjectKey。
22. 什么是Navigator? MaterialApp做了什么?
Navigator是在Flutter中负责管理维护页面堆栈的导航器。MaterialApp在需要的时候,会自动为我们创建Navigator。Navigator.of(context),会使用context来向上遍历Element树,找到MaterialApp提供的_NavigatorState再调用其push/pop方法完成导航操作。
23.flutter与React Native有什么不同?
React Native利用JavaScript桥将其小部件转换为OEM小部件。而且由于它不断地进行这种转换(比较和更新周期),因此会产生瓶颈并导致性能下降。
虽然仍然使用反应式视图的优势,但Flutter并没有使用这种桥将其自己的小部件转换为OEM小部件。除了快速和流畅的UI性能和可预测性之外,作为此项的另一个优势,您在Android KitKat设备上看到的内容与您在Android Pie上获得的内容相同。这种兼容性是显而易见的,因为Flutter不使用OEM小部件,并且不受不同Android版本之间的UI / UX更改的影响。
24.为什么说flutter是原生的
Flutter使用名为Skia的图形引擎在应用程序端执行所有UI呈现。这意味着它不依赖于平台提供的OEM小部件。它只需要平台的画布来绘制自己的渲染。这确保了可预测性和开发人员对小部件和布局的完全控制。
Flutter内部小部件树
除此之外,Flutter将其结构保持为小部件树。顺便说一下,Flutter中的几乎所有东西都是一个小部件,它使您能够在小部件内部的小部件结构中构建您的应用程序。此内部树结构允许Skia仅呈现需要更新的小部件,并从缓存中检索未更改的甚至移动的小部件。
25.讲一下flutter的几个特点/优缺点
Dart是用于开发Flutter应用程序的面向对象,垃圾收集的编程语言。它也是由谷歌创建的,但它是开源的,因此它在Google内外都有社区。
除了Google的起源之外,Dart还被选为Flutter的语言,原因如下:它是极少数可以同时编译AOT(提前)和JIT(即时)的语言之一。
在应用程序开发过程中使用JIT编译,因为它可以通过动态编译代码来实现热重新加载(我将在下一个问题中详细讨论)和快速开发周期。 完成开发并准备发布后,将使用AOT编译。然后将代码AOT编译为本机代码,从而实现应用程序的快速启动和高性能执行。 就个人而言,我对Dart的经验是,如果您是熟悉Java或类似语言的开发人员,只需要几天的时间就可以习惯它。因此,如果您是Android开发人员,那么这种语言的学习曲线应该非常低。
凭借其干净但灵活的语法,Dart可以被识别为仅包含任何高级编程语言中最需要的功能的语言。
26.什么是ScopedModel / BLoC模式?
ScopedModel和BLoC(业务逻辑组件)是常见的Flutter应用程序架构模式,可帮助将业务逻辑与UI代码分离,并使用更少的状态窗口小部件
27.什么是stateWidget和statelessWidget?
内边距margin 和外边距边距 padding
body: Center(
child: Container(
child: new Text("hello zzl ",
style: TextStyle(
fontSize: 40.0,
),
textAlign: TextAlign.center,
),
alignment: Alignment.topCenter,
width: 500.0,
height: 400.0,
color: Colors.blue,
padding: const EdgeInsets.all(50.0),//外边距
// margin: const EdgeInsets.all(100.0),//内边距
),
),
填充控件 Padding
Padding的布局分为两种情况:
当child为空的时候,会产生一个宽为left+right,高为top+bottom的区域; 当child不为空的时候,Padding会将布局约束传递给child,根据设置的padding属性,缩小child的布局尺寸。然后Padding将自己调整到child设置了padding属性的尺寸,在child周围创建空白区域。
import 'package:flutter/material.dart';
class PaddingDemo extends StatelessWidget{
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
• title:new Text("padding填充控件"),
),
body: new Padding(
• padding: const EdgeInsets.all(8.0),
• child: new Image.asset("images/hua3.png"),
),
);
}
}
void main(){
runApp(new MaterialApp(
title: "padding填充控件",
home: new PaddingDemo(),
));
28.如何在Flutter中定义边距和填充?
内边距margin 和外边距边距 padding
body: Center(
child: Container(
child: new Text("hello zzl ",
style: TextStyle(
fontSize: 40.0,
),
textAlign: TextAlign.center,
),
alignment: Alignment.topCenter,
width: 500.0,
height: 400.0,
color: Colors.blue,
padding: const EdgeInsets.all(50.0),//外边距
// margin: const EdgeInsets.all(100.0),//内边距
),
),
填充控件 Padding
Padding的布局分为两种情况:
当child为空的时候,会产生一个宽为left+right,高为top+bottom的区域; 当child不为空的时候,Padding会将布局约束传递给child,根据设置的padding属性,缩小child的布局尺寸。然后Padding将自己调整到child设置了padding属性的尺寸,在child周围创建空白区域。
import 'package:flutter/material.dart';
class PaddingDemo extends StatelessWidget{
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
• title:new Text("padding填充控件"),
),
body: new Padding(
• padding: const EdgeInsets.all(8.0),
• child: new Image.asset("images/hua3.png"),
),
);
}
}
void main(){
runApp(new MaterialApp(
title: "padding填充控件",
home: new PaddingDemo(),
));
29.谈一下flutter state的生命周期
State 的生命周期
从上面的例子中可以看到, 会要求提供一个含有视图树的 。
既然 能够控制一个视图的状态,那它肯定会有一系列的生命周期。
上图就是 State 的生命周期图。
\1. StatefulWidget.createState()
Framework 调用会通过调用 StatefulWidget.createState() 来创建一个 State。
\1. initState()
新创建的 State 会和一个 产生关联,此时认为 State 已经被安装好了,initState() 函数将会被调用。
通常,我们可以重写这个函数,进行初始化操作。
\1. didChangeDependencies()
在 initState() 调用结束后,这个函数会被调用。
事实上,当 State 对象的依赖关系发生变化时,这个函数总会被 Framework 调用。
\1. build()
经过以上步骤,系统认为一个 State 已经准备好了,就会调用 build() 来构建视图。
我们需要在这个函数中,返回一个 Widget。
\1. deactivate()
当 State 被暂时从视图树中移除时,会调用这个函数。
页面切换时,也会调用它,因为此时 State 在视图树中的位置发生了变化,需要先暂时移除后添加。
注意,重写的时候必须要调用 super.deactivate()。
\1. dispose()
当 State 被永久的从视图树中移除,Framework 会调用该函数。
在销毁前触发,我们可以在这里进行最终的资源释放。
在调用这个函数之前,总会先调用 deactivate()。
注意,重写的时候必须要调用 super.dispose()。
\1. didUpdateWidget(covariant T oldWidget)
当 widget 的配置发生变化时,会调用这个函数。
比如, 的时候就会调用这个函数。
这个函数调用后,会调用 build()。
\1. setState()
当我需要更新 State 的视图时,需要手动调用这个函数,它会触发 build() 。
44. StatefulWidget 的生命周期
· initState():Widget 初始化当前 State,在当前方法中是不能获取到 Context 的,如想获取,可以试试 Future.delayed()
· didChangeDependencies():在 initState() 后调用,State对象依赖关系发生变化的时候也会调用。
· deactivate():当 State 被暂时从视图树中移除时会调用这个方法,页面切换时也会调用该方法,和Android里的 onPause 差不多。
· dispose():Widget 销毁时调用。
· didUpdateWidget:Widget 状态发生变化的时候调用。
45. Flutter 如何与 Android iOS 通信?
Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:
\1. BasicMessageChannel:用于传递字符串和半结构化的信息。
\2. MethodChannel:用于传递方法调用。Flutter主动调用Native的方法,并获取相应的返回值。
\3. EventChannel:用于数据流(event streams)的通信。
47. 说一下什么是状态管理,为什么需要它?
首先状态其实是一个概念上的东西,区分全局状态和局部状态。
局部状态比如说一个控件中输入的信息,全局状态比如是登陆后从后台请求回来的 userId。
当全局状态越来越多,多个页面共享一个状态时,我们就需要管理它。
常用的状态管理有:
· ScopedModel
· BLoC
· Redux / FishRedux
· Provider
48. 说一下 BLoC 模式?
具体可以查看: Vadaski - Flutter | 状态管理探索篇——BLoC(三)
这里引用一部分:
BLoC是一种利用reactive programming方式构建应用的方法,这是一个由流构成的完全异步的世界。
49. 如何统一管理错误页面?
我们都知道,如果在 Flutter 当中出错的话,那就是一片红。
可以使用 ErrorWidget.builder 来自定义一个 Widget 就 ok 了