卡段优化
This commit is contained in:
936
docs/superpowers/plans/2026-05-22-performance-optimization.md
Normal file
936
docs/superpowers/plans/2026-05-22-performance-optimization.md
Normal file
@@ -0,0 +1,936 @@
|
||||
# 移动端性能优化 — 智能节流方案 实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 在不阉割任何视觉效果和动画的前提下,通过智能节流策略消除移动端发热和高资源占用问题
|
||||
|
||||
**Architecture:** 创建 PerformanceOrchestrator 性能调度中心单例,统一管理帧率节流、动画可见性感知、后台服务休眠、特效互斥调度。各组件主动注册到调度器,由调度器根据性能级别、电量、前后台状态决定运行策略。
|
||||
|
||||
**Tech Stack:** Flutter/Dart, Riverpod, battery_plus, sensors_plus, visibility_detector
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
### 新建文件
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `lib/core/services/performance/performance_orchestrator.dart` | 性能调度中心单例 — 帧率/级别/电量/前后台统一管理 |
|
||||
| `lib/core/services/performance/app_lifecycle_gate.dart` | 前后台统一管理门 — 暂停/恢复后台服务和动画 |
|
||||
| `lib/core/services/performance/effect_mutex.dart` | 特效互斥调度器 — 同一时刻限制重量级特效数量 |
|
||||
|
||||
### 修改文件
|
||||
| 文件 | 修改内容 |
|
||||
|------|---------|
|
||||
| `lib/shared/widgets/shader_card_background.dart` | Shader 帧率节流 + 可见性暂停 |
|
||||
| `lib/shared/widgets/appbar_character_sprite.dart` | 不可见时暂停 idle 动画 + setState→ValueNotifier |
|
||||
| `lib/shared/widgets/tab_icon_sprite.dart` | 未选中 Tab 停止 glow 动画 |
|
||||
| `lib/shared/widgets/glass_container.dart` | RepaintBoundary 包裹 + 缓存策略 |
|
||||
| `lib/shared/widgets/glass_bottom_nav_bar.dart` | RepaintBoundary 包裹 |
|
||||
| `lib/core/layout/app_shell.dart` | 集成 AppLifecycleGate |
|
||||
| `lib/app/app.dart` | 集成 AppLifecycleGate + PerformanceOrchestrator 初始化 |
|
||||
| `lib/main.dart` | 初始化 PerformanceOrchestrator |
|
||||
| `lib/core/services/device/shake_detector.dart` | 前后台暂停/恢复 |
|
||||
| `lib/core/services/clipboard_monitor_service.dart` | 前后台暂停/恢复 |
|
||||
| `lib/core/services/device/battery_info_service.dart` | 前后台暂停/恢复轮询 |
|
||||
| `lib/core/services/device/battery_optimization_service.dart` | 联动 PerformanceOrchestrator |
|
||||
| `lib/core/utils/interaction_animations.dart` | CelebrationOverlay 按需激活 |
|
||||
| `lib/features/home/presentation/home_refresh_indicator.dart` | setState→局部刷新 |
|
||||
| `CHANGELOG.md` | 记录本次优化 |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 创建 PerformanceOrchestrator 性能调度中心
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/core/services/performance/performance_orchestrator.dart`
|
||||
|
||||
- [ ] **Step 1: 创建 PerformanceOrchestrator 单例**
|
||||
|
||||
```dart
|
||||
// ============================================================
|
||||
// 闲言APP — 性能调度中心
|
||||
// 创建时间: 2026-05-22
|
||||
// 更新时间: 2026-05-22
|
||||
// 作用: 统一管理帧率节流/性能级别/电量联动/前后台切换
|
||||
// 上次更新: 初始创建
|
||||
// ============================================================
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:xianyan/core/services/device/battery_info_service.dart';
|
||||
import 'package:xianyan/core/storage/app_kv_store.dart';
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
|
||||
/// 性能级别
|
||||
enum PerformanceLevel {
|
||||
full('完整', 1, 1),
|
||||
balanced('平衡', 2, 30),
|
||||
saver('省电', 3, 20);
|
||||
|
||||
const PerformanceLevel(this.label, this.sortOrder, this.targetFps);
|
||||
final String label;
|
||||
final int sortOrder;
|
||||
final int targetFps;
|
||||
}
|
||||
|
||||
/// 帧率节流回调类型 — 返回 true 表示本帧需要更新
|
||||
typedef FrameThrottleCallback = bool Function();
|
||||
|
||||
class PerformanceOrchestrator {
|
||||
PerformanceOrchestrator._();
|
||||
static final PerformanceOrchestrator instance = PerformanceOrchestrator._();
|
||||
|
||||
static const _keyLevel = 'performance_level';
|
||||
|
||||
PerformanceLevel _level = PerformanceLevel.full;
|
||||
bool _isAppForeground = true;
|
||||
StreamSubscription<BatteryInfo>? _batterySub;
|
||||
final List<VoidCallback> _onForegroundCallbacks = [];
|
||||
final List<VoidCallback> _onBackgroundCallbacks = [];
|
||||
|
||||
PerformanceLevel get level => _level;
|
||||
bool get isAppForeground => _isAppForeground;
|
||||
bool get shouldThrottleShader => _level != PerformanceLevel.full;
|
||||
bool get shouldPauseAnimations => !_isAppForeground || _level == PerformanceLevel.saver;
|
||||
|
||||
/// 初始化 — 在 main() 中调用
|
||||
Future<void> init() async {
|
||||
final stored = AppKVStore.getString(_keyLevel);
|
||||
_level = _resolveLevel(stored);
|
||||
Log.i('PerformanceOrchestrator 初始化: level=${_level.label}');
|
||||
|
||||
_batterySub = BatteryInfoService.instance.onBatteryChanged.listen((info) {
|
||||
if (info.isCritical && _level != PerformanceLevel.saver) {
|
||||
setLevel(PerformanceLevel.saver);
|
||||
Log.i('PerformanceOrchestrator: 电量严重不足,自动切换省电模式');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 设置性能级别
|
||||
void setLevel(PerformanceLevel newLevel) {
|
||||
if (_level == newLevel) return;
|
||||
_level = newLevel;
|
||||
AppKVStore.setString(_keyLevel, newLevel.name);
|
||||
Log.i('PerformanceOrchestrator: 级别切换为 ${newLevel.label}');
|
||||
}
|
||||
|
||||
/// App 进入前台
|
||||
void onAppResumed() {
|
||||
if (_isAppForeground) return;
|
||||
_isAppForeground = true;
|
||||
Log.i('PerformanceOrchestrator: App 回到前台');
|
||||
for (final cb in _onForegroundCallbacks) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
/// App 进入后台
|
||||
void onAppPaused() {
|
||||
if (!_isAppForeground) return;
|
||||
_isAppForeground = false;
|
||||
Log.i('PerformanceOrchestrator: App 进入后台');
|
||||
for (final cb in _onBackgroundCallbacks) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
/// 注册前台回调
|
||||
void onForeground(VoidCallback callback) {
|
||||
_onForegroundCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/// 注册后台回调
|
||||
void onBackground(VoidCallback callback) {
|
||||
_onBackgroundCallbacks.add(callback);
|
||||
}
|
||||
|
||||
/// 移除回调
|
||||
void removeCallbacks(VoidCallback callback) {
|
||||
_onForegroundCallbacks.remove(callback);
|
||||
_onBackgroundCallbacks.remove(callback);
|
||||
}
|
||||
|
||||
/// 创建节流帧计数器 — 返回每 N 帧触发一次的判断函数
|
||||
/// full: 每1帧, balanced: 每2帧, saver: 每3帧
|
||||
FrameThrottleCallback createFrameThrottle() {
|
||||
int frameCount = 0;
|
||||
return () {
|
||||
frameCount++;
|
||||
final skip = switch (_level) {
|
||||
PerformanceLevel.full => 1,
|
||||
PerformanceLevel.balanced => 2,
|
||||
PerformanceLevel.saver => 3,
|
||||
};
|
||||
if (frameCount % skip == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
PerformanceLevel _resolveLevel(String? stored) {
|
||||
return switch (stored) {
|
||||
'full' => PerformanceLevel.full,
|
||||
'balanced' => PerformanceLevel.balanced,
|
||||
'saver' => PerformanceLevel.saver,
|
||||
_ => PerformanceLevel.full,
|
||||
};
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_batterySub?.cancel();
|
||||
_onForegroundCallbacks.clear();
|
||||
_onBackgroundCallbacks.clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证文件创建**
|
||||
|
||||
Run: `dart analyze lib/core/services/performance/performance_orchestrator.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 创建 AppLifecycleGate 前后台管理门
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/core/services/performance/app_lifecycle_gate.dart`
|
||||
|
||||
- [ ] **Step 1: 创建 AppLifecycleGate**
|
||||
|
||||
```dart
|
||||
// ============================================================
|
||||
// 闲言APP — 前后台统一管理门
|
||||
// 创建时间: 2026-05-22
|
||||
// 更新时间: 2026-05-22
|
||||
// 作用: 统一管理前后台切换时暂停/恢复后台服务和动画
|
||||
// 上次更新: 初始创建
|
||||
// ============================================================
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:xianyan/core/services/performance/performance_orchestrator.dart';
|
||||
import 'package:xianyan/core/services/device/shake_detector.dart';
|
||||
import 'package:xianyan/core/services/clipboard_monitor_service.dart';
|
||||
import 'package:xianyan/core/services/device/battery_info_service.dart';
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
|
||||
/// App 生命周期统一管理门
|
||||
///
|
||||
/// 在 App 根组件中混入,统一管理前后台切换时:
|
||||
/// - 暂停/恢复传感器(摇一摇)
|
||||
/// - 暂停/恢复剪贴板监控
|
||||
/// - 暂停/恢复电池轮询
|
||||
/// - 通知 PerformanceOrchestrator 前后台状态
|
||||
mixin AppLifecycleGate on WidgetsBindingObserver {
|
||||
bool _lifecycleGateInitialized = false;
|
||||
|
||||
void initLifecycleGate() {
|
||||
if (_lifecycleGateInitialized) return;
|
||||
_lifecycleGateInitialized = true;
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
void disposeLifecycleGate() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_lifecycleGateInitialized = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
switch (state) {
|
||||
case AppLifecycleState.paused:
|
||||
_onEnterBackground();
|
||||
case AppLifecycleState.resumed:
|
||||
_onEnterForeground();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _onEnterBackground() {
|
||||
Log.i('AppLifecycleGate: 进入后台 — 暂停服务和动画');
|
||||
PerformanceOrchestrator.instance.onAppPaused();
|
||||
|
||||
ShakeDetector.instance.stop();
|
||||
ClipboardMonitorService.instance.stopMonitor();
|
||||
BatteryInfoService.instance.pausePolling();
|
||||
}
|
||||
|
||||
void _onEnterForeground() {
|
||||
Log.i('AppLifecycleGate: 回到前台 — 恢复服务和动画');
|
||||
PerformanceOrchestrator.instance.onAppResumed();
|
||||
|
||||
if (ShakeDetector.instance.isEnabled) {
|
||||
ShakeDetector.instance.start();
|
||||
}
|
||||
if (ClipboardMonitorService.instance.isEnabled) {
|
||||
ClipboardMonitorService.instance.startMonitor();
|
||||
}
|
||||
BatteryInfoService.instance.resumePolling();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 为 BatteryInfoService 添加 pausePolling/resumePolling 方法**
|
||||
|
||||
修改 `lib/core/services/device/battery_info_service.dart`,添加:
|
||||
|
||||
```dart
|
||||
bool _pollingPaused = false;
|
||||
|
||||
void pausePolling() {
|
||||
_pollingPaused = true;
|
||||
_pollTimer?.cancel();
|
||||
_pollTimer = null;
|
||||
Log.i('BatteryInfoService: 轮询已暂停');
|
||||
}
|
||||
|
||||
void resumePolling() {
|
||||
if (!_pollingPaused) return;
|
||||
_pollingPaused = false;
|
||||
_pollTimer?.cancel();
|
||||
_pollTimer = Timer.periodic(const Duration(minutes: 5), (_) async {
|
||||
try {
|
||||
_currentLevel = await _battery.batteryLevel;
|
||||
_notify();
|
||||
} catch (_) {}
|
||||
});
|
||||
Log.i('BatteryInfoService: 轮询已恢复');
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证**
|
||||
|
||||
Run: `dart analyze lib/core/services/performance/app_lifecycle_gate.dart lib/core/services/device/battery_info_service.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 创建 EffectMutex 特效互斥调度器
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/core/services/performance/effect_mutex.dart`
|
||||
|
||||
- [ ] **Step 1: 创建 EffectMutex**
|
||||
|
||||
```dart
|
||||
// ============================================================
|
||||
// 闲言APP — 特效互斥调度器
|
||||
// 创建时间: 2026-05-22
|
||||
// 更新时间: 2026-05-22
|
||||
// 作用: 限制同一时刻运行的重量级特效数量,防止GPU过载
|
||||
// 上次更新: 初始创建
|
||||
// ============================================================
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:xianyan/core/utils/logger.dart';
|
||||
|
||||
/// 特效优先级
|
||||
enum EffectPriority {
|
||||
interaction(0),
|
||||
entrance(1),
|
||||
decoration(2);
|
||||
|
||||
const EffectPriority(this.value);
|
||||
final int value;
|
||||
}
|
||||
|
||||
/// 特效令牌 — 持有者表示正在运行一个重量级特效
|
||||
class EffectToken {
|
||||
EffectToken._(this._mutex, this.name);
|
||||
final EffectMutex _mutex;
|
||||
final String name;
|
||||
bool _released = false;
|
||||
|
||||
bool get isActive => !_released;
|
||||
|
||||
void release() {
|
||||
if (_released) return;
|
||||
_released = true;
|
||||
_mutex._release(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// 特效互斥调度器
|
||||
///
|
||||
/// 限制同一时刻运行的重量级特效数量。
|
||||
/// 当已有 maxConcurrent 个特效运行时,低优先级特效会被延迟。
|
||||
class EffectMutex {
|
||||
EffectMutex({this.maxConcurrent = 2});
|
||||
|
||||
final int maxConcurrent;
|
||||
final List<EffectToken> _active = [];
|
||||
final List<_PendingEffect> _pending = [];
|
||||
|
||||
/// 申请一个特效槽位
|
||||
///
|
||||
/// 如果当前活跃特效数未达上限,立即返回 token;
|
||||
/// 否则等待直到有槽位释放。
|
||||
Future<EffectToken> acquire(String name, {EffectPriority priority = EffectPriority.decoration}) async {
|
||||
if (_active.length < maxConcurrent) {
|
||||
final token = EffectToken._(this, name);
|
||||
_active.add(token);
|
||||
Log.i('EffectMutex: "$name" 获得槽位 (${_active.length}/$maxConcurrent)');
|
||||
return token;
|
||||
}
|
||||
|
||||
final completer = Completer<EffectToken>();
|
||||
_pending.add(_PendingEffect(name, priority, completer));
|
||||
Log.i('EffectMutex: "$name" 排队等待 (优先级=${priority.name}, 队列=${_pending.length})');
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _release(EffectToken token) {
|
||||
_active.remove(token);
|
||||
Log.i('EffectMutex: "${token.name}" 释放槽位 (${_active.length}/$maxConcurrent)');
|
||||
|
||||
if (_pending.isNotEmpty) {
|
||||
_pending.sort((a, b) => a.priority.value.compareTo(b.priority.value));
|
||||
final next = _pending.removeAt(0);
|
||||
final newToken = EffectToken._(this, next.name);
|
||||
_active.add(newToken);
|
||||
next.completer.complete(newToken);
|
||||
Log.i('EffectMutex: "${next.name}" 获得槽位 (${_active.length}/$maxConcurrent)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _PendingEffect {
|
||||
_PendingEffect(this.name, this.priority, this.completer);
|
||||
final String name;
|
||||
final EffectPriority priority;
|
||||
final Completer<EffectToken> completer;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证**
|
||||
|
||||
Run: `dart analyze lib/core/services/performance/effect_mutex.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Shader 帧率节流 — 修改 ShaderCardBackground
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/shared/widgets/shader_card_background.dart`
|
||||
|
||||
- [ ] **Step 1: 添加帧率节流 + 可见性感知**
|
||||
|
||||
将 `_ShaderCardBackgroundState` 修改为:
|
||||
|
||||
```dart
|
||||
class _ShaderCardBackgroundState extends State<ShaderCardBackground>
|
||||
with SingleTickerProviderStateMixin, WidgetsBindingObserver {
|
||||
late Ticker _ticker;
|
||||
final ValueNotifier<double> _timeNotifier = ValueNotifier(0);
|
||||
ui.FragmentProgram? _program;
|
||||
bool _loaded = false;
|
||||
bool _isVisible = true;
|
||||
late FrameThrottleCallback _shouldRenderFrame;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_shouldRenderFrame = PerformanceOrchestrator.instance.createFrameThrottle();
|
||||
_ticker = createTicker(_onTick);
|
||||
_ticker.start();
|
||||
_loadShader();
|
||||
|
||||
PerformanceOrchestrator.instance.onForeground(_resumeTicker);
|
||||
PerformanceOrchestrator.instance.onBackground(_pauseTicker);
|
||||
}
|
||||
|
||||
void _pauseTicker() {
|
||||
if (_ticker.isActive) _ticker.stop();
|
||||
}
|
||||
|
||||
void _resumeTicker() {
|
||||
if (!_ticker.isActive && _isVisible) _ticker.start();
|
||||
}
|
||||
|
||||
void _onTick(Duration elapsed) {
|
||||
if (!_shouldRenderFrame()) return;
|
||||
_timeNotifier.value = elapsed.inMicroseconds / 1000000.0;
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.paused) {
|
||||
_pauseTicker();
|
||||
} else if (state == AppLifecycleState.resumed && _isVisible) {
|
||||
_resumeTicker();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
PerformanceOrchestrator.instance.removeCallbacks(_resumeTicker);
|
||||
PerformanceOrchestrator.instance.removeCallbacks(_pauseTicker);
|
||||
_ticker.dispose();
|
||||
_timeNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_loaded || _program == null) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
const Color(0xFF6699EE).withValues(alpha: 0.6),
|
||||
const Color(0xFFB366CC).withValues(alpha: 0.6),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return RepaintBoundary(
|
||||
child: CustomPaint(
|
||||
painter: _ShaderPainter(
|
||||
program: _program!,
|
||||
timeNotifier: _timeNotifier,
|
||||
touch: widget.touchOffset ?? Offset.zero,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
同时添加 import:
|
||||
```dart
|
||||
import 'package:xianyan/core/services/performance/performance_orchestrator.dart';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证**
|
||||
|
||||
Run: `dart analyze lib/shared/widgets/shader_card_background.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 5: 动画可见性感知 — 修改 AppBarCharacterSprite
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/shared/widgets/appbar_character_sprite.dart`
|
||||
|
||||
- [ ] **Step 1: 添加前后台感知 + 不可见时暂停 idle 动画**
|
||||
|
||||
在 `AppBarCharacterSpriteState` 中添加:
|
||||
|
||||
```dart
|
||||
// 在类顶部添加
|
||||
bool _isAppForeground = true;
|
||||
|
||||
// 在 initState 末尾添加
|
||||
PerformanceOrchestrator.instance.onForeground(_onForeground);
|
||||
PerformanceOrchestrator.instance.onBackground(_onBackground);
|
||||
|
||||
// 添加方法
|
||||
void _onForeground() {
|
||||
_isAppForeground = true;
|
||||
if (widget.animationIntensity > 0.0 && !_idleController.isAnimating) {
|
||||
_idleController.repeat(reverse: true);
|
||||
}
|
||||
}
|
||||
|
||||
void _onBackground() {
|
||||
_isAppForeground = false;
|
||||
_idleController.stop();
|
||||
}
|
||||
|
||||
// 修改 _idleController.repeat 的位置 — 在 initState 中
|
||||
// 原来: if (widget.animationIntensity > 0.0) _idleController.repeat(reverse: true);
|
||||
// 改为: if (widget.animationIntensity > 0.0 && _isAppForeground) _idleController.repeat(reverse: true);
|
||||
|
||||
// 在 didUpdateWidget 中修改 idle 恢复逻辑
|
||||
// 原来: } else if (!_idleController.isAnimating) {
|
||||
// 改为: } else if (!_idleController.isAnimating && _isAppForeground) {
|
||||
|
||||
// 在 dispose 中添加
|
||||
PerformanceOrchestrator.instance.removeCallbacks(_onForeground);
|
||||
PerformanceOrchestrator.instance.removeCallbacks(_onBackground);
|
||||
```
|
||||
|
||||
添加 import:
|
||||
```dart
|
||||
import 'package:xianyan/core/services/performance/performance_orchestrator.dart';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 优化 onPointerMove 中的 setState**
|
||||
|
||||
将 `onPointerMove` 中的 `setState(() {})` 替换为局部刷新。由于 `_eyeOffset` 被 `AnimatedBuilder` 内部使用,且 `AnimatedBuilder` 已经在监听 6 个 Controller,所以 `_eyeOffset` 的变化需要触发重绘。最佳方案是将 `_eyeOffset` 改为 `ValueNotifier<Offset>`:
|
||||
|
||||
```dart
|
||||
// 替换 Offset _eyeOffset = Offset.zero; 为:
|
||||
final ValueNotifier<Offset> _eyeOffsetNotifier = ValueNotifier(Offset.zero);
|
||||
|
||||
// onPointerMove 中替换 setState(() {}); 为:
|
||||
_eyeOffsetNotifier.value = Offset(
|
||||
(delta.dx / distance) * factor,
|
||||
(delta.dy / distance) * factor,
|
||||
);
|
||||
|
||||
// lookAtTitle 中替换 _eyeOffset = ... 为:
|
||||
_eyeOffsetNotifier.value = const Offset(3.0, 0.0);
|
||||
// 和:
|
||||
_eyeOffsetNotifier.value = Offset.zero;
|
||||
|
||||
// AnimatedBuilder 的 animation 改为:
|
||||
animation: Listenable.merge([
|
||||
_bounceController,
|
||||
_earController,
|
||||
_noseController,
|
||||
_cheekController,
|
||||
_expressionController,
|
||||
_idleController,
|
||||
_eyeOffsetNotifier,
|
||||
]),
|
||||
|
||||
// builder 内部替换 _eyeOffset 为 _eyeOffsetNotifier.value
|
||||
|
||||
// dispose 中添加:
|
||||
_eyeOffsetNotifier.dispose();
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证**
|
||||
|
||||
Run: `dart analyze lib/shared/widgets/appbar_character_sprite.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Tab 动画节流 — 修改 TabIconSprite
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/shared/widgets/tab_icon_sprite.dart`
|
||||
|
||||
- [ ] **Step 1: 未选中 Tab 停止 glow 呼吸动画**
|
||||
|
||||
在 `_TabIconSpriteState` 的 `didUpdateWidget` 中,当 Tab 从选中变为未选中时,`_glowController.stop()` 已经存在。需要确保未选中的 Tab 不运行 glow repeat:
|
||||
|
||||
```dart
|
||||
// 在 didUpdateWidget 中,!widget.isSelected && oldWidget.isSelected 分支
|
||||
// 已有 _glowController.stop(); 确认无误
|
||||
|
||||
// 在 initState 中,只有 isSelected 时才 repeat glow
|
||||
// 已有 if (widget.isSelected) { _glowController.repeat(reverse: true); }
|
||||
// 确认无误
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 添加前后台感知**
|
||||
|
||||
```dart
|
||||
// 在 _TabIconSpriteState 中添加
|
||||
bool _isAppForeground = true;
|
||||
|
||||
// initState 末尾添加
|
||||
PerformanceOrchestrator.instance.onForeground(_onForeground);
|
||||
PerformanceOrchestrator.instance.onBackground(_onBackground);
|
||||
|
||||
void _onForeground() {
|
||||
_isAppForeground = true;
|
||||
if (widget.isSelected) {
|
||||
_glowController.repeat(reverse: true);
|
||||
}
|
||||
}
|
||||
|
||||
void _onBackground() {
|
||||
_isAppForeground = false;
|
||||
_glowController.stop();
|
||||
}
|
||||
|
||||
// dispose 中添加
|
||||
PerformanceOrchestrator.instance.removeCallbacks(_onForeground);
|
||||
PerformanceOrchestrator.instance.removeCallbacks(_onBackground);
|
||||
```
|
||||
|
||||
添加 import:
|
||||
```dart
|
||||
import 'package:xianyan/core/services/performance/performance_orchestrator.dart';
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证**
|
||||
|
||||
Run: `dart analyze lib/shared/widgets/tab_icon_sprite.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 7: BackdropFilter 缓存优化 — 修改 GlassContainer
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/shared/widgets/glass_container.dart`
|
||||
|
||||
- [ ] **Step 1: 添加 RepaintBoundary 包裹**
|
||||
|
||||
在 `build` 方法中,将 `child` 用 `RepaintBoundary` 包裹,减少 BackdropFilter 的重绘范围:
|
||||
|
||||
```dart
|
||||
// 在所有 _maybeBackdropFilter 调用中,将 child 参数用 RepaintBoundary 包裹
|
||||
// 例如:
|
||||
child: _maybeBackdropFilter(
|
||||
sigma: cfg.blurSigma * multiplier,
|
||||
child: RepaintBoundary(child: Padding(padding: effectivePadding, child: child)),
|
||||
),
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证**
|
||||
|
||||
Run: `dart analyze lib/shared/widgets/glass_container.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 8: GlassBottomNavBar 缓存优化
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/shared/widgets/glass_bottom_nav_bar.dart`
|
||||
|
||||
- [ ] **Step 1: 添加 RepaintBoundary**
|
||||
|
||||
在 `build` 方法中,将整个 BackdropFilter 子树用 RepaintBoundary 包裹:
|
||||
|
||||
```dart
|
||||
// 在 build 方法的 return Container(...) 外层包裹 RepaintBoundary
|
||||
return RepaintBoundary(
|
||||
child: Container(
|
||||
margin: ...
|
||||
...
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证**
|
||||
|
||||
Run: `dart analyze lib/shared/widgets/glass_bottom_nav_bar.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 9: 集成 AppLifecycleGate 到 App 根组件
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/app/app.dart`
|
||||
- Modify: `lib/main.dart`
|
||||
|
||||
- [ ] **Step 1: 在 _XianyanAppState 中混入 AppLifecycleGate**
|
||||
|
||||
```dart
|
||||
// 修改类声明
|
||||
class _XianyanAppState extends ConsumerState<XianyanApp>
|
||||
with WidgetsBindingObserver, AppLifecycleGate {
|
||||
|
||||
// 修改 initState
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initLifecycleGate();
|
||||
}
|
||||
|
||||
// 修改 dispose
|
||||
@override
|
||||
void dispose() {
|
||||
disposeLifecycleGate();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 删除原有的 didChangeAppLifecycleState 方法(AppLifecycleGate 已处理)
|
||||
// 但保留 AppLockService 相关逻辑,在 AppLifecycleGate 的重写中调用
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
switch (state) {
|
||||
case AppLifecycleState.paused:
|
||||
try { AppLockService.onAppPaused(); } catch (e) { Log.e('应用锁暂停处理失败', e); }
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
Future.microtask(() {
|
||||
try { AppLockService.onAppResumed(); } catch (e) { Log.e('应用锁恢复处理失败', e); }
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
添加 import:
|
||||
```dart
|
||||
import 'package:xianyan/core/services/performance/app_lifecycle_gate.dart';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在 main.dart 中初始化 PerformanceOrchestrator**
|
||||
|
||||
在 `main()` 函数中,`BatteryOptimizationService.init()` 之后添加:
|
||||
|
||||
```dart
|
||||
try {
|
||||
await PerformanceOrchestrator.instance.init();
|
||||
if (pu.isOhos) Log.i('🟢 [OHOS] 性能调度中心初始化完成');
|
||||
} catch (e, st) {
|
||||
Log.e('性能调度中心初始化失败', e, st);
|
||||
}
|
||||
```
|
||||
|
||||
添加 import:
|
||||
```dart
|
||||
import 'core/services/performance/performance_orchestrator.dart';
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证**
|
||||
|
||||
Run: `dart analyze lib/app/app.dart lib/main.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 10: CelebrationOverlay 按需激活
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/core/utils/interaction_animations.dart`
|
||||
|
||||
- [ ] **Step 1: 修改 CelebrationOverlay 使用 EffectMutex**
|
||||
|
||||
```dart
|
||||
// 在 CelebrationOverlayState 中添加
|
||||
EffectToken? _effectToken;
|
||||
|
||||
// 修改 celebrate 方法
|
||||
void celebrate() async {
|
||||
final mutex = EffectMutex(maxConcurrent: 2);
|
||||
_effectToken = await mutex.acquire('celebration', priority: EffectPriority.decoration);
|
||||
_centerController.play();
|
||||
}
|
||||
|
||||
// 在 ConfettiWidget 的 onComplete 或 _centerController 的状态监听中释放 token
|
||||
// 修改 initState:
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_centerController = ConfettiController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
);
|
||||
_centerController.addListener(() {
|
||||
if (_centerController.state == ConfettiControllerState.stopped && _effectToken != null) {
|
||||
_effectToken!.release();
|
||||
_effectToken = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
添加 import:
|
||||
```dart
|
||||
import 'package:xianyan/core/services/performance/effect_mutex.dart';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证**
|
||||
|
||||
Run: `dart analyze lib/core/utils/interaction_animations.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 11: HomeRefreshIndicator 局部刷新优化
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/features/home/presentation/home_refresh_indicator.dart`
|
||||
|
||||
- [ ] **Step 1: 将 _pullProgress 改为 ValueNotifier**
|
||||
|
||||
```dart
|
||||
// 替换 double _pullProgress = 0.0; 为:
|
||||
final ValueNotifier<double> _pullProgressNotifier = ValueNotifier(0.0);
|
||||
|
||||
// 替换所有 setState(() => _pullProgress = newProgress); 为:
|
||||
_pullProgressNotifier.value = newProgress;
|
||||
|
||||
// 替换所有 _pullProgress 读取为 _pullProgressNotifier.value
|
||||
|
||||
// 在 _displayProgress getter 中使用 _pullProgressNotifier.value
|
||||
|
||||
// 在 _onResetTick 中,将 setState(() {}); 改为直接通知 _pullProgressNotifier
|
||||
// 由于 _resetController 的 listener 已经在更新,只需确保 AnimatedBuilder 监听 _pullProgressNotifier
|
||||
|
||||
// 在 build 方法中,用 AnimatedBuilder 包裹需要响应 _pullProgress 的部分:
|
||||
// 将 progress 变量改为从 _pullProgressNotifier 获取
|
||||
```
|
||||
|
||||
注意:由于此文件较复杂,涉及多个状态变量(`_isRefreshing`, `_isComplete`, `_pullProgress`),最安全的做法是将 `_pullProgress` 改为 `ValueNotifier`,其他保持 `setState` 不变(因为它们变化频率低)。
|
||||
|
||||
- [ ] **Step 2: 验证**
|
||||
|
||||
Run: `dart analyze lib/features/home/presentation/home_refresh_indicator.dart`
|
||||
Expected: 无错误
|
||||
|
||||
---
|
||||
|
||||
## Task 12: 全局编译验证 + CHANGELOG 更新
|
||||
|
||||
**Files:**
|
||||
- Modify: `CHANGELOG.md`
|
||||
|
||||
- [ ] **Step 1: 运行全局编译验证**
|
||||
|
||||
Run: `cd e:\project\flutter\f\xianyan && flutter analyze`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 2: 更新 CHANGELOG.md**
|
||||
|
||||
在文件顶部添加新版本记录:
|
||||
|
||||
```markdown
|
||||
## [v14.82.0] - 2026-05-22
|
||||
|
||||
### 移动端性能优化 — 智能节流方案
|
||||
|
||||
**问题**:移动端发热严重,GPU/CPU 资源占用高,后台持续消耗电量
|
||||
|
||||
**根因分析**:
|
||||
- Fragment Shader 每帧 60fps 无间断运行
|
||||
- 21+ 个 AnimationController 全天候运行(角色精灵 + Tab 图标)
|
||||
- 149 处 BackdropFilter 每帧重新模糊
|
||||
- 8+ 个后台服务持续监听(传感器/剪贴板/电池)
|
||||
- 多个重量级特效同时运行(Lottie + Confetti + Shader + Tilt)
|
||||
|
||||
**优化方案**(不阉割任何视觉效果):
|
||||
- ✅ 新增 PerformanceOrchestrator 性能调度中心 — 统一管理帧率/级别/电量联动
|
||||
- ✅ Shader 帧率节流 — full 60fps / balanced 30fps / saver 20fps
|
||||
- ✅ 动画前后台感知 — App 后台时自动暂停所有 repeat 动画
|
||||
- ✅ BackdropFilter 缓存 — RepaintBoundary 减少重绘范围
|
||||
- ✅ AppLifecycleGate 前后台管理门 — 统一暂停/恢复传感器和服务
|
||||
- ✅ EffectMutex 特效互斥调度器 — 限制同时运行的重量级特效数量
|
||||
- ✅ HomeRefreshIndicator 局部刷新 — ValueNotifier 替代 setState
|
||||
- ✅ AppBarCharacterSprite 局部刷新 — _eyeOffset 改为 ValueNotifier
|
||||
|
||||
**新增文件**:
|
||||
- `lib/core/services/performance/performance_orchestrator.dart`
|
||||
- `lib/core/services/performance/app_lifecycle_gate.dart`
|
||||
- `lib/core/services/performance/effect_mutex.dart`
|
||||
|
||||
**预期效果**:
|
||||
- GPU 平均帧耗时 ↓60%
|
||||
- 空闲 CPU 占用 ↓80%
|
||||
- 后台 CPU 占用 ↓95%
|
||||
- 电池续航接近正常 APP
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 最终验证**
|
||||
|
||||
Run: `cd e:\project\flutter\f\xianyan && flutter analyze`
|
||||
Expected: 无错误
|
||||
Reference in New Issue
Block a user