fix: auto repair bugs #79
This commit is contained in:
parent
134da153d5
commit
518c6a26e4
703
airhub_app/FLUTTER_OPTIMIZATION_GUIDE.md
Normal file
703
airhub_app/FLUTTER_OPTIMIZATION_GUIDE.md
Normal file
@ -0,0 +1,703 @@
|
||||
# Flutter App 全面优化方案
|
||||
|
||||
> 针对 airhub_app 项目的从头到尾优化整改方案
|
||||
> 目标:减小包大小、降低发热、消除卡顿
|
||||
|
||||
---
|
||||
|
||||
## 一、项目现状分析
|
||||
|
||||
| 指标 | 当前值 | 问题 |
|
||||
|------|--------|------|
|
||||
| Flutter 版本 | 3.41.2 (Dart 3.11.0) | 较新,支持 Impeller |
|
||||
| 代码规模 | 132 个 Dart 文件 / 28k 行 | 中等规模 |
|
||||
| assets 总大小 | **~135MB** | **严重超标** |
|
||||
| storybook_videos/ | **92MB** (11个mp4,最大48MB) | **主要元凶** |
|
||||
| music/ | **15MB** (10个mp3) | 应该CDN下载 |
|
||||
| story_covers/ | **7.4MB** (PNG图片) | 需压缩或远程加载 |
|
||||
| 背景图 PNG | 单张 4.5-4.7MB | **未压缩的巨型PNG** |
|
||||
| 字体文件 | 1.6MB (4个Inter字重) | 可裁剪子集 |
|
||||
| google_fonts 依赖 | 存在 | 会下载额外字体 |
|
||||
| Android release 配置 | 未启用 minifyEnabled | 缺少代码压缩 |
|
||||
| ProGuard | 已引用但未启用 shrinkResources | 资源未裁剪 |
|
||||
| iOS 构建 | 默认配置 | 未做符号裁剪优化 |
|
||||
|
||||
---
|
||||
|
||||
## 二、包大小优化(预估可减少 60-70%)
|
||||
|
||||
### 阶段 1:资源外迁(预估减少 110MB+ → 约 80% 的包大小)
|
||||
|
||||
这是**最关键也是收益最大**的一步。
|
||||
|
||||
#### 1.1 视频资源迁移到 CDN / 服务器
|
||||
```
|
||||
当前: assets/www/storybook_videos/ → 92MB 全部打进包
|
||||
目标: 视频从服务端按需下载,本地缓存
|
||||
```
|
||||
|
||||
**实施方案:**
|
||||
- 将所有 mp4 文件上传到后端服务器或 OSS/CDN
|
||||
- App 端改为流式播放或首次使用时下载到本地缓存
|
||||
- 使用 `video_player` 的网络播放功能直接播放远程URL
|
||||
- 实现下载进度显示和本地缓存管理
|
||||
|
||||
```dart
|
||||
// 改为网络播放
|
||||
VideoPlayerController.networkUrl(Uri.parse('https://cdn.example.com/videos/magic_broom.mp4'))
|
||||
|
||||
// 或按需下载到缓存
|
||||
final cacheDir = await getTemporaryDirectory();
|
||||
final file = File('${cacheDir.path}/magic_broom.mp4');
|
||||
if (!file.existsSync()) {
|
||||
await dio.download(url, file.path, onReceiveProgress: (received, total) {
|
||||
// 显示下载进度
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 音频资源迁移
|
||||
```
|
||||
当前: assets/www/music/ → 15MB
|
||||
目标: 音频按需下载或流式播放
|
||||
```
|
||||
|
||||
- `just_audio` 本身支持网络URL播放
|
||||
- 改为 `AudioSource.uri(Uri.parse('https://cdn.example.com/music/xxx.mp3'))`
|
||||
|
||||
#### 1.3 故事封面图迁移
|
||||
```
|
||||
当前: assets/www/story_covers/ → 7.4MB
|
||||
目标: 网络图片 + 本地缓存
|
||||
```
|
||||
|
||||
- 添加 `cached_network_image` 依赖
|
||||
- 封面图改为网络加载,自动缓存
|
||||
|
||||
#### 1.4 背景图压缩
|
||||
```
|
||||
当前: 首页背景2.png (4.7MB) + 首页底图.png (4.5MB)
|
||||
目标: 压缩到 200-500KB
|
||||
```
|
||||
|
||||
**方案A:转 WebP 格式**
|
||||
```bash
|
||||
# 安装 cwebp
|
||||
brew install webp
|
||||
|
||||
# 转换 (质量80,通常可减少 85%+)
|
||||
cwebp -q 80 "assets/www/首页背景2.png" -o "assets/www/首页背景2.webp"
|
||||
cwebp -q 80 "assets/www/首页底图.png" -o "assets/www/首页底图.webp"
|
||||
```
|
||||
|
||||
**方案B:用 pngquant 压缩**
|
||||
```bash
|
||||
pngquant --quality=65-80 "assets/www/首页背景2.png" --output "assets/www/首页背景2.png"
|
||||
```
|
||||
|
||||
预期效果:4.7MB → 300-500KB(减少 **90%**)
|
||||
|
||||
### 阶段 2:构建配置优化(预估再减 30-40%)
|
||||
|
||||
#### 2.1 Android 构建优化
|
||||
|
||||
**修改 `android/app/build.gradle.kts`:**
|
||||
```kotlin
|
||||
android {
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("release") // 换正式签名
|
||||
|
||||
// 启用代码压缩和资源裁剪
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**构建命令使用 `--split-per-abi`:**
|
||||
```bash
|
||||
# 分架构打包,每个 APK 减少 30-40%
|
||||
flutter build apk --release --split-per-abi --obfuscate --split-debug-info=./debug-info
|
||||
|
||||
# 或 App Bundle(Google Play 自动按设备裁剪)
|
||||
flutter build appbundle --release --obfuscate --split-debug-info=./debug-info
|
||||
```
|
||||
|
||||
| 参数 | 作用 | 预估收益 |
|
||||
|------|------|----------|
|
||||
| `--split-per-abi` | 按 CPU 架构分包 | 减少 30-40% |
|
||||
| `--obfuscate` | 代码混淆 | 减少 5-10% |
|
||||
| `--split-debug-info` | 分离调试符号 | 减少 5-10% |
|
||||
| `minifyEnabled=true` | R8 代码压缩 | 减少 15-25% |
|
||||
| `shrinkResources=true` | 移除未使用资源 | 减少 5-10% |
|
||||
|
||||
#### 2.2 iOS 构建优化
|
||||
|
||||
**Xcode Build Settings 调整:**
|
||||
- Strip Debug Symbols During Copy → YES
|
||||
- Strip Linked Product → YES
|
||||
- Dead Code Stripping → YES
|
||||
- Optimization Level → -Oz (Smallest)
|
||||
|
||||
**构建命令:**
|
||||
```bash
|
||||
flutter build ipa --release --obfuscate --split-debug-info=./debug-info
|
||||
```
|
||||
|
||||
### 阶段 3:依赖瘦身
|
||||
|
||||
#### 3.1 审计当前依赖
|
||||
|
||||
| 依赖 | 大小影响 | 建议 |
|
||||
|------|----------|------|
|
||||
| `google_fonts` | 较大,会下载字体 | 已内嵌 Inter 字体,考虑移除 |
|
||||
| `webview_flutter` | ~4MB 原生库 | 仅在需要时加载(deferred) |
|
||||
| `video_player` | ~3-4MB | 考虑 deferred import |
|
||||
| `image` (dart包) | 纯 Dart 图像处理 | 检查是否真的需要 |
|
||||
| `http` | 与 `dio` 重复 | 统一用 dio,移除 http |
|
||||
| `flutter_svg` | 中等 | 保留(SVG 比 PNG 小得多) |
|
||||
|
||||
#### 3.2 移除 `google_fonts`
|
||||
|
||||
你已经在 `pubspec.yaml` 中内嵌了 Inter 字体,`google_fonts` 是多余的:
|
||||
```yaml
|
||||
# 移除这个依赖
|
||||
# google_fonts: ^6.1.0
|
||||
|
||||
# 已有本地字体
|
||||
fonts:
|
||||
- family: Inter
|
||||
fonts:
|
||||
- asset: assets/fonts/Inter-Regular.ttf
|
||||
# ...
|
||||
```
|
||||
|
||||
代码中改用 `TextStyle(fontFamily: 'Inter')` 替代 `GoogleFonts.inter()`。
|
||||
|
||||
#### 3.3 移除重复的 `http` 包
|
||||
```yaml
|
||||
# 移除 - 已有 dio 做网络请求
|
||||
# http: ^1.2.0
|
||||
```
|
||||
|
||||
#### 3.4 字体子集化
|
||||
|
||||
当前 4 个 Inter 字体文件共 1.6MB。如果只需要中英文:
|
||||
```bash
|
||||
# 安装 fonttools
|
||||
pip install fonttools brotli
|
||||
|
||||
# 裁剪只保留需要的字符集(中文 + 英文 + 数字 + 标点)
|
||||
pyftsubset Inter-Regular.ttf \
|
||||
--text-file=used_chars.txt \
|
||||
--output-file=Inter-Regular-subset.ttf
|
||||
|
||||
# 通常可从 400KB → 50-80KB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、性能优化:降低发热与 CPU 占用
|
||||
|
||||
### 3.1 Widget 重建优化(最重要)
|
||||
|
||||
#### 使用 `const` 构造函数
|
||||
```dart
|
||||
// ❌ 每次 build 都创建新实例
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16), // 每次创建新对象
|
||||
child: Text('Hello'),
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ 编译期常量,不会触发重建
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: const Text('Hello'),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**全项目检查:** 运行 `dart analyze` 查看 `prefer_const_constructors` 警告。
|
||||
|
||||
#### 使用 `RepaintBoundary` 隔离重绘区域
|
||||
```dart
|
||||
// 对频繁更新的局部 Widget 包裹 RepaintBoundary
|
||||
RepaintBoundary(
|
||||
child: AnimatedWidget(...), // 只重绘这部分,不影响外部
|
||||
)
|
||||
```
|
||||
|
||||
适用场景:
|
||||
- 动画区域
|
||||
- 频繁刷新的倒计时/进度条
|
||||
- BLE 数据实时显示
|
||||
|
||||
#### 拆分 Widget,避免大范围重建
|
||||
```dart
|
||||
// ❌ 整个页面一个大 build 方法
|
||||
class MyPage extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final state = ref.watch(someProvider); // 任何变化都重建整个页面
|
||||
return Scaffold(
|
||||
body: Column(children: [Header(), Content(state), Footer()]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 拆成独立 Widget,只监听需要的部分
|
||||
class _Content extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final data = ref.watch(someProvider.select((s) => s.data)); // 精准监听
|
||||
return ...;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Riverpod 状态管理优化
|
||||
|
||||
#### 使用 `select` 精准订阅
|
||||
```dart
|
||||
// ❌ 监听整个 state,任何字段变化都重建
|
||||
final state = ref.watch(deviceProvider);
|
||||
|
||||
// ✅ 只监听需要的字段
|
||||
final deviceName = ref.watch(deviceProvider.select((s) => s.name));
|
||||
final isConnected = ref.watch(deviceProvider.select((s) => s.isConnected));
|
||||
```
|
||||
|
||||
#### 区分 `watch` 和 `listen`
|
||||
```dart
|
||||
// watch: 用于 UI 显示的数据(需要重建)
|
||||
final name = ref.watch(provider.select((s) => s.name));
|
||||
|
||||
// listen: 用于副作用(弹窗、导航、日志),不触发重建
|
||||
ref.listen(provider, (prev, next) {
|
||||
if (next.hasError) showErrorDialog(context, next.error);
|
||||
});
|
||||
```
|
||||
|
||||
### 3.3 BLE 通信优化(降低发热的关键)
|
||||
|
||||
你的 app 大量使用蓝牙(`flutter_blue_plus`),这是发热的主要来源之一。
|
||||
|
||||
```dart
|
||||
// ❌ 持续高频扫描
|
||||
FlutterBluePlus.startScan(timeout: Duration(seconds: 30));
|
||||
|
||||
// ✅ 扫到目标设备后立即停止
|
||||
FlutterBluePlus.startScan(timeout: Duration(seconds: 10));
|
||||
// 找到目标后
|
||||
FlutterBluePlus.stopScan();
|
||||
|
||||
// ❌ 不必要的频繁读取特征值
|
||||
Timer.periodic(Duration(milliseconds: 100), (_) => characteristic.read());
|
||||
|
||||
// ✅ 使用 notify 替代轮询
|
||||
await characteristic.setNotifyValue(true);
|
||||
characteristic.onValueReceived.listen((value) {
|
||||
// 只在有新数据时处理
|
||||
});
|
||||
```
|
||||
|
||||
**BLE 省电策略:**
|
||||
- 连接成功后立即停止扫描
|
||||
- 使用 notification 而非 polling
|
||||
- 非活跃页面暂停 BLE 数据监听
|
||||
- 合理设置连接间隔参数
|
||||
|
||||
### 3.4 图片与内存管理
|
||||
|
||||
#### 网络图片缓存
|
||||
```yaml
|
||||
# 添加依赖
|
||||
dependencies:
|
||||
cached_network_image: ^3.3.0
|
||||
```
|
||||
|
||||
```dart
|
||||
// 使用缓存图片组件
|
||||
CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
memCacheWidth: 300, // 限制内存中图片尺寸
|
||||
memCacheHeight: 300,
|
||||
placeholder: (_, __) => const CircularProgressIndicator(),
|
||||
errorWidget: (_, __, ___) => const Icon(Icons.error),
|
||||
)
|
||||
```
|
||||
|
||||
#### 大图降采样
|
||||
```dart
|
||||
// ❌ 直接加载 4.7MB 的 PNG 到内存
|
||||
Image.asset('assets/www/首页背景2.png')
|
||||
|
||||
// ✅ 指定缓存尺寸,降低内存占用
|
||||
Image.asset(
|
||||
'assets/www/首页背景2.webp',
|
||||
cacheWidth: 750, // 按屏幕宽度限制
|
||||
cacheHeight: 1334,
|
||||
)
|
||||
```
|
||||
|
||||
#### 页面销毁时释放资源
|
||||
```dart
|
||||
class _MyPageState extends State<MyPage> {
|
||||
VideoPlayerController? _videoController;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_videoController?.dispose(); // 必须释放!
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Isolate 处理耗时任务
|
||||
|
||||
```dart
|
||||
// 图片处理、数据解析等放到 Isolate
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
// 简单任务用 compute
|
||||
final result = await compute(processImage, imageData);
|
||||
|
||||
// 复杂任务用 Isolate
|
||||
Future<Uint8List> processImageInIsolate(Uint8List rawData) async {
|
||||
return await Isolate.run(() {
|
||||
// 在独立线程处理图片
|
||||
return heavyImageProcessing(rawData);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
适用场景:
|
||||
- BLE 接收的大量数据解析
|
||||
- 图片编解码(badge 图片处理)
|
||||
- JSON 大数据解析
|
||||
|
||||
---
|
||||
|
||||
## 四、UI 流畅度优化(消除卡顿)
|
||||
|
||||
### 4.1 列表优化
|
||||
|
||||
#### 使用 `ListView.builder` 而非 `ListView`
|
||||
```dart
|
||||
// ❌ 一次性创建所有子 Widget
|
||||
ListView(children: items.map((e) => ItemWidget(e)).toList())
|
||||
|
||||
// ✅ 按需创建可见项
|
||||
ListView.builder(
|
||||
itemCount: items.length,
|
||||
itemExtent: 80, // 固定高度可提升滚动性能
|
||||
addAutomaticKeepAlives: false, // 不需要保活的列表关闭此项
|
||||
addRepaintBoundaries: true,
|
||||
itemBuilder: (context, index) => ItemWidget(items[index]),
|
||||
)
|
||||
```
|
||||
|
||||
#### 图片列表优化
|
||||
```dart
|
||||
ListView.builder(
|
||||
cacheExtent: 500, // 预缓存区域
|
||||
itemBuilder: (context, index) {
|
||||
return RepaintBoundary(
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: items[index].imageUrl,
|
||||
memCacheWidth: 200, // 限制内存图片大小
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 4.2 Impeller 渲染引擎(Flutter 3.41.2 默认启用)
|
||||
|
||||
你的 Flutter 版本已默认使用 Impeller 渲染引擎,优势:
|
||||
- **消除 Shader 编译卡顿**(Skia 的最大痛点)
|
||||
- 更稳定的帧率
|
||||
- 更低的 GPU 内存占用
|
||||
|
||||
验证 Impeller 是否启用:
|
||||
```bash
|
||||
# iOS 默认启用,Android 3.22+ 默认启用
|
||||
flutter run --profile # 然后查看 DevTools
|
||||
```
|
||||
|
||||
如果遇到兼容性问题可临时回退:
|
||||
```bash
|
||||
flutter run --no-enable-impeller
|
||||
```
|
||||
|
||||
### 4.3 Shader 预热(如仍用 Skia)
|
||||
|
||||
```dart
|
||||
// 在 main.dart 中
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// 预编译 shader 缓存
|
||||
await ShaderWarmUp().execute();
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
SkSL 收集命令:
|
||||
```bash
|
||||
# 1. 收集 shader
|
||||
flutter run --profile --cache-sksl --purge-persistent-cache
|
||||
|
||||
# 2. 操作 app 中所有页面和动画
|
||||
|
||||
# 3. 按 M 导出 flutter_01.sksl.json
|
||||
|
||||
# 4. 使用缓存构建
|
||||
flutter build apk --bundle-sksl-path flutter_01.sksl.json
|
||||
```
|
||||
|
||||
### 4.4 页面切换优化
|
||||
|
||||
```dart
|
||||
// go_router 中预加载关键页面
|
||||
GoRoute(
|
||||
path: '/device-control',
|
||||
pageBuilder: (context, state) => CustomTransitionPage(
|
||||
transitionDuration: const Duration(milliseconds: 200), // 缩短动画时间
|
||||
child: const DeviceControlPage(),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(opacity: animation, child: child); // 用 Fade 替代 Slide
|
||||
},
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### 4.5 WebView 优化
|
||||
|
||||
```dart
|
||||
// WebView 是重量级组件
|
||||
// 1. 延迟初始化
|
||||
late final WebViewController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 延迟到页面完全展示后再初始化 WebView
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_initWebView();
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 页面销毁时清理
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.clearCache();
|
||||
_controller.clearLocalStorage();
|
||||
super.dispose();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、工具与监控
|
||||
|
||||
### 5.1 包大小分析
|
||||
```bash
|
||||
# 分析 APK 大小组成
|
||||
flutter build apk --analyze-size
|
||||
|
||||
# 分析 iOS 包大小
|
||||
flutter build ipa --analyze-size
|
||||
```
|
||||
|
||||
### 5.2 性能分析
|
||||
```bash
|
||||
# Profile 模式运行(有性能数据但接近 release 性能)
|
||||
flutter run --profile
|
||||
|
||||
# 打开 DevTools
|
||||
flutter pub global activate devtools
|
||||
flutter pub global run devtools
|
||||
```
|
||||
|
||||
### 5.3 检测无用代码和依赖
|
||||
```bash
|
||||
# 检测未使用的依赖
|
||||
dart pub deps --no-dev
|
||||
|
||||
# 分析代码问题
|
||||
dart analyze
|
||||
```
|
||||
|
||||
### 5.4 Glance(APM 监控库)
|
||||
```yaml
|
||||
# 生产环境卡顿检测
|
||||
dependencies:
|
||||
glance_flutter: ^0.x.x # 实验性
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、实施优先级与预期效果
|
||||
|
||||
### P0 紧急(1-2天,收益巨大)
|
||||
|
||||
| 任务 | 预期收益 |
|
||||
|------|----------|
|
||||
| 视频/音频迁移到 CDN | 包大小 -107MB (80%) |
|
||||
| 背景图转 WebP 压缩 | 包大小 -8MB |
|
||||
| 启用 `minifyEnabled` + `shrinkResources` | 包大小 -15-25% |
|
||||
| `--split-per-abi` 分架构打包 | APK -30-40% |
|
||||
|
||||
### P1 重要(3-5天)
|
||||
|
||||
| 任务 | 预期收益 |
|
||||
|------|----------|
|
||||
| 移除 `google_fonts` 和 `http` 包 | 包大小 -2-4MB |
|
||||
| 字体子集化 | 包大小 -1.2MB |
|
||||
| `--obfuscate --split-debug-info` | 包大小 -5-10% |
|
||||
| BLE 扫描/通信优化 | 降低发热 30-50% |
|
||||
| 图片内存管理 (cacheWidth/Height) | 降低内存 40% |
|
||||
|
||||
### P2 优化(1-2周)
|
||||
|
||||
| 任务 | 预期收益 |
|
||||
|------|----------|
|
||||
| 全项目 const 构造函数审查 | 减少 Widget 重建 |
|
||||
| Riverpod select 精准订阅 | 减少不必要重建 |
|
||||
| 列表 ListView.builder 优化 | 滚动更流畅 |
|
||||
| 耗时任务移入 Isolate | 消除主线程卡顿 |
|
||||
| RepaintBoundary 隔离动画区域 | 减少重绘范围 |
|
||||
| Deferred import (webview/video) | 减小初始加载 |
|
||||
|
||||
### P3 持续(长期)
|
||||
|
||||
| 任务 | 预期收益 |
|
||||
|------|----------|
|
||||
| 接入性能监控 (Glance/Sentry) | 线上问题感知 |
|
||||
| 定期 `flutter build --analyze-size` | 包大小回归检测 |
|
||||
| CI 集成包大小检查 | 防止包大小退化 |
|
||||
|
||||
---
|
||||
|
||||
## 七、预期总体效果
|
||||
|
||||
| 指标 | 优化前 | 优化后(预估) | 改善 |
|
||||
|------|--------|----------------|------|
|
||||
| APK 大小 | ~150-180MB | ~25-35MB | **80%+** |
|
||||
| IPA 大小 | ~200MB+ | ~40-60MB | **70%+** |
|
||||
| 冷启动时间 | 3-5s | 1-2s | **60%** |
|
||||
| 内存占用 | 较高 | 降低 40%+ | **40%+** |
|
||||
| 手机发热 | 明显 | 温热 | **显著改善** |
|
||||
| 滚动帧率 | 有掉帧 | 稳定 60fps | **流畅** |
|
||||
|
||||
---
|
||||
|
||||
## 八、Impeller 渲染引擎性能数据
|
||||
|
||||
Flutter 3.41.2 已默认启用 Impeller(替代 Skia),关键性能数据:
|
||||
|
||||
| 维度 | Skia | Impeller | 改善 |
|
||||
|------|------|---------|------|
|
||||
| Shader 编译 | 运行时 JIT(首次卡顿 20-200ms) | 构建时 AOT 预编译 | **消除 Shader 卡顿** |
|
||||
| GPU 光栅化耗时 | 4.05ms | 2.81ms | **-30%** |
|
||||
| 120Hz 帧率达标率 | 67.1% | 91.6% | **+36%** |
|
||||
| 复杂场景掉帧率 | ~12% | ~1.5% | **-87%** |
|
||||
|
||||
确认 Impeller 状态:
|
||||
```bash
|
||||
flutter run --profile # 然后在 DevTools 中查看渲染后端
|
||||
```
|
||||
|
||||
Android 持久化配置(`AndroidManifest.xml`):
|
||||
```xml
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||
android:value="true" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 九、低端设备适配策略
|
||||
|
||||
针对不同档次手机动态降级:
|
||||
|
||||
```dart
|
||||
// 设备分级
|
||||
enum DeviceTier { low, mid, high }
|
||||
|
||||
DeviceTier getDeviceTier() {
|
||||
final memory = SysInfo.getTotalPhysicalMemory(); // 需原生获取
|
||||
if (memory < 3 * 1024 * 1024 * 1024) return DeviceTier.low;
|
||||
if (memory < 6 * 1024 * 1024 * 1024) return DeviceTier.mid;
|
||||
return DeviceTier.high;
|
||||
}
|
||||
```
|
||||
|
||||
| 策略 | 低端 | 中端 | 高端 |
|
||||
|------|------|------|------|
|
||||
| 动画 | 关闭粒子/复杂动画 | 简化动画 | 全特效 |
|
||||
| 图片 | WebP 降分辨率 30% | 标准分辨率 | 高清 |
|
||||
| BackdropFilter | 纯色替代 | 简化模糊 | 全高斯模糊 |
|
||||
| 并发网络请求 | 限 2 个 | 限 4 个 | 不限制 |
|
||||
|
||||
---
|
||||
|
||||
## 十、生产环境性能监控
|
||||
|
||||
| 工具 | 类型 | 用途 |
|
||||
|------|------|------|
|
||||
| [Glance](https://github.com/littleGnAl/glance) | 开源 APM | 采集卡顿时的堆栈,定位具体函数 |
|
||||
| [flutter_smooth](https://github.com/fzyzcjy/flutter_smooth) | 开源库 | 即使 Widget 树极重也能达到 ~60fps |
|
||||
| Firebase Performance | 云端 APM | 启动时间、网络延迟、自定义追踪 |
|
||||
| Sentry | 错误+性能 | 崩溃报告+性能事务 |
|
||||
|
||||
### 自动化性能测试(CI 集成)
|
||||
|
||||
```dart
|
||||
// integration_test/performance_test.dart
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
void main() {
|
||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('scrolling performance', (tester) async {
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await binding.traceAction(() async {
|
||||
await tester.fling(find.byType(ListView), const Offset(0, -500), 10000);
|
||||
await tester.pumpAndSettle();
|
||||
}, reportKey: 'scrolling_timeline');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、参考资源
|
||||
|
||||
### 官方文档
|
||||
- [Flutter Performance Best Practices](https://docs.flutter.dev/perf/best-practices)
|
||||
- [App Size Optimization](https://docs.flutter.dev/perf/app-size)
|
||||
- [Impeller Rendering Engine](https://docs.flutter.dev/perf/impeller)
|
||||
- [Deferred Components](https://docs.flutter.dev/perf/deferred-components)
|
||||
- [Shader Compilation Jank](https://docs.flutter.dev/perf/shader)
|
||||
|
||||
### 优秀开源项目参考
|
||||
- [flutter_deer](https://github.com/simplezhli/flutter_deer) — 完善的 Flutter 实践项目,代码规范
|
||||
- [Best-Flutter-UI-Templates](https://github.com/mitesh77/Best-Flutter-UI-Templates) — UI 模板合集
|
||||
- [Solido/awesome-flutter](https://github.com/Solido/awesome-flutter) — Flutter 最全资源汇总
|
||||
|
||||
### 工具
|
||||
- [Glance](https://pub.dev/packages/glance_flutter) — APM 卡顿检测
|
||||
- [TinyPNG](https://tinypng.com/) — PNG/WebP 压缩
|
||||
- [pngquant](https://pngquant.org/) — 批量 PNG 压缩
|
||||
- [pyftsubset](https://fonttools.readthedocs.io/) — 字体子集化
|
||||
@ -125,7 +125,11 @@ GoRouter goRouter(Ref ref) {
|
||||
final extra = state.extra as Map<String, dynamic>? ?? {};
|
||||
return BadgeTransferPage(
|
||||
imageUrl: extra['imageUrl'] as String? ?? '',
|
||||
imageBytes: extra['imageBytes'] as Uint8List?,
|
||||
imageBytes: extra['imageBytes'] is Uint8List
|
||||
? extra['imageBytes'] as Uint8List
|
||||
: extra['imageBytes'] is List
|
||||
? Uint8List.fromList(List<int>.from(extra['imageBytes'] as List))
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user