Flutter 跨平台开发
Flutter 架构
分层架构
Flutter 的三层架构从底至上分别为 Embedder、Engine、Framework。
+----------------------------------------------------------+
| Framework (Dart) |
| Material / Cupertino / Widgets / Rendering / Animation |
+----------------------------------------------------------+
| Engine (C++) |
| Skia/Impeller | Dart Runtime | Platform Channel |
+----------------------------------------------------------+
| Embedder (Platform-specific) |
| Android / iOS / Web / macOS / Windows / Linux |
+----------------------------------------------------------+Embedder:平台嵌入层,使用各平台原生语言(Android 上 Java/Kotlin,iOS 上 Objective-C/Swift)实现,负责管理应用生命周期、输入事件、Surface 创建等底层平台交互。
Engine:核心引擎层,使用 C++ 实现,包含以下核心组件:
- Skia / Impeller 渲染引擎:Skia 是 Google 的 2D 图形库,是 Flutter 的默认渲染后端。Impeller 是 Flutter 新一代渲染引擎,旨在解决 Skia 在 iOS 上的预编译着色器卡顿问题(SkSL 编译延迟导致的 jank)。Impeller 使用 SPIR-V 作为中间表示,在构建时预编译所有着色器,避免运行时编译开销。
- Dart 运行时:负责 Dart 代码的垃圾回收(分代式 GC)、isolate 管理、JIT(开发模式)和 AOT(发布模式)执行。
- Platform Channel:与原生平台通信的桥梁。
Framework:框架层使用 Dart 实现,包含 Material Design、Cupertino(iOS 风格)、Widgets、Rendering、Animation、Painting 等库,是开发者日常接触最多的层次。
Platform Channel
Flutter 与原生平台通过 Platform Channel 进行异步消息通信,数据以二进制编码形式跨语言传递。
MethodChannel
用于方法调用模式,Flutter 调用原生方法并获取返回值。
// Flutter 端
import 'package:flutter/services.dart';
final channel = const MethodChannel('com.example/battery');
Future<int> getBatteryLevel() async {
try {
final int result = await channel.invokeMethod('getBatteryLevel');
return result;
} on PlatformException catch (e) {
print('获取电池电量失败: ${e.message}');
return -1;
}
}// Android 端 (MainActivity.kt)
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.os.BatteryManager
import android.content.Context
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example/battery"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "无法获取电池电量", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}EventChannel
用于事件流模式,原生端持续发送事件,Flutter 端通过 Stream 接收。
// Flutter 端
final eventChannel = const EventChannel('com.example/sensor');
void startListening() {
eventChannel.receiveBroadcastStream().listen(
(dynamic event) {
print('传感器数据: $event');
},
onError: (error) => print('传感器错误: $error'),
onDone: () => print('传感器已关闭'),
);
}BasicMessageChannel
用于简单的字符串或半结构化消息传递,不限定方法调用语义。
final messageChannel = const BasicMessageChannel<String>(
'com.example/message',
StringCodec(),
);
// 发送消息
await messageChannel.send('你好原生');
// 接收消息
messageChannel.setMessageHandler((String? message) async {
return '收到: $message';
});数据编码
| Codec | 支持类型 |
|---|---|
| StandardMethodCodec | 默认,支持基本类型、List、Map(需互为可转换类型) |
| JSONMethodCodec | 使用 JSON 格式编码,仅支持 JSON 可序列化类型 |
| StandardMessageCodec | 与 StandardMethodCodec 相同的二进制格式 |
| StringCodec | UTF-8 字符串编码 |
| BinaryCodec | 原始字节数据,不作转换 |
多线程处理
- UI 线程:Dart 代码默认在 UI isolate 中运行,所有 Platform Channel 调用默认也在 UI isolate 中处理结果。
- 后台 isolate:Android 上,原生代码的回调可能在非 UI 线程执行,需要手动切换到主线程。
- 发送到后台 isolate:通过
SendPort/ReceivePort机制将数据传递给后台 isolate 处理。
// MethodChannel 回调在 UI 线程,但原生端可能在其他线程执行
// 在原生端确保回到主线程再回调 Flutter
Handler(Looper.getMainLooper()).post {
result.success(data)
}Pigeon
Pigeon 是官方推出的代码生成工具,用于替代手写 Platform Channel,提供类型安全的消息传递。
安装配置:
# pubspec.yaml
dev_dependencies:
pigeon: ^22.0.0接口定义(pigeons/messages.dart):
import 'package:pigeon/pigeon.dart';
class SearchRequest {
final String query;
SearchRequest({required this.query});
}
class SearchReply {
final String result;
SearchReply({required this.result});
}
@HostApi()
abstract class SearchApi {
@async
SearchReply search(SearchRequest request);
}
@FlutterApi()
abstract class SearchCallback {
void onSearchResult(SearchReply reply);
}运行代码生成:
dart run pigeon \
--input pigeons/messages.dart \
--dart_out lib/pigeon/messages.dart \
--objc_header_out ios/Runner/messages.h \
--objc_source_out ios/Runner/messages.m \
--java_out android/app/src/main/java/io/flutter/plugins/Messages.java \
--java_package "io.flutter.plugins"生成的 Dart 代码无需手动处理序列化,直接调用类型安全的方法:
// Dart 端调用
import 'pigeon/messages.dart';
final api = SearchApi();
final reply = await api.search(SearchRequest(query: 'Flutter'));
print(reply.result);// Android 端实现
class SearchApiImpl : SearchApi {
override fun search(request: SearchRequest, callback: (Result<SearchReply>) -> Unit) {
val result = performSearch(request.query)
callback(Result.success(SearchReply(result)))
}
}Pigeon 的优势:
- 编译期类型检查:参数类型错误在生成代码时即可发现。
- 减少样板代码:无需手动编写 MethodChannel invoke 和 result 处理。
- 空安全:生成的代码完整继承 Dart 空安全特性。
- 结构化数据:自动处理复杂嵌套对象的序列化/反序列化。
- 双向通信:支持
@HostApi(Dart 调用原生)和@FlutterApi(原生调用 Dart)。
Dart 语言核心特性
空安全(Null Safety)
Dart 3 默认启用健全空安全,所有类型默认不可空,需要显式标记 ? 表示可空。
// 不可空类型
String name = 'Flutter'; // 不能赋值为 null
// 可空类型
String? nullableName; // 可以是 null
// 空值合并运算符 ??
String displayName = nullableName ?? '默认名称';
// 条件访问
int? length = nullableName?.length;
// 空值断言(仅在确信不为 null 时使用)
String nonNull = nullableName!;
// late 关键字:延迟初始化,在访问前赋值
late String lateInit;
lateInit = '稍后初始化';
print(lateInit); // 正常级联运算符
final paint = Paint()
..color = Colors.red
..strokeWidth = 2.0
..style = PaintingStyle.stroke;Extension 方法
extension StringExtensions on String {
String capitalize() {
if (isEmpty) return this;
return this[0].toUpperCase() + substring(1);
}
}
void main() {
print('hello'.capitalize()); // Hello
}异步编程
// Future
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 1));
return '数据加载完成';
}
// Stream
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
// StreamBuilder 在 Widget 中使用
StreamBuilder<int>(
stream: countStream(5),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('计数: ${snapshot.data}');
}
return CircularProgressIndicator();
},
)Isolate
Dart 是单线程模型,但通过 Isolate 实现并发。每个 Isolate 有独立的内存堆,通过消息传递通信。
import 'dart:isolate';
void heavyComputation(SendPort sendPort) {
int result = 0;
for (int i = 0; i < 1000000000; i++) {
result += i;
}
sendPort.send(result);
}
void main() async {
final receivePort = ReceivePort();
await Isolate.spawn(heavyComputation, receivePort.sendPort);
final result = await receivePort.first;
print('计算结果: $result');
}在实际开发中,通常使用 compute 工具函数(Flutter 提供)简化:
import 'package:flutter/foundation.dart';
int sum(List<int> numbers) {
return numbers.reduce((a, b) => a + b);
}
void main() async {
final result = await compute(sum, [1, 2, 3, 4, 5]);
}Dart 3 记录与模式匹配
// 记录(Record)
(String, int) userInfo = ('张三', 25);
print('${userInfo.$1} - ${userInfo.$2}岁');
// 带命名字段的记录
({String name, int age}) person = (name: '李四', age: 30);
// 解构赋值
var (name, age) = ('王五', 28);
// 模式匹配 switch
String describe(dynamic value) => switch (value) {
int i => '整数 $i',
String s => '字符串 $s',
(int x, int y) => '坐标 ($x, $y)',
_ => '未知类型',
};
// when 子句
String describeNumber(int value) => switch (value) {
> 0 => '正数',
0 => '零',
< 0 => '负数',
_ => '不可能', // 实际不会执行
};Sealed Class 与 Class Modifier
// sealed class:限制继承范围,switch 必须穷举所有子类型
sealed class ApiResult<T> {
const ApiResult();
}
class Success<T> extends ApiResult<T> {
final T data;
const Success(this.data);
}
class Error<T> extends ApiResult<T> {
final String message;
const Error(this.message);
}
class Loading<T> extends ApiResult<T> {
const Loading();
}
// 配合 switch 的穷举检查
Widget buildUI(ApiResult<String> result) => switch (result) {
Success(data: var d) => Text(d),
Error(message: var m) => Text('错误: $m'),
Loading() => CircularProgressIndicator(),
};
// Class modifier
base class BaseClass {} // 必须在同一库中继承
interface class InterfaceClass {} // 可被实现,但不能继承
final class FinalClass {} // 不能继承或实现
mixin class MixinClass {} // 既是 mixin 又是 class项目结构
my_flutter_app/
lib/ # Dart 源代码
main.dart # 入口文件
app.dart # 应用根组件
config/ # 配置(主题、路由)
models/ # 数据模型
providers/ # 状态管理
repositories/ # 数据仓库
services/ # 服务层(API、本地存储)
widgets/ # 公共组件
screens/ # 页面级组件
utils/ # 工具函数
pubspec.yaml # 项目配置与依赖
test/ # 单元测试和 Widget 测试
build/ # 构建产物(gitignore)
android/ # Android 原生工程
ios/ # iOS 原生工程
web/ # Web 构建配置
linux/ # Linux 桌面构建配置
macos/ # macOS 桌面构建配置
windows/ # Windows 桌面构建配置
assets/ # 资源文件(图片、字体、JSON)
images/
fonts/
analysis_options.yaml # 静态分析规则配置pubspec.yaml
name: my_flutter_app
description: 一个 Flutter 跨平台应用
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.5.0
flutter: ^3.27.0
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
# 状态管理
provider: ^6.1.2
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
# 路由
go_router: ^14.6.2
# 网络
dio: ^5.7.0
# 本地存储
shared_preferences: ^2.3.4
# 状态管理(可选)
flutter_bloc: ^8.1.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.4.13
riverpod_generator: ^2.6.3
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/fonts/
fonts:
- family: NotoSansSC
fonts:
- asset: assets/fonts/NotoSansSC-Regular.ttf
- asset: assets/fonts/NotoSansSC-Bold.ttf
weight: 700analysis_options.yaml
include: package:flutter_lints/flutter.yaml
linter:
rules:
prefer_const_constructors: true
prefer_const_declarations: true
avoid_print: false
prefer_single_quotes: true
analyzer:
errors:
invalid_annotation_target: ignore
exclude:
- '**/*.g.dart'
- '**/*.freezed.dart'资源管理
Flutter 资源在 pubspec.yaml 中声明后,通过 AssetBundle 访问:
// 访问图片
Image.asset('assets/images/logo.png')
// 访问 JSON 配置文件
Future<String> loadConfig() async {
return await rootBundle.loadString('assets/config.json');
}
// 加载文件
final data = await rootBundle.load('assets/data/file.bin');Widget 与布局
Widget 分类
Flutter 中一切都是 Widget。Widget 描述 UI 配置,Element 负责渲染树管理。
StatelessWidget vs StatefulWidget
| 特性 | StatelessWidget | StatefulWidget |
|---|---|---|
| 可变性 | 不可变,属性不可修改 | 通过 State 管理可变状态 |
| 重建触发 | 外部父 Widget 重建 | setState() 或 InheritedWidget 更新 |
| 生命周期 | build | createState → initState → build → setState → dispose |
| 适用场景 | 静态 UI、纯展示组件 | 交互式组件、动态数据 |
生命周期
StatefulWidget 生命周期流程:
createState()
|
initState() ← 创建时调用一次,用于初始化
|
didChangeDependencies() ← 依赖的 InheritedWidget 变化时调用
|
build() ← 构建 Widget 树,可多次调用
|
setState() ← 触发重新 build
|
didUpdateWidget() ← 父 Widget 重建且配置变化时调用
|
deactivate() ← 从树中移除时调用
|
dispose() ← 永久销毁时调用class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
@override
void initState() {
super.initState();
print('初始化');
}
@override
void dispose() {
print('销毁');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('计数: $_count'),
ElevatedButton(
onPressed: () {
setState(() {
_count++;
});
},
child: const Text('增加'),
),
],
);
}
}布局 Widget
单子布局
// Container:最常用的容器 Widget,可配置 padding、margin、decoration、transform 等
Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
),
child: const Text('容器内容'),
)
// Padding:仅用于添加内边距,比 Container 更轻量
const Padding(
padding: EdgeInsets.all(16),
child: Text('带内边距的文本'),
)
// Center:居中布局
const Center(child: FlutterLogo(size: 100))
// Align:灵活对齐
const Align(
alignment: Alignment.bottomRight,
child: Text('右下角'),
)
// SizedBox:固定尺寸
const SizedBox(width: 100, height: 50, child: Text('固定大小'))
// ConstrainedBox:约束最大/最小尺寸
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 100, maxWidth: 200, minHeight: 50, maxHeight: 100,
),
child: const Text('受约束的盒子'),
)多子布局
// Column:垂直排列
Column(
mainAxisAlignment: MainAxisAlignment.center, // 主轴对齐
crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴对齐
children: [
const Text('第一行'),
const Text('第二行'),
const Text('第三行'),
],
)
// Row:水平排列
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(Icons.home),
Icon(Icons.search),
Icon(Icons.settings),
],
)
// Flex + Expanded:弹性布局
Flex(
direction: Axis.horizontal,
children: [
Expanded( // 占据剩余空间的 1/3
flex: 1,
child: Container(color: Colors.red),
),
Expanded( // 占据剩余空间的 2/3
flex: 2,
child: Container(color: Colors.blue),
),
],
)
// Flexible:与 Expanded 类似但允许子 Widget 自然尺寸小于分配空间
Row(
children: [
Flexible(
flex: 1,
fit: FlexFit.loose, // 子 Widget 可以小于分配空间
child: Container(width: 50, height: 50, color: Colors.red),
),
const Text('Hello'),
],
)层叠布局
// Stack:层叠布局,后添加的 Widget 在上层
Stack(
children: [
Container(width: 200, height: 200, color: Colors.blue),
const Positioned( // 精确定位
top: 20,
left: 20,
child: Text('浮动文本'),
),
const Positioned(
bottom: 0,
right: 0,
child: Icon(Icons.star, color: Colors.yellow),
),
],
)
// IndexedStack:只显示指定索引的子 Widget,但保持所有子 Widget 状态
IndexedStack(
index: currentIndex,
children: [
const HomeScreen(),
const SearchScreen(),
const ProfileScreen(),
],
)比例与约束
// AspectRatio:保持宽高比
AspectRatio(
aspectRatio: 16 / 9,
child: Container(color: Colors.grey),
)
// FractionallySizedBox:按父容器比例确定尺寸
FractionallySizedBox(
widthFactor: 0.8, // 父容器宽度的 80%
heightFactor: 0.5, // 父容器高度的 50%
child: Container(color: Colors.amber),
)
// LimitedBox:无约束时限制最大尺寸
LimitedBox(
maxWidth: 300,
maxHeight: 200,
child: const Text('有限制的盒子'),
)
// OverflowBox:允许子 Widget 超出父容器约束
OverflowBox(
alignment: Alignment.center,
minWidth: 0,
maxWidth: 200,
minHeight: 0,
maxHeight: 200,
child: Container(
width: 300, // 超出 200 但不会被裁剪
height: 300,
color: Colors.red.withOpacity(0.5),
),
)
// CustomMultiChildLayout:自定义多子布局
// 需搭配 LayoutDelegate 使用,提供精细的位置控制RenderFlex
RenderFlex 是 Flex(Row / Column 的基类)的渲染对象,负责主轴和交叉轴上的布局计算。理解其行为有助于排查布局问题:
- MainAxisAlignment:主轴对齐方式(start、end、center、spaceBetween、spaceAround、spaceEvenly)
- CrossAxisAlignment:交叉轴对齐方式(start、end、center、stretch、baseline)
- MainAxisSize:主轴尺寸(min:收缩到子 Widget 大小,max:撑满父容器)
- FlexFit.tight(Expanded):强制拉伸子 Widget 填满可用空间
- FlexFit.loose(Flexible):子 Widget 可以小于可用空间
滚动 Widget
基础滚动
// ListView:默认列表
ListView(
children: const [
ListTile(title: Text('项目 1')),
ListTile(title: Text('项目 2')),
ListTile(title: Text('项目 3')),
],
)
// GridView:网格布局
GridView.count(
crossAxisCount: 2,
children: List.generate(20, (index) {
return Card(child: Center(child: Text('$index')));
}),
)
// PageView:页面滑动
PageView(
children: [
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.blue),
],
)
// SingleChildScrollView:单个子 Widget 可滚动
SingleChildScrollView(
child: Column(
children: [
const Text('长内容...'),
// 大量内容自动获得滚动能力
],
),
)
// NestedScrollView:嵌套滚动(可滚动 Header + 可滚动 Body)
NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverAppBar(
title: const Text('嵌套滚动'),
pinned: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.network('https://example.com/banner.jpg'),
),
),
],
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('第 $index 项')),
),
)CustomScrollView 与 Sliver
CustomScrollView 配合各种 Sliver 组件,实现复杂滚动效果。Sliver 是可滚动的"碎片",各自独立管理布局和滚动行为。
// CustomScrollView 综合示例
CustomScrollView(
slivers: [
// SliverAppBar:可折叠顶部栏
SliverAppBar(
title: const Text('高级滚动'),
pinned: true,
floating: false,
expandedHeight: 200,
),
// SliverToBoxAdapter:将普通 Widget 转为 Sliver
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('滚动头部内容'),
),
),
// SliverList:懒加载列表
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('列表项 $index')),
childCount: 30,
),
),
// 分隔线
const SliverToBoxAdapter(
child: Divider(height: 32, thickness: 2),
),
// SliverGrid:网格
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
delegate: SliverChildBuilderDelegate(
(context, index) => Container(color: Colors.primaries[index % 18]),
childCount: 20,
),
),
// SliverFillRemaining:填充剩余空间(常用于占位或底部内容)
const SliverFillRemaining(
child: Center(child: Text('到底了')),
),
// SliverPersistentHeader:固定头部,随滚动变化
SliverPersistentHeader(
pinned: true,
delegate: _FixedHeaderDelegate(),
),
// SliverPadding:给其他 Sliver 添加内边距
SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Text('带内边距的第 $index 项'),
childCount: 10,
),
),
),
// SliverOpacity:透明度动画
SliverOpacity(
opacity: 0.5,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Text('半透明项 $index'),
childCount: 5,
),
),
),
// SliverAnimatedList:带动画的增删列表
// 需要配合 GlobalKey<SliverAnimatedListState> 使用
],
)列表优化与性能
Flutter 的列表性能优化至关重要,特别是面对长列表和大数据量时。
列表构建模式
// 不推荐:一次性构建所有子 Widget(适用于少量固定项)
ListView(
children: List.generate(1000, (index) => ListTile(title: Text('$index'))),
)
// 推荐:懒加载构建,仅在可见区域内构建 Widget
ListView.builder(
itemCount: 10000,
itemBuilder: (context, index) {
return ListTile(
title: Text('项 $index'),
subtitle: Text('懒加载构建'),
);
},
)
// 带分隔线的懒加载列表
ListView.separated(
itemCount: 100,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) => ListTile(title: Text('项 $index')),
)
// 最高性能:SliverList 懒加载
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Sliver 项 $index')),
childCount: 10000,
),
),
],
)自动回收与重用
Flutter 的 ScrollView 默认会对离屏 Widget 进行回收和重用。SliverList 和 ListView.builder 仅在视口附近构建可见 Widget,当滑动时,离开视口的 Element 被复用给新进入的项。
然而,在某些使用 AutomaticKeepAliveClientMixin 或 PageStorage 的场景下,Widget 可能被标记为需保持存活,不会回收。
keepAlive
// 保持列表项存活(例如:TabBarView 中保持页面状态)
class MyListPage extends StatefulWidget {
@override
State<MyListPage> createState() => _MyListPageState();
}
class _MyListPageState extends State<MyListPage>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context); // 必须调用
return ListView.builder(
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('项 $index')),
);
}
}图片延迟加载与缓存
// 使用 cached_network_image 实现图片缓存和懒加载
CachedNetworkImage(
imageUrl: 'https://example.com/large-image.jpg',
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.error),
memCacheWidth: 200, // 内存缓存限制宽度
memCacheHeight: 200, // 内存缓存限制高度
maxWidthDiskCache: 400, // 磁盘缓存限制宽度
)监听滚动事件
// ScrollController:控制滚动位置和监听事件
final ScrollController _controller = ScrollController();
@override
void initState() {
super.initState();
_controller.addListener(() {
final maxScroll = _controller.position.maxScrollExtent;
final currentScroll = _controller.position.pixels;
if (currentScroll >= maxScroll * 0.8) {
// 滚动到 80% 时触发加载更多
loadMore();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
// NotificationListener:监听深层的滚动通知
NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
print('滚动位置: ${notification.metrics.pixels}');
}
return false; // 返回 true 阻止事件继续冒泡
},
child: ListView.builder(
controller: _controller,
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('项 $index')),
),
)CachingScrollController
Flutter 默认的 ScrollController 不提供预加载缓存。对于需要预加载数据的场景,可以使用 CachingScrollController(社区方案)或自行实现预加载逻辑。
// 简单的预加载实现
class PreloadController extends ScrollController {
final int preloadThreshold;
final VoidCallback onPreload;
PreloadController({
this.preloadThreshold = 200,
required this.onPreload,
}) {
addListener(_onScroll);
}
void _onScroll() {
if (position.pixels >= position.maxScrollExtent - preloadThreshold) {
onPreload();
}
}
}长列表性能准则
- 始终使用
ListView.builder或SliverList替代ListView(children: [...])直接传列表。 - 图片使用
cached_network_image并限制缓存尺寸。 - 列表项 Widget 尽量保持轻量,避免深层次嵌套。
- 使用
const构造函数减少重建。 - 在列表项中避免
Opacity和Clip操作(它们会触发 saveLayer,导致性能开销)。 - 使用
RepaintBoundary隔离不需要频繁重绘的区域。 - 对于超大列表(10万+),考虑分页加载而非一次性渲染。
// 高性能列表项示例
class OptimizedListItem extends StatelessWidget {
final String title;
final String imageUrl;
const OptimizedListItem({
super.key,
required this.title,
required this.imageUrl,
});
@override
Widget build(BuildContext context) {
return RepaintBoundary( // 隔离重绘
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
ClipRRect( // 仅在需要裁剪时使用
borderRadius: BorderRadius.circular(8),
child: CachedNetworkImage(
imageUrl: imageUrl,
width: 50,
height: 50,
memCacheWidth: 50,
memCacheHeight: 50,
),
),
const SizedBox(width: 12),
Expanded(child: Text(title)),
],
),
),
);
}
}状态管理
Provider
Provider 是 Flutter 官方推荐的基础状态管理方案,本质是对 InheritedWidget 的封装。
核心概念
// pubspec.yaml
dependencies:
provider: ^6.1.2
flutter:
sdk: flutter// 1. 创建数据模型
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // 通知所有监听者重建
}
}
// 2. 提供数据
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const MyApp(),
),
);
}
// 3. 消费数据
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// context.watch:监听变化,数据变化时重建
final counter = context.watch<CounterModel>();
// context.read:仅读取,不监听(在回调中使用)
// final counter = context.read<CounterModel>();
return Scaffold(
body: Center(child: Text('${counter.count}')),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterModel>().increment(),
),
);
}
}Provider 类型
| Provider 类型 | 用途 | 特性 |
|---|---|---|
Provider | 提供不可变对象 | 不触发重建 |
ChangeNotifierProvider | 提供 ChangeNotifier | 调用 notifyListeners 时重建 |
ListenableProvider | 提供任意 Listenable | 比 ChangeNotifierProvider 更通用 |
ValueListenableProvider | 监听 ValueNotifier | 监听值的变化 |
StreamProvider | 提供 Stream | 自动处理 Stream 订阅 |
FutureProvider | 提供 Future | 异步数据加载 |
MultiProvider | 组合多个 Provider | 避免嵌套层级过深 |
// MultiProvider:多个状态管理共存
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => UserModel()),
Provider(create: (_) => ApiService()),
StreamProvider(create: (_) => SocketService().stream),
],
child: const MyApp(),
),
);
}ProxyProvider
依赖其他 Provider 的数据创建新 Provider,在其他 Provider 变化时自动重建。
class User {
final String name;
final String role;
User(this.name, this.role);
}
class Permissions {
final bool canEdit;
Permissions(this.canEdit);
}
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserModel()),
ProxyProvider<UserModel, Permissions>(
update: (_, userModel, __) {
return Permissions(userModel.role == 'admin');
},
),
],
child: const MyApp(),
),
);
}Consumer 与 Selector
// Consumer:精细化重建,只 rebuild Consumer 子树
Consumer<CounterModel>(
builder: (context, counter, child) {
// counter 变化时仅此处重建
return Column(
children: [
Text('${counter.count}'),
child!, // 不变的部分通过 child 参数传入
],
);
},
child: const Text('永不重建的部分'), // 只构建一次
)
// Selector:更细粒度的选择
Selector<CartModel, double>(
selector: (_, cart) => cart.totalPrice, // 仅当 totalPrice 变化时重建
builder: (context, totalPrice, child) {
return Text('总价: ¥${totalPrice.toStringAsFixed(2)}');
},
)性能要点
- 使用
context.watch获取监听数据,使用context.read在不监听的位置获取数据。 - 使用
Consumer/Selector缩小重建范围。 Provider.of<T>(context, listen: false)等价于context.read<T>()。- 避免在
build方法中频繁创建新对象,使用const和缓存。
Riverpod
Riverpod 是 Provider 的继任者,由同一作者开发,解决了 Provider 的一些固有缺陷(如编译期类型安全不足、依赖注入顺序问题)。
dependencies:
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
dev_dependencies:
build_runner: ^2.4.13
riverpod_generator: ^2.6.3基础 Provider
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider:提供任意类型
final greetingProvider = Provider<String>((ref) {
return 'Hello, Riverpod!';
});
// StateProvider:提供可修改的简单状态
final counterProvider = StateProvider<int>((ref) => 0);
// StateNotifierProvider:提供可修改的复杂状态
class TodoNotifier extends StateNotifier<List<Todo>> {
TodoNotifier() : super([]);
void addTodo(Todo todo) => state = [...state, todo];
void removeTodo(String id) => state = state.where((t) => t.id != id).toList();
}
final todoListProvider = StateNotifierProvider<TodoNotifier, List<Todo>>((ref) {
return TodoNotifier();
});
// FutureProvider:异步数据
final userProvider = FutureProvider<User>((ref) async {
final api = ref.watch(apiProvider);
return api.fetchCurrentUser();
});
// StreamProvider:流数据
final clockProvider = StreamProvider.autoDispose((ref) {
return Stream.periodic(const Duration(seconds: 1), (_) => DateTime.now());
});family 与 autoDispose
// family:带参数的 Provider
final userProfileProvider = FutureProvider.family<User, String>((ref, userId) async {
final api = ref.watch(apiProvider);
return api.fetchUser(userId);
});
// 使用方式
ref.watch(userProfileProvider('user_123'));
// autoDispose:不再被监听时自动释放资源
final autoDisposeProvider = FutureProvider.autoDispose((ref) async {
final connection = await createConnection();
ref.onDispose(() {
connection.close(); // 自动清理
});
return connection;
});在 Widget 中使用
// 继承 ConsumerWidget 或 ConsumerStatefulWidget
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider); // 监听
final greeting = ref.watch(greetingProvider);
return Scaffold(
body: Center(child: Text('$greeting $count')),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++, // 不监听
),
);
}
}
// ConsumerWidget 的精细化重建
class TodoList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo.title),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
ref.read(todoListProvider.notifier).removeTodo(todo.id);
},
),
);
},
);
}
}override 测试
void main() {
test('计数器可以被覆盖', () {
final container = ProviderContainer(
overrides: [
counterProvider.overrideWith((ref) => 42), // 覆盖为固定值
],
);
expect(container.read(counterProvider), 42);
});
}
// ProviderScope.overrides 用于 Widget 测试
testWidgets('测试 Provider 覆盖', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
apiProvider.overrideWith((ref) => MockApiService()),
],
child: const MyApp(),
),
);
});Provider vs Riverpod 对比
| 特性 | Provider | Riverpod |
|---|---|---|
| 编译期安全 | 运行时异常可能(Provider 未找到) | 编译期安全,全局可访问 |
| 依赖注入 | 依赖 Widget 树位置 | 全局声明,不依赖 Widget 树 |
| 代码生成 | 无 | 支持 riverpod_generator |
| 参数化 Provider | 不支持 | family 修饰符 |
| 自动释放 | 不支持 | autoDispose 修饰符 |
| 测试 | 需要 Widget 树 | ProviderContainer 可直接测试 |
| 重建控制 | Selector / Consumer | ref.watch / ref.listen 灵活控制 |
Bloc
Bloc(Business Logic Component)是一种基于事件驱动(Event → Bloc → State)的模式,适合复杂业务逻辑。
dependencies:
flutter_bloc: ^8.1.6
bloc: ^8.1.4基础实现
// 1. 定义 Event
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
// 2. 定义 State
class CounterState {
final int count;
const CounterState(this.count);
}
// 3. 实现 Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(const CounterState(0)) {
on<Increment>((event, emit) {
emit(CounterState(state.count + 1));
});
on<Decrement>((event, emit) {
emit(CounterState(state.count - 1));
});
}
}Cubit(简化版 Bloc)
Cubit 比 Bloc 更轻量,不需要定义 Event,直接调用方法。
// Cubit 定义
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
// 在 Widget 中使用
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: Scaffold(
body: BlocBuilder<CounterCubit, int>(
builder: (context, count) => Center(child: Text('$count')),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterCubit>().increment(),
),
),
);
}
}Bloc Widget 类型
// BlocProvider:提供 Bloc/Cubit 实例
BlocProvider(
create: (context) => CounterBloc(),
child: const CounterPage(),
)
// BlocBuilder:根据状态构建 UI
BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('${state.count}');
},
buildWhen: (previous, current) => previous.count != current.count, // 条件重建
)
// BlocListener:监听状态变化执行副作用
BlocListener<CounterBloc, CounterState>(
listener: (context, state) {
if (state.count >= 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('计数达到 10!')),
);
}
},
child: const CounterContent(),
)
// BlocConsumer:组合 BlocBuilder + BlocListener
BlocConsumer<CounterBloc, CounterState>(
listener: (context, state) {
// 副作用处理
},
builder: (context, state) {
// UI 构建
return Text('${state.count}');
},
)
// RepositoryProvider:提供仓库层依赖
RepositoryProvider(
create: (context) => TodoRepository(),
child: const TodoListPage(),
)RepositoryProvider 与多层架构
// 多层 Bloc 架构
class TodoRepository {
Future<List<Todo>> fetchTodos() async {
// 数据源访问
}
}
// RepositoryProvider 提供依赖
void main() {
runApp(
RepositoryProvider(
create: (_) => TodoRepository(),
child: const MyApp(),
),
);
}
// Bloc 中使用 Repository
class TodoBloc extends Bloc<TodoEvent, TodoState> {
final TodoRepository repository;
TodoBloc(this.repository) : super(TodoInitial()) {
on<LoadTodos>((event, emit) async {
try {
emit(TodoLoading());
final todos = await repository.fetchTodos();
emit(TodoLoaded(todos));
} catch (e) {
emit(TodoError(e.toString()));
}
});
}
}
// Bloc 依赖注入
BlocProvider(
create: (context) => TodoBloc(
RepositoryProvider.of<TodoRepository>(context),
),
child: const TodoPage(),
)Bloc 选择器
// 使用 distinct() 避免重复触发同值
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) {
emit(state + 1);
});
}
@override
Stream<int> mapEventToState(CounterEvent event) async* {
// distinct 去重
yield* super.mapEventToState(event).distinct();
}
}状态管理选型对比
| 维度 | Provider | Riverpod | Bloc | GetX |
|---|---|---|---|---|
| 复杂度 | 低 | 中 | 高 | 低 |
| 学习曲线 | 低 | 中-高 | 高 | 低 |
| 类型安全 | 运行时 | 编译期 | 编译期 | 运行时 |
| 代码量 | 少 | 中 | 多 | 少 |
| 测试性 | 中 | 高 | 高 | 低 |
| 依赖关系 | 依赖 Widget 树 | 全局声明 | 依赖注入 | 全局 |
| 异步支持 | Stream/FutureProvider | Stream/FutureProvider | Event-Stream | 内置 |
| 社区生态 | 官方维护 | 活跃社区 | 官方维护 | 第三方 |
| 适合规模 | 小型应用 | 中小型应用 | 中大型/复杂应用 | 快速原型 |
| 团队协作 | 中 | 高 | 高 | 低 |
| 性能 | 好(Selector) | 好(family/autoDispose) | 极好(去重) | 好 |
选型建议:
- 小型应用 / 快速开发:Provider 或 GetX。
- 中型应用 / 需要良好测试性:Riverpod(推荐新项目优先考虑)。
- 大型复杂应用 / 团队协作:Bloc(事件驱动模式,职责划分清晰)。
- 需要原子化状态:Riverpod 的 family + autoDispose 组合。
- 复杂异步流处理:Bloc 的 Event/State 模式。
- 团队已有 Provider 经验:可继续使用 Provider + Selector 优化。
路由与导航
GoRouter
GoRouter 是基于 Navigator 2.0 的声明式路由框架,支持深度链接、重定向、嵌套路由和 Shell 路由。
dependencies:
go_router: ^14.6.2基础配置
import 'package:go_router/go_router.dart';
// 定义路由配置
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/profile/:userId', // 路径参数
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return ProfileScreen(userId: userId);
},
),
GoRoute(
path: '/search',
builder: (context, state) {
final query = state.uri.queryParameters['q'] ?? ''; // 查询参数
return SearchScreen(query: query);
},
),
],
);
void main() {
runApp(MaterialApp.router(
routerConfig: router,
));
}ShellRoute 与 StatefulShellRoute
ShellRoute 用于定义页面框架(如底部导航栏),子路由共享该框架。
// ShellRoute:共享框架
final router = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) {
return AppShell(child: child); // 底部导航栏框架
},
routes: [
GoRoute(path: '/home', builder: (context, state) => const HomePage()),
GoRoute(path: '/search', builder: (context, state) => const SearchPage()),
GoRoute(path: '/settings', builder: (context, state) => const SettingsPage()),
],
),
],
);
// StatefulShellRoute:保持每个导航项的状态(推荐)
final router = GoRouter(
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return ScaffoldWithNavBar(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
routes: [GoRoute(path: '/home', builder: (_, __) => const HomePage())],
),
StatefulShellBranch(
routes: [GoRoute(path: '/search', builder: (_, __) => const SearchPage())],
),
StatefulShellBranch(
routes: [GoRoute(path: '/profile', builder: (_, __) => const ProfilePage())],
),
],
),
],
);重定向与导航守卫
final router = GoRouter(
initialLocation: '/',
// redirect 在每次导航前触发
redirect: (context, state) {
final isLoggedIn = AuthService.isLoggedIn;
final isLoginRoute = state.matchedLocation == '/login';
// 未登录且不在登录页则重定向
if (!isLoggedIn && !isLoginRoute) return '/login';
// 已登录且当前在登录页则跳到首页
if (isLoggedIn && isLoginRoute) return '/';
// 返回 null 表示不重定向
return null;
},
// 防止 redirect 循环:记录上次重定向位置
refreshListenable: AuthService.instance, // Listenable,监听认证状态变化
routes: [...],
);参数传递
// GoRoute 参数定义
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
// 通过 extra 传递对象
final extra = state.extra as Map<String, dynamic>?;
return ProductScreen(id: id, product: extra);
},
)
// 导航传参
context.go('/product/123', extra: {'name': 'Flutter Book', 'price': 99.0});
// 类型安全的命名参数
GoRoute(
path: '/product/:id',
name: 'product', // 命名路由
builder: (context, state) {
final id = state.pathParameters['id']!;
return ProductScreen(id: id);
},
)
// 使用命名路由导航
context.goNamed('product', pathParameters: {'id': '456'}, extra: productData);深度链接
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
GoRoute(path: '/product/:id', builder: (_, state) {
final id = state.pathParameters['id']!;
return ProductScreen(id: id);
}),
],
);
// Android: AndroidManifest.xml 配置 intent-filter
// iOS: Info.plist 配置 URL types
// 处理深度链接回调
void main() {
runApp(
MaterialApp.router(
routerConfig: router,
// 监听 app 启动时的深度链接
),
);
}错误处理
final router = GoRouter(
routes: [...],
errorBuilder: (context, state) => const NotFoundScreen(),
errorPageBuilder: (context, state) => MaterialPage(
child: Scaffold(
appBar: AppBar(title: const Text('404')),
body: Center(child: Text('页面未找到: ${state.error?.message}')),
),
),
);导航动画
// 自定义页面过渡动画
final router = GoRouter(
routes: [
GoRoute(
path: '/detail',
pageBuilder: (context, state) => CustomTransitionPage(
key: state.pageKey,
child: const DetailScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0), // 从右侧滑入
end: Offset.zero,
).animate(animation),
child: child,
);
},
transitionDuration: const Duration(milliseconds: 300),
),
),
],
);Navigator 2.0 vs 1.0
| 特性 | Navigator 1.0 | Navigator 2.0 (GoRouter) |
|---|---|---|
| 路由定义 | 静态路由表 routes | 声明式 GoRoute 配置 |
| 导航方式 | Navigator.push / pop | context.go / context.push |
| URL 支持 | 不直接支持 | 原生 URL 路径映射 |
| 深度链接 | 需手动解析 | 内置支持 |
| 重定向 | 手动判断 | redirect 回调 |
| 嵌套路由 | 需手动管理 | ShellRoute / StatefulShellRoute |
| 动画控制 | 有限 | CustomTransitionPage |
| 状态保存 | 默认丢失 | StatefulShellRoute 保持状态 |
| 类型安全 | 运行时 | 可通过命名路由实现 |
| 适合场景 | 简单应用 | 复杂多页面应用 |
// Navigator 1.0 风格(仍可混用)
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailScreen()),
);
// GoRouter / Navigator 2.0 风格
context.push('/detail');
context.go('/profile/123');页面过渡动画
// MaterialPageRoute:默认 Material 风格(平台自适应)
MaterialPageRoute(
builder: (_) => const DetailScreen(),
fullscreenDialog: true, // 模态风格
)
// CupertinoPageRoute:iOS 风格
CupertinoPageRoute(
builder: (_) => const DetailScreen(),
)
// 禁用动画
MaterialPageRoute(
builder: (_) => const DetailScreen(),
settings: const RouteSettings(),
// 无过渡动画
)
// 自定义动画
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => const DetailScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var begin = 0.0;
var end = 1.0;
var curve = Curves.ease;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
var fadeAnimation = animation.drive(tween);
return FadeTransition(opacity: fadeAnimation, child: child);
},
transitionDuration: const Duration(milliseconds: 500),
)Shared Axis 与 Hero 动画
// Hero 动画:共享元素过渡
class HeroExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => const DetailScreen(),
)),
child: Hero(
tag: 'avatar_hero', // 相同 tag 的元素产生过渡动画
child: CircleAvatar(
radius: 50,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
),
);
}
}
// 详情页匹配相同 tag
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Hero(
tag: 'avatar_hero',
child: CircleAvatar(
radius: 100,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
),
),
);
}
}
// 自定义 Hero 飞行效果
Hero(
tag: 'custom_hero',
flightShuttleBuilder: (flightContext, animation, direction,
fromContext, toContext) {
return RotationTransition(
turns: animation,
child: Icon(Icons.star, size: 100),
);
},
child: const Icon(Icons.star, size: 50),
)打包与发布
Android 打包
# 调试 APK
flutter build apk --debug
# 发布 APK(需要签名配置)
flutter build apk --release
# 发布 App Bundle(推荐 Play Store 使用)
flutter build appbundle --release签名配置
// android/app/build.gradle
android {
...
signingConfigs {
release {
storeFile file('release.jks')
storePassword System.getenv('STORE_PASSWORD')
keyAlias System.getenv('KEY_ALIAS')
keyPassword System.getenv('KEY_PASSWORD')
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}生成签名密钥
keytool -genkey -v -keystore release.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias release_keyiOS 打包
# 构建发布版本
flutter build ios --release
# 构建 Archive(通过 Xcode)
flutter build ios --release --no-codesign
# 导出 ipa
flutter build ipa --release证书与配置文件
iOS 打包需要 Apple Developer 账号和以下配置:
- 在 Apple Developer Center 创建 App ID。
- 生成分发证书(Distribution Certificate)。
- 创建 Provisioning Profile。
- 在 Xcode 中配置 Signing & Capabilities。
# 使用 fastlane 自动化打包(推荐)
fastlane match development
fastlane match appstoreWeb 打包
# 构建 Web 应用
flutter build web --release
# 指定渲染器
flutter build web --web-renderer canvaskit # CanvasKit 渲染(默认)
flutter build web --web-renderer html # HTML 渲染(更轻量)
# 输出目录:build/web/
# 部署到任何静态服务器即可桌面打包
# macOS
flutter build macos --release
# Windows
flutter build windows --release
# Linux
flutter build linux --releasePlay Store 发布
# 1. 构建 App Bundle
flutter build appbundle --release
# 2. 生成的 AAB 文件位置
# build/app/outputs/bundle/release/app-release.aab
# 3. 在 Google Play Console 创建应用
# 4. 上传 AAB 文件
# 5. 填写 Store Listing、定价、评分等信息
# 6. 审核发布App Store 发布
# 1. 构建 ipa
flutter build ipa --release
# 2. 生成的 IPA 文件位置
# build/ios/ipa/*.ipa
# 3. 通过 Xcode Archiver 或 Transporter 上传
# 4. 在 App Store Connect 配置应用信息
# 5. 提交审核版本管理与构建号
# pubspec.yaml
version: 1.2.3+4 # versionName=1.2.3, versionCode=4
# 使用命令行参数覆盖
flutter build appbundle --build-name=2.0.0 --build-number=10
# 或使用 flutter_version 包自动化版本管理
dependencies:
flutter_version: ^1.0.0持续集成示例
# .github/workflows/flutter_release.yml
name: Flutter Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.27.x'
- run: flutter pub get
- run: flutter test
- run: flutter build appbundle --release
- uses: actions/upload-artifact@v4
with:
name: release-aab
path: build/app/outputs/bundle/release/app-release.aab