大家好,歡迎來到第二十天!在 Day 19,我們建立了完整的 Firebase Analytics 數據分析系統。今天,我們要解決另一個重要問題:如何了解和優化 App 的效能表現?
從專案開發經驗來看,效能問題往往隱藏在使用者的真實使用場景中。在開發環境中一切順暢,但部署到使用者手機上卻可能遇到載入緩慢、卡頓等問題。今天我們要為 Crew Up! 建立效能監控系統,就像 App 的健康儀表板,幫助我們用真實數據發現和解決效能瓶頸。
在開發 Crew Up! 的過程中,我們發現幾個常見的效能盲點:
🔍 開發環境的假象
📊 真實使用場景的挑戰
Firebase Performance Monitoring 提供了全面的效能監控能力,包含:
📊 自動收集的效能指標
Firebase 會自動為您收集以下重要指標,無需額外程式碼:
App 啟動時間:測量從使用者點擊圖示到 App 可互動的時間
畫面渲染效能:自動追蹤每個 Screen 的渲染表現
HTTP 網路請求:自動監控所有 HTTP/HTTPS 請求
💡 重要提示:因為 Crew Up! 專案使用
cloud_firestore
套件,所有對 Firestore 的讀寫操作(如getActivities()
、createActivity()
)都會被自動記錄為網路請求,並出現在 Performance Monitoring 儀表板中。這意味著您無需額外程式碼就能監控資料庫效能!
🔧 自定義追蹤能力
除了自動收集,我們還可以透過 PerformanceService
針對業務邏輯建立自定義追蹤:
這種架構設計遵循「關注點分離」原則,透過統一的服務層管理所有效能監控,讓程式碼更易維護。
📈 數據驅動的優化決策
# pubspec.yaml
dependencies:
firebase_performance: ^0.11.0+2 # Firebase Performance Monitoring SDK
firebase_core: ^4.1.0 # Firebase 核心
flutter_riverpod: ^2.6.1 # 狀態管理
💡 重要提醒:
- Firebase 專案設定請參考 Day 13 - Firebase 專案設定
- 確保
google-services.json
(Android) 和GoogleService-Info.plist
(iOS) 正確配置,這些檔案包含了 Firebase Performance Monitoring 所需的專案識別資訊- 在 Firebase Console 中啟用 Performance Monitoring 服務
參考 Day 18 的 CrashlyticsService
和 Day 19 的 AnalyticsService
設計理念,我們建立了統一的 PerformanceService
:
// lib/app/core/services/performance_service.dart
/// Firebase Performance Monitoring 服務
class PerformanceService {
final FirebasePerformance _performance;
PerformanceService({FirebasePerformance? performance})
: _performance = performance ?? FirebasePerformance.instance;
/// 初始化 Performance Monitoring
/// 支援 --dart-define 參數和 Debug 模式自動啟用
static Future<void> initialize() async {
try {
final performance = FirebasePerformance.instance;
// 檢查 --dart-define 參數
const debugEnabled = String.fromEnvironment('FIRDebugEnabled');
const performanceDebugEnabled = String.fromEnvironment(
'FIRPerformanceDebugEnabled',
);
// 決定是否啟用 Performance Monitoring
bool shouldEnable = kDebugMode; // 預設:Debug 模式下啟用
if (debugEnabled.isNotEmpty) {
shouldEnable = debugEnabled == '1';
developer.log(
'📊 Performance Monitoring ${shouldEnable ? 'enabled' : 'disabled'} via FIRDebugEnabled=$debugEnabled',
name: 'PerformanceService',
);
} else if (performanceDebugEnabled.isNotEmpty) {
shouldEnable = performanceDebugEnabled == '1';
developer.log(
'📊 Performance Monitoring ${shouldEnable ? 'enabled' : 'disabled'} via FIRPerformanceDebugEnabled=$performanceDebugEnabled',
name: 'PerformanceService',
);
} else if (kDebugMode) {
developer.log(
'📊 Performance Monitoring enabled in debug mode',
name: 'PerformanceService',
);
}
await performance.setPerformanceCollectionEnabled(shouldEnable);
developer.log(
'✅ Performance Monitoring initialized successfully',
name: 'PerformanceService',
);
} on Exception catch (e) {
developer.log(
'❌ Performance Monitoring initialization failed: $e',
name: 'PerformanceService',
level: 1000,
);
}
}
// ... 其他方法
}
@riverpod
PerformanceService performanceService(Ref ref) => PerformanceService();
🎯 設計重點說明
與專案架構一致
遵循 Day 18 Crashlytics 和 Day 19 Analytics 的設計模式:
initialize()
方法在 Firebase 初始化時呼叫@riverpod
註解提供依賴注入彈性的啟用控制機制
與 Crashlytics 不同,Performance Monitoring 在 Debug 模式下啟用更有價值(開發時即時發現效能問題)。Crew Up! 專案實作了三層優先級的控制機制:
控制優先級(由高到低):
FIRDebugEnabled
(最高優先級)
1
時啟用所有 Firebase Debug 功能(包含 Performance)0
時停用FIRPerformanceDebugEnabled
(次優先級)
kDebugMode
(預設行為)
--dart-define
參數實際使用範例:
# 場景 1:在 production 環境中啟用所有 Firebase Debug(包含 Performance)
flutter run --flavor production \
--dart-define=FIRDebugEnabled=1
# 場景 2:只啟用 Performance Monitoring Debug
flutter run --flavor production \
--dart-define=FIRPerformanceDebugEnabled=1
# 場景 3:在 Debug 模式下明確停用 Performance Monitoring
flutter run --flavor development \
--dart-define=FIRPerformanceDebugEnabled=0
# 場景 4:不傳任何參數,使用預設行為(Debug 模式自動啟用)
flutter run --flavor development
這種設計的優勢:
/// 建立自定義 Trace
Future<Trace?> startTrace(String name) async {
try {
final trace = _performance.newTrace(name);
await trace.start();
developer.log(
'⏱️ Performance: Started trace "$name"',
name: 'PerformanceService',
);
return trace;
} on Exception catch (e) {
developer.log(
'❌ Performance error starting trace "$name": $e',
name: 'PerformanceService',
);
return null;
}
}
/// 停止 Trace 並記錄結果
Future<void> stopTrace(
Trace? trace, {
Map<String, int>? metrics,
Map<String, String>? attributes,
bool? success,
}) async {
if (trace == null) return;
try {
// 設置自定義指標
if (metrics != null) {
for (final entry in metrics.entries) {
trace.setMetric(entry.key, entry.value);
}
}
// 設置自定義屬性
if (attributes != null) {
for (final entry in attributes.entries) {
trace.putAttribute(entry.key, entry.value);
}
}
// 設置成功/失敗屬性
if (success != null) {
trace.putAttribute('success', success.toString());
}
await trace.stop();
developer.log(
'✅ Performance: Stopped trace successfully',
name: 'PerformanceService',
);
} on Exception catch (e) {
developer.log(
'❌ Performance error stopping trace: $e',
name: 'PerformanceService',
);
}
}
/// 測量函數執行時間的便利方法
Future<T> measure<T>(
String name,
Future<T> Function() operation, {
Map<String, String>? attributes,
}) async {
final trace = await startTrace(name);
try {
final result = await operation();
await stopTrace(trace, attributes: attributes, success: true);
return result;
} on Exception catch (e) {
await stopTrace(
trace,
attributes: {
...?attributes,
'error': e.toString().substring(0, 100),
},
success: false,
);
rethrow;
}
}
/// 建立 HTTP 請求監控
Future<HttpMetric?> newHttpMetric(
String url,
HttpMethod httpMethod,
) async {
try {
final metric = _performance.newHttpMetric(url, httpMethod);
developer.log(
'🌐 Performance: Created HTTP metric for $url',
name: 'PerformanceService',
);
return metric;
} on Exception catch (e) {
developer.log(
'❌ Performance error creating HTTP metric: $e',
name: 'PerformanceService',
);
return null;
}
}
/// 停止 HTTP 請求監控
Future<void> stopHttpMetric(
HttpMetric? metric, {
int? statusCode,
int? requestPayloadSize,
int? responsePayloadSize,
String? responseContentType,
}) async {
if (metric == null) return;
try {
if (statusCode != null) metric.httpResponseCode = statusCode;
if (requestPayloadSize != null) {
metric.requestPayloadSize = requestPayloadSize;
}
if (responsePayloadSize != null) {
metric.responsePayloadSize = responsePayloadSize;
}
if (responseContentType != null) {
metric.responseContentType = responseContentType;
}
await metric.stop();
developer.log(
'✅ Performance: Stopped HTTP metric (status: $statusCode)',
name: 'PerformanceService',
);
} on Exception catch (e) {
developer.log(
'❌ Performance error stopping HTTP metric: $e',
name: 'PerformanceService',
);
}
}
/// 監控活動載入效能
Future<T> measureActivityLoad<T>(
String activityType,
Future<T> Function() operation,
) async {
return measure(
'load_$activityType',
operation,
attributes: {'activity_type': activityType},
);
}
/// 監控資料庫操作效能
Future<T> measureDatabaseOperation<T>(
String operation,
Future<T> Function() action, {
String? collection,
}) async {
return measure(
'db_$operation',
action,
attributes: {
'operation': operation,
if (collection != null) 'collection': collection,
},
);
}
/// 監控使用者操作效能
Future<T> measureUserAction<T>(
String action,
Future<T> Function() operation, {
Map<String, String>? metadata,
}) async {
return measure(
'user_$action',
operation,
attributes: {
'action': action,
...?metadata,
},
);
}
在 Crew Up! 專案中,我們將 Firebase 初始化邏輯封裝在 firebase_initializer.dart
中,遵循關注點分離和單一職責原則。
// lib/app/core/services/firebase_initializer.dart
/// 確保 Firebase 在每個進程中只初始化一次
/// 支援 Hot Restart 和多入口點
Future<void> ensureFirebaseInitialized() async {
// 如果已經有預設 app,直接返回
try {
Firebase.app();
return;
} on FirebaseException catch (e) {
if (e.code != 'no-app') {
rethrow;
}
}
// 僅在真正沒有 app 時才初始化
try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 初始化 Firebase Crashlytics
await CrashlyticsService.initialize();
// 初始化 Firebase Performance Monitoring
await PerformanceService.initialize();
} on FirebaseException catch (e) {
if (e.code == 'duplicate-app') {
// 其他 isolate/入口點已經初始化,安全忽略
return;
}
rethrow;
}
}
// lib/main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 使用專用的 FirebaseInitializer 處理初始化
await ensureFirebaseInitialized();
runApp(
const ProviderScope(
child: CrewUpApp(),
),
);
}
🎯 架構設計優勢
關注點分離
firebase_initializer.dart
專門負責 Firebase 服務初始化main.dart
保持簡潔,只處理應用程式入口邏輯可維護性提升
ensureFirebaseInitialized()
中添加一行安全性保障
在 Crew Up! 專案中,我們已經有 AuthPerformanceMonitor
用於認證流程的效能監控。現在我們整合 Firebase Performance Monitoring:
// lib/features/auth/data/services/auth_performance_monitor.dart
/// 認證效能指標(整合 Firebase Performance)
class AuthPerformanceMetrics {
final String operation;
final DateTime startTime;
DateTime? endTime;
bool success;
String? errorMessage;
final Map<String, dynamic> metadata;
final Trace? firebaseTrace; // 整合 Firebase Performance Trace
// ... 其他程式碼
}
/// 認證效能監控服務
class AuthPerformanceMonitor {
final PerformanceService? _performanceService;
AuthPerformanceMonitor({PerformanceService? performanceService})
: _performanceService = performanceService;
/// 開始監控操作(同時啟動 Firebase Performance Trace)
Future<AuthPerformanceMetrics> startOperation(
String operation, {
Map<String, dynamic> metadata = const {},
}) async {
// 啟動 Firebase Performance Trace
final firebaseTrace =
await _performanceService?.startTrace('auth_$operation');
final metric = AuthPerformanceMetrics(
operation: operation,
startTime: DateTime.now(),
success: false,
metadata: metadata,
firebaseTrace: firebaseTrace, // 保存 Trace 實例
);
return metric;
}
/// 完成操作監控(同時停止 Firebase Performance Trace)
Future<void> completeOperation(
AuthPerformanceMetrics metric, {
bool success = true,
String? errorMessage,
}) async {
metric.endTime = DateTime.now();
metric.success = success;
metric.errorMessage = errorMessage;
// 停止 Firebase Performance Trace
await _performanceService?.stopTrace(
metric.firebaseTrace,
metrics: {'duration_ms': metric.duration.inMilliseconds},
attributes: {
'operation': metric.operation,
if (errorMessage != null) 'error': errorMessage.substring(0, 100),
},
success: success,
);
// ... 其他日誌記錄
}
}
🎯 整合設計理念
為什麼保留本地 AuthPerformanceMonitor
?
或許有人會問,既然有了 Firebase Performance 這麼強大的遠端監控,為什麼不直接取代本地的 AuthPerformanceMonitor?這其實是一個關於監控粒度和開發效率的權衡。
AuthRetryService
中調整重試策略時。auth_retry_service_test.dart
)時,我們只需 Mock AuthPerformanceMonitor
,而無需處理與 Firebase 相關的複雜性。因此,我們採取了「漸進式整合」的策略,讓兩套系統各司其職,在開發效率與遠端監控能力之間取得平衡。
雙重監控的價值
AuthPerformanceMetrics
):詳細的本地日誌,開發時除錯用漸進式整合
PerformanceService
在實際專案中,我們可以在 CreateActivityNotifier
的 createActivity()
方法中整合效能監控:
// lib/features/activity/presentation/providers/create_activity_notifier.dart
class CreateActivityNotifier extends StateNotifier<CreateActivityState> {
final PerformanceService _performanceService;
/// 創建活動(整合 Performance Monitoring)
Future<bool> createActivity() async {
// 開始效能追蹤
final trace = await _performanceService.startTrace('create_crew_activity_flow');
try {
developer.log('🚀 開始創建活動', name: 'CreateActivityNotifier');
state = state.copyWith(isLoading: true, errorMessage: null);
// 1. 構建 Activity 實體(驗證資料)
final startBuildTime = DateTime.now();
final activity = await _buildActivityFromState();
final buildDuration = DateTime.now().difference(startBuildTime).inMilliseconds;
// 2. 上傳自定義圖片(如果有)
int uploadDuration = 0;
if (state.customImagePath != null) {
final startUploadTime = DateTime.now();
await _uploadCustomImage(activity);
uploadDuration = DateTime.now().difference(startUploadTime).inMilliseconds;
}
// 3. 使用 Use Case 創建活動
final useCase = ref.read(createActivityUseCaseProvider);
final result = await useCase.execute(activity);
if (result.isSuccess) {
// 成功 - 記錄詳細指標
await _performanceService.stopTrace(
trace,
metrics: {
'build_time_ms': buildDuration,
'upload_time_ms': uploadDuration,
},
attributes: {
'category': state.selectedCategory.name,
'has_custom_image': (state.customImagePath != null).toString(),
'duration_days': state.durationDays.toString(),
},
success: true,
);
developer.log('✅ 活動創建成功: ${activity.id}', name: 'CreateActivityNotifier');
state = state.copyWith(isLoading: false, createdActivityId: activity.id);
return true;
} else {
// 失敗 - 記錄錯誤
await _performanceService.stopTrace(
trace,
attributes: {
'error': result.error.message.substring(0, 100),
'error_type': 'business_logic',
},
success: false,
);
state = state.copyWith(
isLoading: false,
errorMessage: result.error.message,
);
return false;
}
} on Exception catch (e) {
// 異常 - 記錄錯誤
await _performanceService.stopTrace(
trace,
attributes: {
'error': e.toString().substring(0, 100),
'error_type': 'exception',
},
success: false,
);
developer.log('❌ 創建活動時發生錯誤: $e', name: 'CreateActivityNotifier');
state = state.copyWith(
isLoading: false,
errorMessage: e.toString(),
);
return false;
}
}
}
🎯 範例亮點
這個範例展示了真實專案中的實踐方式:
這樣一來,在 Firebase Console 中您就能看到:
在實際使用 PerformanceService
後,我們加入了以下改善:
1. 自定義異常處理
/// Performance 相關異常
class PerformanceException implements Exception {
final String message;
final String? code;
final dynamic originalError;
const PerformanceException(
this.message, {
this.code,
this.originalError,
});
}
2. 配置管理
/// Performance 配置
class PerformanceConfig {
static const int maxTraceNameLength = 100;
static const int maxAttributeNameLength = 40;
static const int maxAttributeValueLength = 100;
static const int maxAttributeCount = 5;
static const int traceTimeoutMs = 300000; // 5 分鐘
final bool enableValidation;
final bool enableAutoCleanup;
final int autoCleanupIntervalMs;
const PerformanceConfig({
this.enableValidation = true,
this.enableAutoCleanup = true,
this.autoCleanupIntervalMs = 60000,
});
}
3. 活動 Trace 追蹤
防止重複啟動同名 Trace,並自動清理過期的 Trace:
// 檢查是否已經有同名的活動 Trace
if (_activeTraces.containsKey(name)) {
developer.log(
'⚠️ Trace "$name" is already active, skipping duplicate start',
name: 'PerformanceService',
);
return _activeTraces[name]?.trace;
}
4. 自動清理機制
超過 5 分鐘的 Trace 會被自動停止並清理:
bool get isExpired {
final now = DateTime.now();
final duration = now.difference(startTime).inMilliseconds;
return duration > PerformanceConfig.traceTimeoutMs;
}
5. 驗證與限制檢查
在 startTrace
和 stopTrace
時自動驗證:
// 驗證 Trace 名稱格式
final validPattern = RegExp(r'^[a-z0-9_]+$');
if (!validPattern.hasMatch(name)) {
throw PerformanceException(
'Trace name must contain only lowercase letters, numbers, and underscores',
code: 'INVALID_TRACE_NAME_FORMAT',
);
}
// 驗證屬性數量
if (attributes.length > PerformanceConfig.maxAttributeCount) {
throw PerformanceException(
'Attributes count exceeds maximum of ${PerformanceConfig.maxAttributeCount}',
code: 'TOO_MANY_ATTRIBUTES',
);
}
6. 效能狀態追蹤
// 獲取當前活動的 Trace 數量
int get activeTracesCount => _activeTraces.length;
// 獲取所有活動 Trace 的名稱
List<String> get activeTraceNames => _activeTraces.keys.toList();
// 手動觸發清理過期 Trace
void cleanupNow() {
_cleanupExpiredTraces();
}
這些改善讓 PerformanceService
更加健壯、易於除錯,並能自動避免常見的使用錯誤。
Trace 限制:
效能目標建議:
✅ 應該監控的場景
❌ 不需要監控的場景
今天我們為 Crew Up! 建立了完整的 Performance Monitoring 系統:
✅ 已完成的功能
從實際開發中,我們學到:
明天(Day 21),我們將開始探索 AI 整合,學習如何將 Google Cloud Vertex AI 整合到 Crew Up! 中,為使用者提供智能推薦和內容生成功能。
期待與您在 Day 21 相見!