版本新特性
一、Flutter SDK 版本
Flutter 3.x 系列
- Flutter 3.24+
- 引入了新的Widget和动画效果,增强了UI设计的灵活性。
- 进一步优化了性能,包括渲染速度和内存使用量的减少。
- 增强了多平台支持,包括Android、iOS、Web、macOS、Windows等。
- Flutter DevTools获得了多项改进,包括更好的调试体验和更丰富的性能分析工具。
- Flutter 3.23
- 提供了更多的Material Design组件和自定义动画效果。
- 增强了性能优化工具,包括内存调试和CPU性能分析。
- Flutter 3.20
- 发布了新的功能和性能改进,可能包括新的API、性能优化、错误修复等。
- Flutter 3.19
- 引入了专为Gemini设计的新Dart SDK。
- 提供了新的Widget,允许开发者对Widget动画实现精细化控制。
- Impeller更新带来了渲染性能提升。
- 增加了对Windows Arm64的支持。
- Flutter 3.16
- 发布了稳定版,包含了一系列性能优化和新特性。
- 改善了two_dimensional_scrollables package中的TableView Widget表现。
- Flutter 3.13及之前版本
- 提供了重要的功能更新和性能改进,包括新的API、性能优化、错误修复等。
Flutter 2.x 系列
- Flutter 2.10
- 提供了对Windows平台的更好支持。
- 引入了新的组件和API,增强了开发者的工具集。
- Flutter 2.8
- 提供了性能改进和错误修复。
- 增强了与Firebase的集成。
- Flutter 2.5
- 引入了新的功能和性能改进。
- 增强了多平台支持,包括Web和桌面端。
- Flutter 2.2
- 提供了更多的Material Design组件和自定义动画效果。
- 增强了性能优化工具。
- Flutter 2.0
- 正式支持Web、Windows、macOS和Linux等多个平台。
- 引入了空安全特性。
Flutter 1.x 系列
- Flutter 1.22
- 提供了对Android 11和iOS 14的完整支持。
- 引入了更多的Material Design 3组件和动画效果。
- Flutter 1.17
- 解决了大量问题,并添加了新功能,如iOS上的Metal支持、新的Material组件等。
- Flutter 1.12
- 引入了新的渲染引擎和性能优化工具。
- 增强了Dart语言的支持。
- Flutter 1.9
- 提供了更多的功能和性能改进。
- Flutter 1.7
- 增强了Web平台的支持。
- 提供了更多的国际化和本地化支持。
Flutter DevTools 版本
Flutter DevTools是Flutter开发者用于调试和性能分析的重要工具。随着Flutter SDK的更新,DevTools也会不断引入新功能和改进。以下是一些DevTools的重要更新(注意:由于DevTools通常与Flutter SDK版本紧密相关,因此以下更新可能并不完全对应于特定的DevTools版本号,而是与Flutter SDK版本相关联):
- 性能分析工具改进
- 提供了更详细的CPU和内存使用情况分析。
- 增强了Frame Graph和Timeline视图的交互性。
- 网络调试工具
- 允许开发者查看和分析应用的网络请求和响应。
- 提供了过滤和搜索功能,方便开发者定位问题。
- 布局检查器
- 提供了更详细的Widget树和布局信息。
- 允许开发者在运行时修改Widget属性,实时查看效果。
- 源代码调试
- 增强了源代码级别的调试功能,包括断点设置、变量查看等。
- 提供了调用堆栈和异常信息,帮助开发者快速定位问题。
二、dart 版本
Dart 2.x 版本
- Dart 2.15
- 新增了具备更快并发能力的isolates,并引入了isolate groups概念,使得在isolate groups中启动额外的isolate可以快100倍,且内存使用更少。
- 支持了tear-off的构造函数,即可以通过构造函数的名称创建一个指向该构造函数的函数对象。
- 对dart:core库的枚举支持进行了改进。
- 提供了包发布者的新功能。
- Dart 2.12 及之前版本 (具体细节可能因版本而异,但以下是一些普遍的新特性)
- 引入了空安全(Null Safety)特性,这是Dart 2.12版本中的重大更新,它要求开发者在变量声明时明确其是否为可空,从而避免了大量的空指针异常。
- 增强了类型系统,提供了更丰富的类型检查和转换功能。
- 对异步编程进行了优化,提供了更简洁的async/await语法,并增强了Future和Stream的使用体验。
- 引入了新的语言特性,如可选命名参数、可选位置参数等,使得函数调用更加灵活。
- 对Dart VM和编译器进行了优化,提高了程序的运行速度和编译效率。
Dart 1.x 版本
- Dart 1.9
- 引入了async方法和await表达式,这两个特性都是基于现有的Future API,使得异步编程更加简洁和直观。
- 引入了新的正则引擎,显著提升了正则表达式的匹配速度和效率。
- Dart 1.12 RC0
- 新增了大量null-aware操作符语言特性,如
??
(if null operator)、??=
(null-aware assignment)、x?.p
(null-aware access)和x?.m()
(null-aware method invocation)等,这些特性使得在处理可能为null的变量时更加安全和便捷。
- 新增了大量null-aware操作符语言特性,如
- Dart 1.x 早期版本 (具体版本和特性可能因时间久远而无法准确追溯)
- Dart语言在早期版本中逐渐形成了自己的语法和语义体系,包括变量声明、函数定义、类与对象、异常处理等基本概念。
- 提供了丰富的标准库和API,支持文件I/O、网络编程、多线程(通过isolates实现)等高级功能。
- 强调了类型安全和内存管理的重要性,通过静态类型检查和垃圾回收机制等手段确保程序的稳定性和可靠性。
Package
Package 介绍
Flutter packages 分为几种类型,主要包括:
- 纯 Dart 的 packages:完全用 Dart 语言编写的库,可以跨平台使用,无需特定于平台的实现。
- 原生插件类型的 packages:包含平台特定代码(如 Java/Kotlin 用于 Android,Swift/Objective-C 用于 iOS)的插件,用于访问原生平台的功能。
- FFI(外部函数接口)插件:允许 Dart 代码调用本地(C/C++)代码,实现高性能和底层访问。
开发纯 Dart 的 packages
第一步:创建 package
使用 Flutter CLI 创建一个新的 Dart package:
bash
flutter create --template=package my_dart_package
第二步:实现 package
在 lib
目录下编写 Dart 代码,定义你的包的功能。例如,一个简单的数学运算库:
dart
// lib/my_dart_package.dart
library my_dart_package;
class MathUtils {
static int add(int a, int b) {
return a + b;
}
}
开发原生插件类型的 packages
联合插件
Flutter 插件通常包含 Dart 代码和平台特定代码。Dart 代码作为插件的接口,而平台特定代码则实现具体功能。
指定一个插件支持的平台
在 pubspec.yaml
中指定支持的平台:
yaml
flutter:
plugin:
platforms:
android:
package: com.example.myplugin
pluginClass: MyPlugin
ios:
pluginClass: MyPlugin
第一步:创建 package
使用 Flutter CLI 创建一个新的插件:
bash
flutter create --template=plugin my_plugin
第二步:实现 package
在 android
和 ios
目录下实现平台特定代码。例如,在 Android 中:
java
// android/src/main/java/com/example/myplugin/MyPlugin.java
package com.example.myplugin;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import android.app.Activity;
import android.content.Context;
public class MyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
private MethodChannel channel;
private Context applicationContext;
private Activity activity;
@Override
public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "my_plugin");
channel.setMethodCallHandler(this);
applicationContext = flutterPluginBinding.getApplicationContext();
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getPlatformVersion")) {
String version = android.os.Build.VERSION.RELEASE;
result.success(version);
} else {
result.notImplemented();
}
}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
activity = binding.getActivity();
}
@Override
public void onDetachedFromActivityForConfigChanges() {
activity = null;
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
activity = binding.getActivity();
}
@Override
public void onDetachedFromActivity() {
activity = null;
}
}
为现有的插件项目加入平台的支持
如果你已经有一个 Dart package,并想添加平台支持,可以手动添加 android
和 ios
目录,并按照上述方式实现平台代码。
Dart 的平台实现
Dart 代码通过 MethodChannel
与平台代码通信。例如:
dart
// lib/my_plugin.dart
import 'package:flutter/services.dart';
class MyPlugin {
static const MethodChannel _channel = const MethodChannel('my_plugin');
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
测试你的插件
在 example
目录下创建一个 Flutter 应用,用于测试你的插件。
开发 FFI 插件
第 1 步:创建 package
使用 Flutter CLI 创建一个新的 FFI 插件(虽然 Flutter CLI 目前没有直接支持 FFI 插件的模板,但你可以手动创建):
bash
flutter create --template=package my_ffi_plugin
第 2 步:构建和绑定本地原生代码
编写本地(C/C++)代码,并编译成共享库(如 .so
、.dylib
、.dll
)。
第 3 步:绑定本地原生代码
使用 Dart 的 dart:ffi
库绑定本地代码。例如:
dart
// lib/my_ffi_plugin.dart
import 'dart:ffi';
import 'package:ffi/ffi.dart';
typedef NativeAddFunc = NativeFunction<Int32 Function(Int32, Int32)>;
typedef AddFunc = int Function(int, int);
class MyFFIPlugin {
static DynamicLibrary _openLibrary() {
if (Platform.isAndroid) {
return DynamicLibrary.open("libmy_ffi_native.so");
} else if (Platform.isLinux) {
return DynamicLibrary.open("libmy_ffi_native.so");
} else if (Platform.isMacOS) {
return DynamicLibrary.open("libmy_ffi_native.dylib");
} else if (Platform.isWindows) {
return DynamicLibrary.open("my_ffi_native.dll");
} else {
throw UnsupportedError("This platform is not supported");
}
}
static AddFunc? _addFunc;
static int add(int a, int b) {
_addFunc ??= _openLibrary()
.lookup<NativeFunction<Int32 Function(Int32, Int32)>>('add')
.asFunction<AddFunc>();
return _addFunc!(a, b);
}
}
第 4 步:调用本地原生代码
在你的 Dart 代码中调用绑定的函数:
dart
void main() {
print(MyFFIPlugin.add(3, 4)); // 输出 7
}
添加文档
API 文档
在 lib
目录下使用 DartDoc 生成 API 文档。例如,在 my_dart_package.dart
文件顶部添加文档注释:
dart
/// 一个简单的数学运算库。
library my_dart_package;
/// 提供基本的数学运算功能。
class MathUtils {
/// 返回两个整数的和。
static int add(int a, int b) {
return a + b;
}
}
运行 flutter pub publish --dry-run
检查并生成文档。
将许可证添加到 LICENSE 文件中
确保你的包包含一个 LICENSE
文件,描述你的包的许可证类型。
提交 package
使用 flutter pub publish
命令将你的包发布到 pub.dev。
Package 依赖处理
对于不同平台,你可能需要处理特定的依赖。例如,在 android/build.gradle
中添加 Android 依赖,在 ios/Podfile
中添加 iOS 依赖,或在 web/
目录下添加 Web 特定的依赖。
yaml
// android/build.gradle
dependencies {
implementation 'com.google.android.material:material:1.4.0'
}
# ios/Podfile
platform :ios, '10.0'
target 'Runner' do
use_frameworks!
pod 'SomeiOSLibrary', '~> 1.0'
end
Flutter Lint
package:flutter_lints
是Flutter官方推荐的一个linting包,用于鼓励良好的编码实践。以下是package:flutter_lints
的详细使用流程:
1、前提条件
确保你的Flutter开发环境已经正确配置,并且你的项目是基于Flutter框架构建的。
2、添加依赖
- 打开项目:使用你喜欢的IDE(如Android Studio、VSCode等)打开你的Flutter项目。
- 编辑
pubspec.yaml
:在项目的根目录下找到pubspec.yaml
文件,并添加flutter_lints
作为开发依赖(dev_dependency)。
yaml
dev_dependencies:
flutter_lints: ^最新版本号
请注意,你应该使用flutter_lints
的最新稳定版本。你可以在pub.dev上找到最新版本号。
- 运行
flutter pub get
:在终端中运行flutter pub get
命令,以安装新添加的依赖。
3、创建或更新analysis_options.yaml
- 创建文件 :如果你的项目根目录下还没有
analysis_options.yaml
文件,你需要创建一个。 - 添加内容 :在
analysis_options.yaml
文件中,添加以下内容来激活flutter_lints
中的推荐lint规则。
yaml
yaml复制代码
include: package:flutter_lints/flutter.yaml
如果你的项目已经有一个自定义的analysis_options.yaml
文件,你只需要在文件的顶部添加上述include:
指令即可。
4、配置自定义lint规则(可选)
如果你想自定义lint规则,你可以在analysis_options.yaml
文件中添加或修改linter:
部分下的rules:
子部分。例如:
yaml
linter:
rules:
avoid_print: false # 禁用avoid_print规则
prefer_single_quotes: true # 启用prefer_single_quotes规则
你可以根据需要启用或禁用特定的lint规则。所有可用的lint规则及其文档可以在Dart Linter规则索引上找到。
5、运行lint分析
- 使用IDE:大多数支持Dart的IDE都会自动显示lint分析器识别到的问题。你可以在IDE的用户界面中查看这些问题,并根据需要进行修复。
- 使用命令行 :你也可以通过运行
flutter analyze
命令来手动调用lint分析器。在终端中运行此命令,lint分析器将扫描你的项目代码,并输出任何识别到的问题。
6、修复问题
根据lint分析器识别到的问题,你需要对代码进行相应的修复。修复可能包括重构代码、添加缺失的注释、修复类型错误等。
7、持续集成(可选)
如果你的项目使用了持续集成(CI)服务,你可以配置CI服务在每次代码更改时自动运行flutter analyze
命令。这将确保你的代码始终符合良好的编码实践,并在问题出现时及时得到通知。
主题
Flutter中的主题(Theme)是应用于整个应用程序、屏幕或视图层次结构的样式集合,它可以帮助开发者统一应用的整体风格,提高用户体验。以下是对Flutter主题的详细介绍:
1、主题的定义与设置
在Flutter中,主题是通过ThemeData
类来定义的。ThemeData
类包含了应用的颜色、字体、图标等样式信息。通过设置主题,可以实现应用的整体风格统一。
-
定义主题:
在Flutter应用中,可以通过
MaterialApp
组件的theme
属性来设置全局主题。例如:dartvoid main() { runApp(MaterialApp( home: MyAppHome(), theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.blueAccent, fontFamily: 'Arial', ), )); }
在上面的代码中,我们定义了一个全局主题,其中主色调为蓝色,辅助色调为蓝色点缀色,字体系列为Arial。
-
使用主题:
在Flutter组件中,可以通过
Theme.of(context)
方法来获取当前主题,并使用主题中的样式。例如:dartText('Hello World', style: Theme.of(context).textTheme.headline6)
在上面的代码中,我们获取了当前主题中的
headline6
文本样式,并将其应用于Text
组件。
2、主题的自定义与扩展
Flutter提供了丰富的自定义和扩展主题的功能,以满足不同开发者的需求。
-
自定义颜色:
可以通过在
ThemeData
中设置colorScheme
属性来自定义颜色方案。例如:dartThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.green, background: Colors.white, error: Colors.red, onTertiary: Colors.orange, ), )
在上面的代码中,我们定义了一个基于绿色种子颜色的颜色方案,并设置了背景色、错误色和次要文字颜色等。
-
自定义文本样式:
可以通过在
ThemeData
中设置textTheme
属性来自定义文本样式。例如:dartThemeData( textTheme: TextTheme( headline6: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), bodyText2: TextStyle(fontSize: 16, color: Colors.grey), ), )
在上面的代码中,我们自定义了
headline6
和bodyText2
两种文本样式。 -
自定义AppBar样式:
可以通过在
ThemeData
中设置appBarTheme
属性来自定义AppBar的样式。例如:dartThemeData( appBarTheme: AppBarTheme( elevation: 0, color: Colors.white, iconTheme: IconThemeData(color: Colors.black), textTheme: TextTheme( headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), )
在上面的代码中,我们自定义了AppBar的高度、背景色、图标颜色和标题样式。
-
自定义按钮样式:
可以通过在
ThemeData
中设置buttonTheme
属性来自定义按钮的样式。例如:dartThemeData( buttonTheme: ButtonThemeData( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), buttonColor: Colors.blue, textTheme: ButtonTextTheme.primary, ), )
在上面的代码中,我们自定义了按钮的形状、颜色和文本样式。
3、动态主题切换
Flutter还支持动态主题切换功能,可以根据用户的偏好或系统设置自动切换主题。
-
实现动态主题切换:
可以通过使用
ThemeMode
枚举来定义主题模式(亮色模式、深色模式或系统模式),并通过在MaterialApp
组件中设置themeMode
属性来实现动态主题切换。例如:dartclass MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { ThemeMode _themeMode = ThemeMode.system; @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Dynamic Theme', theme: ThemeData.light(), darkTheme: ThemeData.dark(), themeMode: _themeMode, home: HomePage( onThemeChanged: (ThemeMode themeMode) { setState(() { _themeMode = themeMode; }); }, ), ); } } class HomePage extends StatelessWidget { final Function(ThemeMode) onThemeChanged; const HomePage({super.key, required this.onThemeChanged}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('动态主题切换')), body: Column( children: [ ElevatedButton( onPressed: () => onThemeChanged(ThemeMode.light), child: Text('切换到亮色模式'), ), ElevatedButton( onPressed: () => onThemeChanged(ThemeMode.dark), child: Text('切换到深色模式'), ), ElevatedButton( onPressed: () => onThemeChanged(ThemeMode.system), child: Text('跟随系统设置'), ), ], ), ); } }
在上面的代码中,我们定义了一个支持动态主题切换的Flutter应用。通过点击按钮,可以在亮色模式、深色模式和系统模式之间切换。
-
自定义主题:
除了支持动态主题切换外,Flutter还支持自定义主题。可以通过在
MaterialApp
组件中设置theme
属性为自定义的ThemeData
对象来实现自定义主题。例如:dartThemeData _customTheme() { return ThemeData( scaffoldBackgroundColor: Colors.yellow, // 其他自定义样式... ); } // 在MaterialApp中使用自定义主题 MaterialApp( title: 'Flutter Custom Theme', theme: _customTheme(), // 其他属性... )
在上面的代码中,我们定义了一个自定义主题,并将其应用于
MaterialApp
组件中。
4、Material 3迁移中的变化和更新
-
默认主题变更
- 从Flutter 3.16版本开始,
useMaterial3
标志默认为true
,这意味着应用程序将自动采用Material 3设计规范的主题。
- 从Flutter 3.16版本开始,
-
回退到Material 2
- 尽管
useMaterial3
默认为true
,但开发者仍然可以通过将useMaterial3
设置为false
来暂时回退到Material 2的行为。然而,这只是一个临时解决方案,Material 2的实现和useMaterial3
标志最终将被移除。
- 尽管
-
颜色方案的更新
ThemeData.colorScheme
的默认值已更新以匹配Material 3设计规范。- 引入了
ColorScheme.fromSeed
构造函数,它可以根据给定的种子颜色生成一个符合Material 3设计系统要求的颜色方案。 - 迁移时,建议使用
ColorScheme.fromSeed
生成的颜色方案来确保UI的正确显示。
-
背景色和表面着色
- Material 3引入了一个新的背景色
ColorScheme.surfaceTint
,它表示一个提升(elevated)组件的着色。 - 一些组件可能会同时使用
surfaceTint
和shadowColor
来表示提升效果。
- Material 3引入了一个新的背景色
-
文字主题的更新
ThemeData.textTheme
的默认值已更新以匹配Material 3的默认设置。- 更新包括字体大小、字体粗细、字母间距和行高等。
-
组件的迁移
- 一些组件无法仅通过更新来匹配Material 3设计规范,而需要全新的实现。这些组件需要手动迁移。
- 例如,
BottomNavigationBar
已被替换为新的NavigationBar
,Drawer
已被替换为NavigationDrawer
。
-
应用栏的变化
- Material 3引入了中等和大型应用栏,它们在滚动前会显示一个较大的标题。
- 滚动时,不再使用阴影,而是使用
ColorScheme.surfaceTint
颜色来与内容创建分隔。
-
选项卡栏的变化
- 现在有两种类型的
TabBar
组件:主要(primary)和次要(secondary)。 - 次要选项卡用于内容区域内部,以进一步分隔相关内容并建立层次结构。
- 现在有两种类型的
-
分段按钮
SegmentedButton
是ToggleButtons
的更新版本,具有完全圆角、不同的布局高度和大小,并使用DartSet
来确定选定的项目。
-
迁移代码示例
- 文章中提供了多个迁移代码示例,包括颜色方案的迁移、组件的替换以及应用栏的实现等。
国际化
Flutter的国际化与本地化功能使得应用能够在不同的语言和地区环境中运行,满足不同用户的文化和语言需求。以下是对Flutter应用本地化的全面介绍,涵盖从配置到高级操作的各个方面。
国际化介绍
一、配置一个国际化的app:flutter_localizations package
-
安装依赖
- 在
pubspec.yaml
文件中添加flutter_localizations
依赖。
- 在
-
配置本地化资源
- 在
lib/l10n
目录下创建ARB(Application Resource Bundle)文件,用于存储不同语言的翻译资源。 - 每个语言或地区对应一个子目录,子目录中包含一个名为
messages_xx.arb
(xx为语言代码,如en、zh等)的文件。
- 在
-
配置MaterialApp
- 在
MaterialApp
的构造函数中,通过localizationsDelegates
和supportedLocales
参数配置本地化支持。 localizationsDelegates
参数指定了哪些本地化代理将被用于加载本地化资源。supportedLocales
参数定义了应用支持的语言环境列表。
- 在
二、重载语言
通过更改MaterialApp
的locale
属性,可以实现语言的动态切换。这通常涉及到一个状态管理,以便在应用的不同部分之间共享当前的语言设置。
三、添加你自己的本地化信息
-
创建ARB文件
- 在
lib/l10n
目录下的相应语言子目录中,创建或编辑ARB文件,添加或更新翻译资源。
- 在
-
使用占位符
- 在ARB文件中,可以使用占位符来定义可替换的文本片段。这有助于在翻译时保持文本的灵活性和一致性。
-
处理复数和选项
- Flutter的本地化系统支持复数和选项的处理。在ARB文件中,可以使用特定的语法来定义这些规则。
四、避免语法解析错误
在编写ARB文件时,需要注意避免语法解析错误。确保文件的格式正确,键和值都使用双引号括起来,并且遵循ARB文件的规范。
五、包含数字和货币的信息
在本地化过程中,需要特别注意数字和货币的格式。这些格式可能因地区而异,因此需要根据目标地区的习惯进行调整。
六、带日期的信息
日期和时间的格式也是本地化过程中需要重点关注的内容。Flutter提供了相应的API来处理不同地区的日期和时间格式。
七、iOS 本地化:更新 iOS app bundle
对于iOS应用,除了配置Flutter的本地化资源外,还需要更新iOS app bundle中的相关信息,以确保应用能够正确显示本地化的内容。
八、定制的进阶操作
-
高级语言环境定义
- 可以根据需要定义更复杂的语言环境设置,以满足特定地区或用户群体的需求。
-
获取语言环境
- 使用
Locale
类和Localizations
Widget可以获取当前的语言环境信息。
- 使用
九、配置l10n.yaml文件
l10n.yaml
文件是Flutter Intl插件用于生成和管理本地化资源的配置文件。通过配置这个文件,可以方便地添加、删除和更新语言环境。
十、Flutter里的国际化是如何工作的
Flutter的国际化系统基于ARB文件和flutter_localizations
包实现。在运行时,Flutter会根据当前的语言环境加载相应的ARB文件,并使用其中的翻译资源来替换应用中的文本。
十一、加载和获取本地化值
在Flutter应用中,可以使用Localizations.of<T>(context)
方法获取当前上下文的本地化对象,并通过该对象访问翻译后的字符串。
十二、为app的本地化资源定义一个类
为了更方便地管理本地化资源,可以定义一个类来封装与本地化相关的逻辑和数据。这个类可以包含加载本地化资源、获取翻译字符串等方法。
十三、添加支持新的语言
要添加对新的语言的支持,只需在lib/l10n
目录下创建相应的语言子目录和ARB文件,并在pubspec.yaml
文件中添加相应的语言代码即可。然后运行flutter gen-l10n
命令来生成新的本地化资源文件。
十四、其他的国际化方法
除了使用ARB文件和flutter_localizations
包进行国际化外,Flutter还支持其他国际化方法,如使用JSON或XML文件存储翻译资源等。这些方法可以根据项目的具体需求进行选择。
十五、应用程序本地化资源的替代类
在某些情况下,可能需要使用替代类来管理应用程序的本地化资源。这些替代类可以提供更灵活或更强大的本地化功能,以满足特定项目的需求。
示例介绍
一、安装依赖
首先,在pubspec.yaml
文件中添加必要的依赖:
yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.19.0 # 或者最新版本
flutter_intl:
enabled: true
# 其他配置参数...
然后,运行flutter pub get
命令来安装这些依赖。
二、配置ARB文件
在lib/l10n
目录下创建两个ARB文件,分别用于存储英文和中文的翻译资源。
英文ARB文件(intl_en.arb
)
json
{
"home": "Home",
"settingLanguage": "Set Language",
"languageName_en": "English",
"languageName_zh": "Simplified Chinese",
"login_pageName": "Login Page",
"login_title": "Test App Demo",
"login_userName": "Username",
"login_userName_empty": "Username can't be empty!",
"login_password": "Password",
"login_password_empty": "Password can't be empty!",
"login_btn": "Login"
}
中文ARB文件(intl_zh.arb
)
json
{
"home": "首页",
"settingLanguage": "语言设置",
"languageName_en": "英语",
"languageName_zh": "简体中文",
"login_pageName": "登录页",
"login_title": "App测试",
"login_userName": "用户名",
"login_userName_empty": "用户名不能为空!",
"login_password": "密码",
"login_password_empty": "密码不能为空!",
"login_btn": "登录"
}
三、配置MaterialApp
在main.dart
文件中,配置MaterialApp
以支持国际化:
dart
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:your_app_name/l10n.dart'; // 导入生成的本地化资源文件
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// ... 主题配置
),
localizationsDelegates: const [
S.delegate, // 本地化的代理类
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales, // 应用支持的语言环境列表
locale: Locale('zh'), // 默认语言环境,可以根据需要更改为其他语言
home: MyHomePage(),
);
}
}
四、使用国际化字符串
在应用的各个页面中,可以使用S.of(context).xxx
的方式来获取国际化后的字符串。例如:
dart
import 'package:flutter/material.dart';
import 'package:your_app_name/l10n.dart'; // 导入生成的本地化资源文件
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(S.of(context).home), // 使用国际化后的字符串
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(S.of(context).login_title),
TextField(
decoration: InputDecoration(
labelText: S.of(context).login_userName,
),
),
TextField(
decoration: InputDecoration(
labelText: S.of(context).login_password,
),
obscureText: true,
),
ElevatedButton(
onPressed: () {
// 按钮点击事件处理
},
child: Text(S.of(context).login_btn),
),
],
),
),
);
}
}
五、动态切换语言
为了实现语言的动态切换,可以在应用中添加一个语言选择器,并在选择语言时更新MaterialApp
的locale
属性。这通常涉及到一个状态管理,如使用Provider
、GetX
或Riverpod
等状态管理库。
原生集成flutter
集成Flutter限制
原生集成Flutter存在一些限制,这些限制主要源于Flutter与原生平台(如Android和iOS)之间的差异以及Flutter自身的架构设计。以下是一些主要的限制:
-
移动端不支持多视图模式(仅限多引擎)。
-
移动端不支持将多个 Flutter 库(Flutter 模块)同时打包进一个应用。
-
移动端不支持
FlutterPlugin
的插件如果在 add-to-app 进行一些不合理的假设(例如假设 Flutter 的Activity
始终存在),可能会出现意外行为。 -
Android 平台的 Flutter 模块仅支持适配了 AndroidX 的应用。
-
Web 端不支持多引擎模式(仅限多视图)。
-
Web 端无法完全"关闭" Flutter 引擎。应用程序可以移除所有 FlutterView 对象,并确保所有数据通过 Dart 常规的垃圾回收机制被清理。然而,即使引擎不再渲染任何内容,它仍会保持预热状态。
- 架构和兼容性限制 :
- Flutter主要支持特定的CPU架构,如armeabi-v7a和arm64-v8a,这意味着在某些老旧或非主流的设备上可能无法运行Flutter应用。
- Flutter与原生代码的交互需要一定的桥接机制,这可能会引入一些兼容性问题。
- 性能和资源限制 :
- Flutter引擎需要占用一定的内存和CPU资源,这可能会影响原生应用的性能,特别是在资源受限的设备上。
- Flutter的渲染机制与原生渲染机制不同,可能会导致在某些复杂场景下渲染性能的差异。
- 插件和库的限制 :
- 虽然Flutter拥有丰富的插件生态系统,但并非所有原生库和插件都有对应的Flutter版本。这可能需要开发者自己编写桥接代码或使用其他替代方案。
- Flutter插件的更新速度可能滞后于原生库的更新速度,这可能导致一些新功能或修复无法及时在Flutter应用中实现。
- 开发和调试限制 :
- 在原生项目中集成Flutter后,开发和调试过程可能会变得更加复杂。开发者需要同时熟悉Flutter和原生平台的开发工具和调试技巧。
- Flutter的热重载和热替换功能在原生集成环境中可能无法完全发挥作用,这会影响开发效率。
- 版本配套和依赖关系 :
- Flutter播放器SDK等特定组件与Flutter SDK存在一定的配套关系。例如,某个版本的Flutter播放器SDK可能仅支持特定版本的Flutter SDK。这需要在集成时仔细核对版本信息。
- Flutter和原生平台的依赖关系也可能导致一些兼容性问题。例如,Flutter可能依赖于特定版本的Android Gradle Plugin或Xcode等工具链。
- 部署和发布限制 :
- 在将原生集成Flutter的应用部署到应用商店时,可能需要遵循特定的规则和指南。例如,苹果应用商店可能要求Flutter应用使用特定的构建配置和签名方式。
- Flutter应用的包大小和下载速度也可能受到原生平台限制的影响。
iOS 中 Flutter页面
本段落示例代码收集自Flutter官方文档
一、启动FlutterEngine和FlutterViewController
为了在iOS应用中展示Flutter页面,需要启动FlutterEngine和FlutterViewController。FlutterEngine充当Dart VM和Flutter运行时的主机,而FlutterViewController则依附于FlutterEngine,传递UIKit的输入事件,并展示被FlutterEngine渲染的每一帧画面。
1. 创建一个FlutterEngine
创建FlutterEngine的位置取决于宿主类型。以下示例在SwiftUI项目中创建了一个FlutterEngine对象:
swift
import SwiftUI
import Flutter
// 导入Flutter插件与iOS平台代码的连接库
import FlutterPluginRegistrant
@ObservableObject class FlutterDependencies {
let flutterEngine = FlutterEngine(name: "my flutter engine")
init() {
// 运行默认的Dart入口点和Flutter路由
flutterEngine.run()
// 将插件与iOS平台代码连接到此应用
GeneratedPluginRegistrant.register(with: self.flutterEngine)
}
}
@main
struct MyApp: App {
// 通过视图环境注入FlutterDependencies
@StateObject var flutterDependencies = FlutterDependencies()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(flutterDependencies)
}
}
}
2. 使用FlutterEngine展示FlutterViewController
以下示例展示了如何创建一个FlutterViewControllerRepresentable来代表FlutterViewController,并通过视图环境注入FlutterEngine:
swift
import SwiftUI
import Flutter
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
// 通过视图环境获取FlutterDependencies
@EnvironmentObject var flutterDependencies
func makeUIViewController(context: Context) -> some UIViewController {
return FlutterViewController(engine: flutterDependencies.flutterEngine, nibName: nil, bundle: nil)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// 无需在此处进行更新操作
}
}
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink("My Flutter Feature") {
FlutterViewControllerRepresentable()
}
}
}
}
二、隐式创建FlutterEngine
虽然推荐预热一个"长寿"的FlutterEngine以提高性能,但在某些情况下(如Flutter页面很少被展示时),可以选择让FlutterViewController隐式创建自己的FlutterEngine:
swift
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
return FlutterViewController(project: nil, nibName: nil, bundle: nil)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// 无需在此处进行更新操作
}
}
三、使用FlutterAppDelegate
推荐让应用的UIApplicationDelegate继承FlutterAppDelegate,以利用其功能(如传递openURL回调到Flutter插件)。以下是在SwiftUI项目中创建FlutterAppDelegate子类的示例:
swift
import SwiftUI
import Flutter
import FlutterPluginRegistrant
@ObservableObject class AppDelegate: FlutterAppDelegate {
let flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
flutterEngine.run()
// 连接插件(如果插件包含iOS平台代码)
GeneratedPluginRegistrant.register(with: self.flutterEngine)
return true
}
}
@main
struct MyApp: App {
// 告诉SwiftUI使用AppDelegate类作为应用委托
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView().environmentObject(appDelegate)
}
}
}
// 在FlutterViewControllerRepresentable中使用AppDelegate的FlutterEngine
struct FlutterViewControllerRepresentable: UIViewControllerRepresentable {
// 通过视图环境获取AppDelegate
@EnvironmentObject var appDelegate
func makeUIViewController(context: Context) -> some UIViewController {
return FlutterViewController(engine: appDelegate.flutterEngine, nibName: nil, bundle: nil)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// 无需在此处进行更新操作
}
}
四、不能直接继承FlutterAppDelegate的情况
如果AppDelegate不能直接继承FlutterAppDelegate,可以让其实现FlutterAppLifeCycleProvider协议,以确保Flutter插件接收到必要的回调:
swift
import Foundation
import Flutter
@ObservableObject class AppDelegate: UIResponder, UIApplicationDelegate, FlutterAppLifeCycleProvider {
private let lifecycleDelegate = FlutterPluginAppLifeCycleDelegate()
let flutterEngine = FlutterEngine(name: "my flutter engine")
// 实现UIApplicationDelegate方法,并通过lifecycleDelegate转发回调
// ...(此处省略了具体实现方法)
func add(_ delegate: FlutterApplicationLifeCycleDelegate) {
lifecycleDelegate.add(delegate)
}
}
五、定制Flutter运行时
可以通过指定Dart入口、库和路由来定制Flutter运行时:
- Dart入口 :在FlutterEngine上调用
run
方法时,默认会调用lib/main.dart
文件中的main
函数。也可以使用runWithEntrypoint
方法并指定另一个Dart入口。 - Dart库:在指定Dart函数时,可以指定特定文件的特定函数。
六、其他注意事项
- 在FlutterViewController展示Flutter UI之前,可以通过编写双端平台代码来推入数据和准备Flutter环境。
- 本文档提及的内容适用于Flutter的最新稳定版本。
通过以上步骤和示例,可以轻松地在iOS项目中集成Flutter页面,并根据实际需求选择最佳集成方式。
Android 中 Flutter Fragment
概述
本指南介绍如何向现有的 Android 应用中添加 FlutterFragment
。FlutterFragment
允许开发者在任何使用常规 Fragment
的地方呈现 Flutter 的内容。通过 FlutterFragment
,开发者可以控制 Flutter 的初始路由、Dart 入口、背景透明度等细节。
使用新的 FlutterEngine 添加 FlutterFragment
-
实例化并绑定 FlutterFragment
在
Activity
的onCreate()
方法中,实例化FlutterFragment
并将其添加到Activity
中。kotlinclass MyActivity : FragmentActivity() { companion object { private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment" } private var flutterFragment: FlutterFragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.my_activity_layout) val fragmentManager: FragmentManager = supportFragmentManager flutterFragment = fragmentManager.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment? if (flutterFragment == null) { val newFlutterFragment = FlutterFragment.createDefault() flutterFragment = newFlutterFragment fragmentManager.beginTransaction() .add(R.id.fragment_container, newFlutterFragment, TAG_FLUTTER_FRAGMENT) .commit() } } }
-
处理系统回调
为了使
FlutterFragment
如预期一样正常工作,需要将系统回调从Activity
传递到FlutterFragment
。kotlinclass MyActivity : FragmentActivity() { override fun onPostResume() { super.onPostResume() flutterFragment!!.onPostResume() } override fun onNewIntent(intent: Intent) { flutterFragment!!.onNewIntent(intent) } override fun onBackPressed() { flutterFragment!!.onBackPressed() } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { flutterFragment!!.onRequestPermissionsResult(requestCode, permissions, grantResults) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) flutterFragment!!.onActivityResult(requestCode, resultCode, data) } override fun onUserLeaveHint() { flutterFragment!!.onUserLeaveHint() } override fun onTrimMemory(level: Int) { super.onTrimMemory(level) flutterFragment!!.onTrimMemory(level) } }
使用预热的 FlutterEngine
为了减少 FlutterFragment 的初始化时间,可以使用已存在的、预热的 FlutterEngine
。
-
在应用启动时预热 FlutterEngine
kotlinclass MyApplication : Application() { lateinit var flutterEngine: FlutterEngine override fun onCreate() { super.onCreate() flutterEngine = FlutterEngine(this) flutterEngine.navigationChannel.setInitialRoute("your/route/here") flutterEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine) } }
-
在 FlutterFragment 中使用预热的 FlutterEngine
kotlinval flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").build()
指定初始路由和 Dart 入口
-
指定初始路由
使用
FlutterFragment.Builder
指定初始路由(仅适用于新的 FlutterEngine)。kotlinval flutterFragment = FlutterFragment.withNewEngine() .initialRoute("your/custom/route") .build()
注意:当使用已预热的 FlutterEngine 时,指定的初始路由无效。
-
指定 Dart 入口
使用
FlutterFragment.Builder
指定 Dart 入口(仅适用于新的 FlutterEngine)。kotlinval flutterFragment = FlutterFragment.withNewEngine() .dartEntrypoint("mySpecialEntrypoint") .build()
注意:当使用已预热的 FlutterEngine 时,指定的 Dart 入口无效。
控制 FlutterFragment 的渲染模式和透明度
-
选择渲染模式
默认使用
SurfaceView
渲染,性能较好,但无法插入到 Android 的 View 层级中。如需使用TextureView
,可以指定渲染模式。kotlinval flutterFragment = FlutterFragment.withNewEngine() .renderMode(FlutterView.RenderMode.texture) .build()
-
设置透明度
默认背景不透明,可以设置为透明。
kotlinval flutterFragment = FlutterFragment.withNewEngine() .transparencyMode(FlutterView.TransparencyMode.transparent) .build()
FlutterFragment 与 Activity 的关系
使用 shouldAttachEngineToActivity()
方法决定 FlutterFragment 是否应该控制宿主 Activity。
kotlin
val flutterFragment = FlutterFragment.withNewEngine()
.shouldAttachEngineToActivity(false) // 防止 Flutter 控制 Activity 的系统 UI
.build()
通过以上步骤,您可以成功地将 FlutterFragment 集成到现有的 Android 应用中,并根据需要配置各种场景
iOS多页面 FlutterEngineGroup
在 Swift 中使用 FlutterEngineGroup
可以帮助你在 iOS 应用中高效地管理和复用多个 Flutter 引擎实例。下面是一个关于如何在 Swift 中使用 FlutterEngineGroup
的详细指南,包括各种代码示例。
1. 设置 Flutter 依赖
首先,确保你的 iOS 项目已经集成了 Flutter。这通常涉及将 Flutter 模块作为依赖添加到你的原生 iOS 项目中,并配置好相关的 build 设置。
2. 初始化 FlutterEngineGroup
你通常会在应用的入口点(如 AppDelegate
)中初始化 FlutterEngineGroup
。
swift
import UIKit
import Flutter
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var flutterEngineGroup: FlutterEngineGroup?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 初始化 FlutterEngineGroup
flutterEngineGroup = FlutterEngineGroup(name: "my_engine_group", project: nil)
// 其他配置...
return true
}
}
3. 创建和配置 FlutterEngine
当你需要展示一个 Flutter 页面时,你可以从 FlutterEngineGroup
中获取或创建一个 FlutterEngine
实例。
swift
import UIKit
import Flutter
class SomeViewController: UIViewController {
var flutterEngine: FlutterEngine?
var flutterViewController: FlutterViewController?
override func viewDidLoad() {
super.viewDidLoad()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let flutterEngineGroup = appDelegate.flutterEngineGroup else {
return
}
// 从 FlutterEngineGroup 中获取或创建一个 FlutterEngine 实例
flutterEngine = flutterEngineGroup.makeEngine(withEntrypoint: nil, libraryURI: nil)
// 使用 FlutterEngine 实例初始化 FlutterViewController
flutterViewController = FlutterViewController(engine: flutterEngine!, nibName: nil, bundle: nil)
// 将 FlutterViewController 添加到当前视图控制器中
addChild(flutterViewController!)
view.addSubview(flutterViewController!.view)
flutterViewController!.didMove(toParent: self)
}
}
4. 展示 Flutter 页面
在上面的代码中,flutterViewController
已经被添加到 SomeViewController
的视图中。你可以根据需要将 SomeViewController
添加到你的应用中的任何位置,比如作为根视图控制器、子视图控制器或模态呈现。
5. 清理资源
当 FlutterViewController
不再需要时,你应该确保正确地清理资源。这通常涉及移除 FlutterViewController
的视图并从父视图控制器中移除它。
swift
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
flutterViewController?.willMove(toParent: nil)
flutterViewController?.view.removeFromSuperview()
flutterViewController?.removeFromParent()
// 如果这是最后一个使用这个 FlutterEngine 的地方,你可以考虑销毁它
// 注意:通常不建议手动销毁 FlutterEngine,因为 FlutterEngineGroup 会管理它们的生命周期
// flutterEngine = nil // 这不会真正销毁 FlutterEngine,只是将其置为 nil
}
注意 :在大多数情况下,你不应该手动销毁 FlutterEngine
实例。FlutterEngineGroup
会负责管理和缓存 FlutterEngine
实例的生命周期。如果你不再需要某个 FlutterEngine
,并且确信没有其他地方在使用它,你可以将其从缓存中移除(尽管 Flutter SDK 可能不提供直接的方法来做到这一点),但通常这是不必要的,因为 FlutterEngine
的内存管理是由 Flutter 框架自动处理的。
6. 额外的配置和注意事项
- 确保你的
Info.plist
文件中包含了必要的 Flutter 配置。 - 如果你正在使用 Flutter 模块而不是整个 Flutter 应用,你可能需要额外的配置来确保模块正确加载。
- 当你使用
FlutterEngineGroup
时,你可以通过传递相同的组名来确保多个地方共享相同的FlutterEngine
实例(尽管这通常不是必需的,因为FlutterEngineGroup
会为你管理这些实例)。 - 始终检查
FlutterEngine
和FlutterViewController
是否为nil
,以避免在它们尚未初始化时访问它们的属性或方法。
Android多页面 FlutterEngineGroup
在 Kotlin 中使用 FlutterEngineGroup
是为了在 Android 应用中高效地管理和复用 Flutter 引擎实例。这对于需要在多个地方嵌入 Flutter 视图的应用特别有用,因为它可以减少内存占用并提高性能。
以下是在 Kotlin 中使用 FlutterEngineGroup
的详细指南,包括各种代码示例。
1. 添加 Flutter 依赖
首先,确保你的 Android 项目已经集成了 Flutter。这通常涉及将 Flutter 模块作为依赖添加到你的原生 Android 项目中,并配置好相关的 build.gradle 文件。
2. 初始化 FlutterEngineGroup
你通常会在应用的入口点(如 Application
类)中初始化 FlutterEngineGroup
。
kotlin
import android.app.Application
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineGroup
import io.flutter.embedding.android.FlutterActivity
class MyApplication : Application() {
private lateinit var flutterEngineGroup: FlutterEngineGroup
override fun onCreate() {
super.onCreate()
// 初始化 FlutterEngineGroup
flutterEngineGroup = FlutterEngineGroup(this, "my_engine_id")
// 可以在这里预创建 FlutterEngine 实例,以便更快地显示 Flutter 视图
// val precreatedEngine = flutterEngineGroup.makeEngine(null)
}
// 提供一个全局访问点来获取 FlutterEngineGroup 实例
fun getFlutterEngineGroup(): FlutterEngineGroup {
return flutterEngineGroup
}
}
别忘了在 AndroidManifest.xml
中将你的 Application
类设置为应用的入口点。
3. 创建和配置 FlutterEngine
当你需要展示一个 Flutter 页面时,你可以从 FlutterEngineGroup
中获取或创建一个 FlutterEngine
实例,并使用它来启动一个 FlutterActivity
或 FlutterFragment
。
kotlin
import android.content.Context
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
// 在某个 Activity 或 Fragment 中
fun showFlutterScreen(context: Context) {
val application = context.applicationContext as MyApplication
val flutterEngineGroup = application.getFlutterEngineGroup()
// 从 FlutterEngineGroup 中获取或创建一个 FlutterEngine 实例
val flutterEngine: FlutterEngine = flutterEngineGroup.makeEngine(null)
// 配置 FlutterActivity 的启动参数
val flutterActivityIntent = FlutterActivity
.withCachedEngine("my_engine_id") // 使用相同的 ID 来复用 FlutterEngine 实例
.build(context)
// 启动 FlutterActivity
context.startActivity(flutterActivityIntent)
}
注意 :在上面的代码中,withCachedEngine
方法的参数应该是你在 FlutterEngineGroup
构造函数中使用的相同 ID。但是,如果你只是想从 FlutterEngineGroup
中获取一个新的或现有的引擎,并不关心是否缓存了特定的引擎,你可以省略这个 ID(尽管这通常不是最佳实践,因为它会失去使用 FlutterEngineGroup
的主要优势)。实际上,你应该使用 flutterEngineGroup.makeEngine(null)
来创建一个新的引擎(如果还没有缓存的话),并通过其他机制(如传递额外的启动参数)来区分不同的 Flutter 视图。
然而,由于 FlutterActivity.withCachedEngine
方法期望一个已经存在的、通过特定 ID 缓存的引擎,因此上面的代码示例可能并不完全准确。在大多数情况下,你可能不需要显式地指定引擎 ID 来复用引擎,因为 FlutterEngineGroup
会自动管理引擎的生命周期和缓存。相反,你可以简单地调用 flutterEngineGroup.makeEngine(null)
来获取一个新的或现有的引擎,并使用 FlutterActivity.withNewEngine(flutterEngine)
来启动一个带有新引擎的 FlutterActivity
。但是,由于 FlutterActivity
的 API 可能会随着 Flutter 的更新而变化,因此请务必查阅最新的 Flutter 文档以获取准确的信息。
4. 展示 Flutter 页面
上面的代码示例已经展示了如何通过 FlutterActivity
来展示 Flutter 页面。如果你更喜欢使用 FlutterFragment
,你可以类似地配置并添加到你的 Activity 中。
5. 清理资源
通常,你不需要手动清理 FlutterEngine
实例,因为 FlutterEngineGroup
会负责管理和缓存它们。但是,如果你确定某个 FlutterEngine
不再需要,并且想要释放它占用的资源,你可以调用 destroy()
方法来销毁它(尽管这通常不是必需的)。然而,请注意,直接销毁 FlutterEngine
可能会导致未定义的行为,因为 Flutter 框架可能仍然在使用它。因此,在大多数情况下,你应该允许 FlutterEngineGroup
来管理 FlutterEngine
的生命周期。
6. 额外的配置和注意事项
- 确保你的
build.gradle
文件中包含了必要的 Flutter 依赖。 - 如果你正在使用 Flutter 模块而不是整个 Flutter 应用,你可能需要额外的配置来确保模块正确加载。
- 当使用
FlutterEngineGroup
时,确保在需要展示 Flutter 视图的地方正确地获取和使用FlutterEngine
实例。 - 始终检查
FlutterEngine
和其他相关对象是否为null
,以避免在它们尚未初始化时访问它们的属性或方法。
加载顺序
关于控制加载顺序以优化性能与内存的部分,对于add-to-app场景下的性能优化尤为重要。以下是加载Flutter UI时的关键步骤梳理:
1. 查找Flutter资源
- 在Android和iOS上,Flutter引擎运行时和已编译的Dart代码被打包为共享库。
- 当首次调用API构建FlutterEngine时,会在.apk、.ipa或.app中查找这些资源(包括图像、字体以及JIT代码等)。
2. 加载Flutter库
- 引擎的共享库在每个进程中加载一次内存。
- 在Android上,构建FlutterEngine时会加载库;在iOS上,首次运行FlutterEngine时会加载。
3. 启动Dart VM
- Dart运行时管理Dart内存与异步。
- 在Android上首次构建FlutterEngine,以及在iOS上首次运行Dart入口时,会完成Dart VM的启动。
- Dart代码的snapshot从应用程序文件加载到内存中。
- Dart VM启动后不会关闭。
4. 创建并运行Dart Isolate
- 在Dart运行时中启动Dart Isolate。
- 每个FlutterEngine实例存在一个isolate,且同一个Dart VM可以承载多个isolate。
- 在Android上调用DartExecutor.executeDartEntrypoint(),在iOS上调用runWithEntrypoint:时,会发生这一过程。
- Dart代码执行默认的入口点方法(如main.dart文件的main()方法),然后Flutter应用或库的widget树被创建和构建。
5. 将UI挂载到Flutter引擎
- 在add-to-app场景中,通过特定的方法(如Android的FlutterActivity.withCachedEngine()和iOS的initWithEngine: nibName: bundle:)将FlutterEngine挂载到UI组件。
- 如果在没有启动Flutter UI组件的情况下预热FlutterEngine,也会创建一个隐式的FlutterEngine。
- UI组件为FlutterEngine提供渲染层,将Layer树转换为GPU指令。
6. 内存和延迟考虑
- 显示Flutter UI会耗费时间,提前启动Flutter引擎可以降低时间开销。
- 预热FlutterEngine会占用内存和时间,但可以降低后续加载UI首帧的成本。
- 需要权衡预热时机,以避免内存占用延迟和Flutter引擎初始化与显示首帧时机冲突。
- 预热FlutterEngine后,加载UI首帧的成本会降低,但具体时间和内存开销取决于屏幕大小和物理像素等因素。
性能优化建议
- 对于add-to-app场景,可以利用FlutterEngineGroup来管理和复用Flutter引擎实例,以减少内存占用和提高性能。
- 根据应用的结构和不断试探的结果,确定合适的预热时机。
- 监控应用的内存和性能表现,根据需要进行调整和优化。
Dart代码混淆
使用 dart-obfuscate
工具
dart-obfuscate
是一个流行的 Dart 代码混淆工具。你可以通过以下步骤使用它:
-
安装 Dart SDK:确保你已经安装了 Dart SDK。
-
安装
dart-obfuscate
:shdart pub global activate dart_obfuscation
-
混淆你的 Dart 代码:
shdart-obfuscate input.dart --output output.dart
这里
input.dart
是你的源代码文件,output.dart
是混淆后的代码文件。
在iOS工程中配置Dart代码混淆
通常是在Flutter项目中进行的,因为Flutter支持使用Dart语言开发跨平台应用,包括iOS。以下是在iOS工程中配置Dart代码混淆的步骤:
一、前提条件
- 确保你的Flutter项目已经正确设置并能在iOS设备上运行。
- 确保你的Flutter SDK和依赖项都是最新的。
二、配置Dart代码混淆
-
修改构建配置
- 在你的Flutter项目根目录下,找到
ios
文件夹,然后进入Flutter
文件夹,再找到Release.xcconfig
文件。 - 在
Release.xcconfig
文件中,添加以下行来启用Dart代码混淆:
plaintextEXTRA_GEN_SNAPSHOT_OPTIONS=--obfuscate
这告诉Flutter在构建iOS应用时使用混淆选项。
- 在你的Flutter项目根目录下,找到
-
构建应用
- 使用以下命令在release模式下构建你的Flutter应用:
shflutter build ios --release --obfuscate --split-debug-info=/<project-name>/<directory>
这里的
--obfuscate
选项启用代码混淆,--split-debug-info
选项指定了Flutter输出调试文件的目录。请确保替换/<project-name>/<directory>
为你的实际项目名称和目录。 -
保存符号表文件
- 混淆后,Flutter会生成一个符号表文件,该文件用于将混淆后的堆栈跟踪转换回可读格式。请务必保存这个文件,以便在需要时调试混淆后的应用。
三、调试混淆后的应用
如果你需要调试混淆后的应用创建的堆栈跟踪,可以使用flutter symbolize
命令和符号文件来解析堆栈跟踪。具体步骤如下:
- 找到匹配的符号文件。例如,从iOS设备崩溃将需要相应的符号文件。
- 使用
flutter symbolize
命令提供堆栈跟踪(存储在文件中)和符号文件。例如:
sh
flutter symbolize -i <stack trace file> -d /path/to/symbols
这里的<stack trace file>
是堆栈跟踪文件,/path/to/symbols
是符号文件的路径。
四、注意事项
- 混淆后的代码可能使调试变得更加困难,因此建议在发布版本中使用混淆,而在开发过程中使用未混淆的代码。
- 混淆并不能完全防止逆向工程,但可以增加攻击者对代码的理解和分析难度。
- 混淆可能会增加应用构建时间和运行时间的开销。