01. Flutter JIT 和 AOT 之间有什么区别?
JIT: JIT 编译是在应用运行时动态编译代码,将 Dart 代码编译为机器代码,这意味着代码在执行之前是即时编译的
AOT: AOT 编译是在应用发布之前将 Dart 代码编译为机器码,这意味着应用在发布到设备前已经完全编译好
在开发模式下使用 JIT 编译来实现热重载,让开发者快速查看代码更改后的效果。
当应用准备发布时,Flutter 会自动使用 AOT 编译来生成优化后的应用程序,确保发布到生产环境时的高性能和低启动时间。
02. 在 Flutter 中,Keys(键)有哪几种类型, 并解释下其作用?
Widget中有个可选属性key,顾名思义,它是组件的标识符。 Key 是一个非常重要的概念,主要用于区分、保持和管理 Widget 的状态, 尤其在涉及列表、动画和重建(rebuild)等复杂场景时显得尤为重要
2-key的分类
key有两个子类GlobalKey和LocalKey
GlobalKey:GlobalKey全局唯一key,每次build的时候都不会重建,可以长期保持组件的状态,一般用来进行跨组件访问Widget的状态
LocalKey:LocalKey局部key,可以保持当前组件内的子组件状态,用法跟GlobalKey类似,可以访问组件内部的数据。
LocalKey有3个子类ValueKey、ObjectKey、UniqueKey。
- 使用特定的值来区分 Widget,在列表项中比较常用。例如用于标识唯一列表项 ValueKey 源码
class ValueKey<T> extends LocalKey {
/// Creates a key that delegates its [operator==] to the given value.
const ValueKey(this.value);
/// The value to which this key delegates its [operator==]
final T value;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is ValueKey<T>
&& other.value == value;
}
@override
int get hashCode => Object.hash(runtimeType, value);
}
- 使用 Dart 对象来标识,适用于对象实例来区分的场景 ObjectKey 源码
class ObjectKey extends LocalKey {
/// Creates a key that uses [identical] on [value] for its [operator==].
const ObjectKey(this.value);
/// The object whose identity is used by this key's [operator==].
final Object? value;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is ObjectKey
&& identical(other.value, value);
}
@override
int get hashCode => Object.hash(runtimeType, identityHashCode(value));
}
- 为每个 Widget 生成唯一的 Key,通常用于强制 Flutter 重建一个 Widget UniqueKey 源码
class UniqueKey extends LocalKey {
/// Creates a key that is equal only to itself.
///
/// The key cannot be created with a const constructor because that implies
/// that all instantiated keys would be the same instance and therefore not
/// be unique.
// ignore: prefer_const_constructors_in_immutables , never use const for this class
UniqueKey();
}
为什么要使用 Key?
Key 的主要作用是帮助 Flutter 区分 Widget,以便在 Widget 树发生变化时,可以将特定的状态与对应的 Widget 绑定。以下场景中尤其需要 Key:
- 列表项重排:当 ListView 中的项发生增删或重新排序时,通过 Key 能让 Flutter 更好地理解哪些项保持不变、哪些需要更新。
- 保持 Widget 状态:一些 StatefulWidget 需要保持自己的状态不变,比如表单字段的输入、滚动位置等。
- 动画和过渡效果:在切换页面或动态更新界面时,如果希望某些元素不重新生成,可以用 Key 保证它们保持当前状态。
- 使用 GlobalKey 访问状态 GlobalKey 允许在 Widget 树的其他位置访问和操作某个 Widget 的 state。
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
// ignore: library_private_types_in_public_api
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
GlobalKey<FormState> formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Gobal Keys"),
),
body: Center(
child: Form(
key: formKey,
child: Column(
children: [
TextFormField(
validator: (value) => value!.isEmpty ? "Enter text" : null,
),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
print("Form is validate");
}
},
child: const Icon(Icons.send))
],
)),
),
);
}
}
- 使用 ValueKey 在列表中保持 Widget 唯一性
class MyListView extends StatelessWidget {
final List<String> items;
MyListView(this.items);
@override
Widget build(BuildContext context) {
return ListView(
children: items.map((item) => ListTile(
key: ValueKey(item), // 确保每个 item 的唯一性
title: Text(item),
)).toList(),
);
}
}
- 使用 Key 优化动画效果
当 Widget 在页面中移动时,使用 Key 保持它的状态而不销毁可以提高动画流畅性。例如在 AnimatedList 中保持项的唯一性。
class MyAnimatedList extends StatelessWidget {
final List<int> items;
MyAnimatedList(this.items);
@override
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: items.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
key: ValueKey(items[index]), // 为动画列表中的每项设置 Key
sizeFactor: animation,
child: ListTile(title: Text('Item ${items[index]}')),
);
},
);
}
}
03. Flutter从启动到显示
在 Flutter 应用程序启动后,到其内容最终显示在屏幕上,通常涉及以下主要步骤:
- Flutter 引擎初始化:
Flutter 引擎初始化是应用程序启动的第一步。在这个阶段,Flutter 引擎会初始化运行时环境,加载 Flutter 框架和原生平台的相关代码。
- Dart 代码执行:
一旦引擎初始化完成,Flutter 会加载并执行 Dart 代码。Dart 代码包括应用程序的入口点 main() 函数以及应用程序的其他逻辑。
- Widget 树构建:
Flutter 使用一种称为 Widget 的组件模型来构建用户界面。在 Dart 代码执行阶段,Flutter 应用程序会通过调用 build() 方法构建 Widget 树。 在构建 Widget 树期间,Flutter 会创建并配置一系列 Widget,这些 Widget 最终会渲染为用户界面的组件。
- 绘制和布局:
一旦 Widget 树构建完成,Flutter 引擎会执行布局(Layout)和绘制(Painting)阶段。在布局阶段,Flutter 计算每个 Widget 的位置和大小。在绘制阶段,Flutter 将每个 Widget 绘制为屏幕上的图像。
- 合成:
在绘制阶段完成后,Flutter 引擎会将所有绘制的内容合成为一张图像,并将其显示在屏幕上。 Flutter 使用了一种称为“图层合成”的技术,它能够有效地管理屏幕上的图层,提高绘制效率。
- 显示:
最后,一旦合成完成,图像就会被显示在屏幕上,用户就能够看到应用程序的内容了。 总的来说,从 Flutter 应用程序启动到其内容显示在屏幕上,涉及到一系列复杂的过程,包括引擎初始化、Dart 代码执行、Widget 树构建、布局和绘制、合成和显示等阶段。Flutter 通过这些阶段来实现高性能、流畅的用户界面渲染。
04. Flutter三棵树
即Widget树、Element树 和 RenderObject树。
Widget树:控件的配置信息,不涉及渲染,更新代价极低。
RenderObject树:真正的UI渲染树,负责渲染UI,更新代价极大。
Element树:Widget树和RenderObject树之间的粘合剂,负责将Widget树的变更以最低的代价映射到 RenderObject树上
05. 在Flutter项目中,一些常见的棘手问题及其解决方法
- 性能问题:卡顿和掉帧:在UI复杂或列表项较多时可能会出现卡顿
- 状态管理:状态同步问题:在多个组件之间共享状态时,状态可能不同步
- 依赖包冲突:版本冲突:不同依赖包之间可能有版本冲突,导致构建失败
- 平台特定问题:iOS和Android差异:有些功能在iOS和Android上的表现不同,或特定平台上的功能无法正常工作
- 构建和发布问题:构建失败:在不同环境下(如开发、测试、生产)可能会遇到构建失败的问题。
- 网络请求和数据处理:API请求失败:处理网络请求时可能会遇到超时或数据格式错误等问题。
06. flutter中用的哪些组件多一些?
基础组件:
Container:一个多功能容器,支持布局、装饰、定位等属性。
Text:用于显示一段文本。
Image:用于显示图片,可以从网络、文件、内存等加载。
Icon:用于显示图标。
Scaffold:应用程序页面的基础结构,包含AppBar、Drawer、Snackbar等常用组件。
AppBar:顶部应用栏,通常包含标题和操作按钮。
布局组件:
Column:垂直方向布局多个子组件。
Row:水平方向布局多个子组件。
Stack:重叠布局,可以让子组件堆叠显示。
ListView:可滚动列表,用于显示大量子组件。
GridView:网格布局,用于显示两维的子组件列表。
Expanded和Flexible:控制子组件在Flex布局(如Row和Column)中的伸缩行为。
输入组件:
TextField:文本输入框。
Checkbox:复选框。
Radio:单选按钮。
Switch:开关按钮。
Slider:滑块。
DropdownButton:下拉按钮。
按钮组件:
OutlinedButton:带边框按钮。
IconButton:带图标按钮。
FloatingActionButton:悬浮按钮,通常用于突出某个重要操作。
导航和路由:
Navigator:管理应用程序页面的堆栈。
Drawer:侧边栏菜单。
BottomNavigationBar:底部导航栏。
TabBar和TabBarView:标签栏和标签内容视图。
高级组件:
FutureBuilder:基于异步操作的组件,用于处理Future的结果。
StreamBuilder:基于流数据的组件,用于处理Stream的结果。
CustomPaint:自定义绘制组件,允许开发者自己绘制图形。
AnimationController和AnimatedBuilder:动画控制和构建组件。
07. Flutter中的状态管理
-
本地状态管理(Local State Management): 本地状态管理是指在小规模应用或组件内部管理状态的简单方法。 通常使用StatefulWidget和setState来更新状态。 该方法适用于较简单的应用或组件,状态的范围有限且不需要在多个组件之间共享
-
InheritedWidget 通过将状态作为不可变对象传递给子组件来实现状态共享。 当共享的状态发生变化时,它们会自动更新子组件。 这种方法适用于中等规模的应用,可以在组件树中共享状态,但不适用于大型应用或高度复杂的状态管理。
-
Provider: Provider是Flutter社区中广泛使用的状态管理库,它构建在InheritedWidget之上, 提供了一种简化状态共享的方式。 它使用了依赖注入的概念,可以在组件树的任何位置共享状态,并自动通知相关的子组件进行更新。 Provider支持多种类型的状态管理,包括基于ChangeNotifier、Stream、ValueNotifier等。 它适用于中等到大型规模的应用,具有良好的灵活性和性能。
08. Flutter中的调试技巧有哪些?
- 断点
- 打印日志
- DevTools
09. Flutter中的动画是如何实现的?有哪些常用的动画类?
在Flutter中,动画是通过**Animation**和**AnimationController**两个类来实现的。
Animation表示动画的当前状态,例如动画的当前值、是否完成、是否反向等。
AnimationController用于控制动画的开始、暂停、恢复、反向等。
Flutter中的动画可以分为两种类型:显式动画和隐式动画。
显式动画是通过AnimationController控制的,例如Tween动画、Curve动画等。
隐式动画则是通过Flutter框架自动执行的,例如AnimatedContainer、AnimatedOpacity等。
常用的动画类包括:
1. Tween:用于在两个值之间进行插值运算,例如在0和1之间插值计算出当前值。
2. Curve:用于定义动画的速度曲线,例如线性曲线、抛物线曲线、弹性曲线等。
3. AnimationController:用于控制动画的开始、暂停、恢复、反向等。
4. AnimatedBuilder:用于在动画变化时自动重建Widget树,可以用于创建复杂的动画效果。
5. AnimatedContainer:用于创建一个可以自动执行动画的Container。
6. AnimatedOpacity:用于创建一个可以自动执行动画的Opacity。
10. PlatformView 以及其原理?
- 在引擎的插件注册表中,注册了自定义的 PlatformView
iOS 端:
- (void)viewDidLoad {
[super viewDidLoad];
_flutterEngine = [[FlutterEngine alloc] initWithName:@"iOSDemo"];
[_flutterEngine run];
[GeneratedPluginRegistrant registerWithRegistry:_flutterEngine];
id <FlutterPluginRegistrar> nativeViewRegister = [_flutterEngine registrarForPlugin:@"com.pano.dev.nativeview"];
[nativeViewRegister registerViewFactory:[DTFlutterViewFactory new] withId:@"DTFlutterView"];
}
- 创建 PlatformView 和工厂类
#import "DTFlutterViewFactory.h"
@interface DTFlutterView : UIView <FlutterPlatformView>
@end
@implementation DTFlutterView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor cyanColor];
}
return self;
}
- (nonnull UIView *)view {
return self;
}
@end
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
@interface DTFlutterViewFactory : NSObject<FlutterPlatformViewFactory>
@end
@implementation DTFlutterViewFactory
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[DTFlutterView alloc] initWithFrame:frame];
}
@end
flutter端:
class SampleView extends StatefulWidget {
const SampleView({super.key});
@override
State<StatefulWidget> createState() => _SampleViewState();
}
class _SampleViewState extends State<SampleView> {
@override
Widget build(BuildContext context) {
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'DTFlutterView',
onPlatformViewCreated: _onPlatformViewCreated,
);
} else {
return UiKitView(
viewType: 'DTFlutterView',
onPlatformViewCreated: _onPlatformViewCreated);
}
}
_onPlatformViewCreated(int id) => print("DTFlutterView id: $id");
}
注意 viewType 和 withId 需要保持一致
11. flutter 和 native的通信方式有哪些?
- MethodChannel
MethodChannel 是 Flutter 与原生通信的最常用方式,它提供了一种同步调用方法来传递数据。
- 适用场景:用于 Flutter 调用原生方法,并同步等待返回结果,或原生调用 Flutter 方法。
- 实现方式:在 Flutter 中通过 MethodChannel 创建通道,在 Android 和 iOS 中实现对应的方法
Flutter 端:
const platform = MethodChannel("com.pano.dev");
Future<String> sendDataToNative() async {
try {
final res = await platform.invokeMethod("fetchData", {"key": "value"});
print(res);
return res;
} catch (e) {
print("Error: $e");
throw ("fetch data failed");
}
}
iOS 端:
#import "ViewController.h"
#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
@interface ViewController ()
@property (nonatomic, strong) FlutterEngine *flutterEngine;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_flutterEngine = [[FlutterEngine alloc] initWithName:@"iOSDemo"];
[_flutterEngine run];
[GeneratedPluginRegistrant registerWithRegistry:_flutterEngine];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 打开 flutter 页面
self.flutterEngine.viewController = nil;
FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:NULL];
FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"com.pano.dev" binaryMessenger:vc.binaryMessenger];
[channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqualToString:@"fetchData"]) {
result(@"Received: \(data ?? "")");
} else {
result(FlutterMethodNotImplemented);
}
}];
}
@end
由于涉及到跨系统数据交互,Flutter 会使用 StandardMessageCodec 对通道中传输的信息进行类似 JSON 的二进制序列化,以标准 化数据传输行为。这样在我们发送或者接收数据时,这些数据就会根据各自系统预定的规则 自动进行序列化和反序列化
方法通道解决了逻辑层的原生能力复用问题,使得 Flutter 能够通过轻量级的异步方法调 用,实现与原生代码的交互。一次典型的调用过程由 Flutter 发起方法调用请求开始,请求 经由唯一标识符指定的方法通道到达原生代码宿主,而原生代码宿主则通过注册对应方法实 现、响应并处理调用请求,最后将执行结果通过消息通道,回传至 Flutter。
需要注意的是,方法通道是非线程安全的。这意味着原生代码与 Flutter 之间所有接口调用 必须发生在主线程。Flutter 是单线程模型,因此自然可以确保方法调用请求是发生在主线 程(Isolate)的;而原生代码在处理方法调用请求时,如果涉及到异步或非主线程切换, 需要确保回调过程是在原生系统的 UI 线程(也就是 Android 和 iOS 的主线程)中执行 的,否则应用可能会出现奇怪的 Bug,甚至是 Crash。
- EventChannel
EventChannel 是专门用于 Flutter 和原生之间的持续数据流传输的通信方式。例如从原生到 Flutter 的事件流或数据更新。 • 适用场景:用于持续的数据流传递,例如传感器数据、GPS 定位等。 • 实现方式:在 Flutter 中使用 EventChannel,在原生端通过 EventSink 向 Flutter 发送数据
flutter 端:
const eventChannel = EventChannel('com.pano.dev.my_event_channel');
void _receiveEventStream() {
eventChannel.receiveBroadcastStream().listen((data) {
print("Received data: $data");
});
}
iOS 端:
- (void)viewDidLoad
{
FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"com.pano.dev.my_event_channel"
binaryMessenger:vc.binaryMessenger];
[eventChannel setStreamHandler:self];
}
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
eventSink:(FlutterEventSink)events {
events(@"iOS data");
return nil;
}
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
return nil;
}
- BasicMessageChannel
BasicMessageChannel 适合传输文本和半结构化数据,并支持双向通信。它可以使用 JSON 编码的数据,既适合简单的请求/响应模式,也适合持续的消息传递。
- 适用场景:适用于需要双向通信、传输 JSON 数据或半结构化数据的场景。
- 实现方式:在 Flutter 和原生端都使用 BasicMessageChannel,可以实现双向数据传输
flutter 端:
const messageChannel = BasicMessageChannel('com.example/my_message_channel', StandardMessageCodec());
void _sendMessageToNative() {
messageChannel.send({"key": "Hello from Flutter"}).then((reply) {
print("Reply from native: $reply");
});
}
void _receiveMessageFromNative() {
messageChannel.setMessageHandler((message) async {
print("Received from native: $message");
return "Reply from Flutter";
});
}
iOS 端:
let messageChannel = FlutterBasicMessageChannel(name: "com.example/my_message_channel", binaryMessenger: controller.binaryMessenger, codec: FlutterStandardMessageCodec.sharedInstance())
messageChannel.setMessageHandler { (message, reply) in
print("Received from Flutter: \(message ?? "")")
reply("Hello from iOS")
}
4. libffi
待补充