本节算是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图形库和渲染管线。这个过程通常包括创建图形上下文、设置渲染参数、加载字体和纹理等。