本节算是Flutter的进阶内容。
一、Dart相关
1. dart 方法调用更安全?
(1)差异
dart method call 需要使用 ?. 来保证正确性。
但OC中依赖于 method forward,即使是 nil ,调用方法也不会crash。
(2)设计理念不同
编程语言设计理念在安全性这块基本分为两派:
- 出现了超出预期外的异常,就直接crash。比如:银行app
- 出现了超出预期外的异常,可以不crash,可以带着异常继续运行下去。比如:本地单击游戏
(3)进一步思考
有没有可能衍生出一种:场景crash的机制,尤其是在国内超大型app诞生的背景下,来实现按场景crash,而不是一个场景crash了,直接整个app崩了。
2. dart 的数组更先进?
(1)提问
给数据塞不同数据类型的内容:
List<dynamic> mixedList = [];
// 添加一个字符串
mixedList.add("Hello, world!");
// 添加一个整数
mixedList.add(42);
// 添加一个布尔值
mixedList.add(true);
// 添加一个自定义对象
mixedList.add(MyClass());
真的是不同类型吗?
(2)数组的快速索引
之所以可以快速索引,依赖的就是地址的规整,所以:
无论任何一个语言,数组存的数据结构本质都必须是一样的,别看 dart 可以存 int、double,其实它本质都是在存 dynamic。
进而想一个问题,为什么要求数组只能存数据结构相同的内容。这要从数组的特性来说,它可以快速按索引取数据。那一定是 baseAddress➕n✖️memorySize
3. 参数爆炸
//要达到可选命名参数的用法,那就在定义函数的时候给参数加上 {}
void enable2Flags({bool bold = true, bool hidden = false}) => print("$bold ,$hidden");
二、Flutter
(一)Widget and State
StatefulWidget 是不可变的,
需要一个 State 去实际承载者,然后需要实现 StatefulWidget 的泛型,
以便在 State 中通过 widget.获取 widget 的属性。
(二)原生嵌Flutter View
目标:有一个iOS原生的tableView,然后我想在其中展示一些Flutter渲染出来的视图: 1. 需要展示的Flutter视图是:一句话 和 一个五角星 ;2. 要展示的那句话需要Flutter从原生去获取。
OC原生代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellIdentifier" forIndexPath:indexPath];
// 创建一个FlutterViewController
FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
// 添加FlutterViewController的view到cell中
UIView *flutterView = flutterViewController.view;
flutterView.frame = cell.contentView.bounds;
[cell.contentView addSubview:flutterView];
// 设置FlutterMethodChannel
FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"com.example.myapp/message" binaryMessenger:flutterViewController];
// 处理Flutter调用的方法
[channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqualToString:@"getMessage"]) {
// 从原生获取数据
NSString *message = @"你好,这是从原生获取的数据";
result(message);
} else {
result(FlutterMethodNotImplemented);
}
}];
return cell;
}
Flutter代码:
import ‘package:flutter/material.dart’;
import ‘package:flutter/services.dart’;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyFlutterView(),
),
);
}
}
class MyFlutterView extends StatefulWidget {
@override
_MyFlutterViewState createState() => _MyFlutterViewState();
}
class _MyFlutterViewState extends State
String _message = ‘’;
@override
void initState() {
super.initState();
_getMessageFromNative();
}
Future<void> _getMessageFromNative() async {
const channel = MethodChannel('com.example.myapp/message');
final message = await channel.invokeMethod<String>('getMessage');
setState(() {
_message = message!;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_message),
Icon(Icons.star),
],
);
}
}
(三)methodChannel vs pigeon
MethodChannel和Pigeon都是Flutter中用于与原生平台通信的机制,但它们在使用方式和实现原理上有一些区别。
MethodChannel: MethodChannel是Flutter中最基本的原生通信机制。使用MethodChannel时,你需要在Dart和原生平台(如Android和iOS)之间显式地定义方法调用和响应。这意味着你需要手动编写Dart和原生平台的通信代码。
原理上,MethodChannel使用字符串作为方法名和参数,并在Dart和原生平台之间传递这些字符串。这种方式简单易用,但可能导致一些问题,如类型安全性差、易出错和代码冗余等。
Pigeon: Pigeon是一个更高级的原生通信机制。它是一个代码生成工具,可以根据你定义的Dart接口自动生成Dart和原生平台的通信代码。这样可以减少手动编写通信代码的工作量,提高类型安全性和减少错误。
原理上,Pigeon在Dart和原生平台之间建立一个类型安全的通信层。你只需要定义一个Dart接口,然后Pigeon会自动生成相应的Dart和原生平台的代码。这种方式可以提高开发效率,减少错误和保证类型安全。
技术选型: 在选择MethodChannel和Pigeon时,可以考虑以下因素:
项目规模:如果你的项目较小,且原生通信需求较简单,可以考虑使用MethodChannel。如果你的项目较大,且原生通信需求较复杂,可以考虑使用Pigeon,以提高开发效率和减少错误。
类型安全:如果你希望在Dart和原生平台之间建立一个类型安全的通信层,可以考虑使用Pigeon。
代码生成:如果你不介意使用代码生成工具,可以考虑使用Pigeon。需要注意的是,使用代码生成工具可能会增加项目的复杂性和维护成本。
学习曲线:MethodChannel的学习曲线相对较低,因为它是Flutter中最基本的原生通信机制。Pigeon的学习曲线可能稍高,因为它涉及到代码生成和类型安全通信等概念。
总之,在选择MethodChannel和Pigeon时,可以根据你的项目需求、类型安全需求和对代码生成工具的接受程度等因素进行权衡。如果你的项目规模较小且原生通信需求较简单,可以考虑使用MethodChannel;如果你的项目规模较大且需要类型安全的原生通信,可以考虑使用Pigeon。
(四)Flutter Engine
1. 预备知识
- 一个Native进程只有一个DartVM
- 第一个FlutterEngine初始化时,会创建并初始化DartVM
- 一个DartVM可以有多个FlutterEngine,每个FlutterEngine都运行在自己的Isolate中,他们的内存数据不共享,需要通过Isolate事先设置的port(顶级函数)通讯。
实现方式:Flutter页面第一次调用时,会初始化Flutter相关的东西,比如FlutterEngine,DartVM等等,所以可以提前初始化Flutter相关的东西来达到减少第一次启动的耗时:
- 提前预加载
- 全局使用同一个FlutterEngine
App 每个进程中创建第一个 FlutterEngine 实例的时候会加载 Flutter 引擎的原生库并启动 Dart VM(VM 存活生命周期跟随进程),随后同进程中其他的 FlutterEngine 将在同一个 VM 实例上运行,而第一次启动耗时主要花费在加载 Flutter 引擎的原生库和启动 Dart VM。所以可以提前初始化一个FlutterEngine来减少第一次加载时的耗时。
2. 有什么问题?
在底层,预加载Flutter Engine的主要操作包括:
加载Dart运行时:Dart运行时是运行Dart代码所需的基础设施,它包括垃圾回收器、内存管理器、运行时类型检查等功能。加载Dart运行时通常需要一定的时间。
运行Dart代码:当Dart运行时加载完成后,Flutter Engine会开始运行你的Dart代码。这包括执行main函数、创建Widget树、初始化渲染管线等。这个过程中,你的Dart代码可能需要执行一些耗时操作,如解析JSON、读取文件等。
初始化渲染管线:Flutter Engine使用Skia图形库进行图形渲染。在首次显示Flutter视图之前,需要初始化Skia图形库和渲染管线。这个过程通常包括创建图形上下文、设置渲染参数、加载字体和纹理等。