大家好,歡迎來到第二十天!在 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 相見!