返回博客
DEVOPS2025年9月25日安安科技团队阅读时间 14 分钟

FLUTTER CI/CD
流水线搭建

为 Flutter 应用搭建 CI/CD 流水线的完整指南。学习 GitHub Actions、自动化测试、代码质量检查,以及应用商店部署。

Flutter CI/CD 流水线搭建
// 简报

现代应用开发离不开自动化测试与部署流水线。手动测试和部署费时、容易出错,而且随着团队扩大难以为继。

本指南将演示如何使用 GitHub Actions 为 Flutter 应用搭建稳健的 CI/CD 流水线。你将学会自动化测试、代码质量检查、构建,以及部署到 Google Play Store 和 Apple App Store。

GITHUB ACTIONS 基础

GitHub Actions 为公开仓库提供免费 CI/CD,私有仓库也有充裕的免费额度,并能与你的 Flutter 开发流程无缝集成。

.github/workflows/flutter_ci.yml
# .github/workflows/flutter_ci.yml
name: Flutter CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Verify formatting
      run: dart format --output=none --set-exit-if-changed .
      
    - name: Analyze project source
      run: flutter analyze --fatal-infos
      
    - name: Run tests
      run: flutter test --coverage
      
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage/lcov.info
        fail_ci_if_error: true

  build_android:
    needs: test
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build Android APK
      run: flutter build apk --release
      
    - name: Build Android App Bundle
      run: flutter build appbundle --release
      
    - name: Upload APK artifact
      uses: actions/upload-artifact@v3
      with:
        name: android-apk
        path: build/app/outputs/flutter-apk/app-release.apk
        
    - name: Upload AAB artifact
      uses: actions/upload-artifact@v3
      with:
        name: android-aab
        path: build/app/outputs/bundle/release/app-release.aab

  build_ios:
    needs: test
    runs-on: macos-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Build iOS (no signing)
      run: flutter build ios --release --no-codesign
      
    - name: Upload iOS artifact
      uses: actions/upload-artifact@v3
      with:
        name: ios-build
        path: build/ios/iphoneos/Runner.app

工作流优势

  • 每次 push 和 PR 均会运行
  • 并行构建 Android 和 iOS
  • 自动化代码质量检查
  • 覆盖率报告

主要特性

  • Flutter 版本缓存
  • 依赖缓存
  • 构建产物上传
  • 多平台支持

全面的测试流水线

稳健的测试流水线涵盖单元测试、Widget 测试、集成测试及代码质量检查,确保应用在部署前稳定可靠。

.github/workflows/comprehensive_testing.yml
# .github/workflows/comprehensive_testing.yml
name: Comprehensive Testing

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  quality_checks:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    # Code formatting check
    - name: Check code formatting
      run: dart format --output=none --set-exit-if-changed .
      
    # Static analysis
    - name: Run static analysis
      run: flutter analyze --fatal-infos --fatal-warnings
      
    # Import sorting check
    - name: Check import sorting
      run: |
        flutter pub global activate import_sorter
        import_sorter --check-only
        
    # Unused dependencies check
    - name: Check for unused dependencies
      run: |
        flutter pub global activate dependency_validator
        dependency_validator
        
    # Security audit
    - name: Security audit
      run: |
        flutter pub deps --json > deps.json
        # Add security scanning tool here
        
  unit_tests:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run unit tests
      run: |
        flutter test \
          --coverage \
          --test-randomize-ordering-seed random \
          --reporter expanded
          
    - name: Generate coverage report
      run: |
        sudo apt-get update
        sudo apt-get install lcov
        genhtml coverage/lcov.info -o coverage/html
        
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage/lcov.info
        fail_ci_if_error: true
        verbose: true

  widget_tests:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run widget tests
      run: flutter test test/widget_test/
      
  integration_tests:
    runs-on: macos-latest
    strategy:
      matrix:
        device:
          - "iPhone 14 Pro Simulator (16.0)"
          - "iPad Pro (11-inch) (4th generation) Simulator (16.0)"
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Start iOS Simulator
      run: |
        xcrun simctl list devices
        xcrun simctl boot "${{ matrix.device }}" || true
        
    - name: Run integration tests
      run: |
        flutter test integration_test/ \
          --device-id="${{ matrix.device }}"
          
    - name: Upload test results
      uses: actions/upload-artifact@v3
      if: failure()
      with:
        name: integration-test-results
        path: test_results/

测试策略:

  • 单元测试覆盖业务逻辑(占测试 70%)
  • Widget 测试覆盖 UI 组件(占测试 20%)
  • 集成测试覆盖用户流程(占测试 10%)
  • 强制执行代码覆盖率阈值

自动化部署流水线

测试通过后,自动将 Flutter 应用部署到 Google Play Store 和 Apple App Store,涵盖应用签名、版本管理及发布说明。

.github/workflows/deploy.yml
# .github/workflows/deploy.yml
name: Deploy to App Stores

on:
  push:
    tags:
      - 'v*'  # Deploy when version tags are pushed

jobs:
  deploy_android:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    # Setup Android signing
    - name: Setup Android signing
      run: |
        echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks
        echo "storeFile=keystore.jks" >> android/key.properties
        echo "keyAlias=$ANDROID_KEY_ALIAS" >> android/key.properties
        echo "storePassword=$ANDROID_STORE_PASSWORD" >> android/key.properties
        echo "keyPassword=$ANDROID_KEY_PASSWORD" >> android/key.properties
      env:
        ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
        ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
        ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
        ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
        
    - name: Build signed App Bundle
      run: flutter build appbundle --release
      
    - name: Deploy to Google Play Console
      uses: r0adkll/upload-google-play@v1
      with:
        serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
        packageName: com.onon.yourapp
        releaseFiles: build/app/outputs/bundle/release/app-release.aab
        track: production
        status: completed
        inAppUpdatePriority: 2
        userFraction: 0.33
        whatsNewDirectory: distribution/whatsnew
        mappingFile: build/app/outputs/mapping/release/mapping.txt

  deploy_ios:
    runs-on: macos-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Install dependencies
      run: flutter pub get
      
    # Setup iOS certificates and provisioning profiles
    - name: Install Apple Certificate
      uses: apple-actions/import-codesign-certs@v1
      with:
        p12-file-base64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
        p12-password: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
        
    - name: Install Provisioning Profile
      uses: apple-actions/download-provisioning-profiles@v1
      with:
        bundle-id: com.onon.yourapp
        issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
        api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
        api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
        
    - name: Build iOS app
      run: |
        flutter build ios --release --no-codesign
        cd ios
        xcodebuild -workspace Runner.xcworkspace \
          -scheme Runner \
          -configuration Release \
          -destination generic/platform=iOS \
          -archivePath $PWD/build/Runner.xcarchive \
          archive
        xcodebuild -exportArchive \
          -archivePath $PWD/build/Runner.xcarchive \
          -exportOptionsPlist ExportOptions.plist \
          -exportPath $PWD/build
          
    - name: Deploy to TestFlight
      uses: apple-actions/upload-testflight-build@v1
      with:
        app-path: ios/build/Runner.ipa
        issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
        api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
        api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

  create_release:
    needs: [deploy_android, deploy_ios]
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Create GitHub Release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: Release ${{ github.ref }}
        body: |
          🚀 New release deployed to app stores!
          
          ## What's New
          - Check CHANGELOG.md for detailed changes
          
          ## Deployment Status
          - ✅ Android: Google Play Store
          - ✅ iOS: TestFlight & App Store
        draft: false
        prerelease: false

部署功能:

  • 两个平台的自动化应用签名
  • 分阶段发布以降低风险
  • 从更新日志自动生成发布说明
  • 版本管理与标签

环境与密钥管理

妥善的密钥管理和环境配置能确保部署安全,同时支持开发、预发布和生产等不同环境。

.github/workflows/multi_environment.yml
# .github/workflows/multi_environment.yml
name: Multi-Environment Deployment

on:
  push:
    branches:
      - develop      # Deploy to staging
      - main         # Deploy to production
  pull_request:
    branches:
      - main         # Test on PR

jobs:
  determine_environment:
    runs-on: ubuntu-latest
    outputs:
      environment: ${{ steps.env.outputs.environment }}
      api_url: ${{ steps.env.outputs.api_url }}
    steps:
    - name: Determine environment
      id: env
      run: |
        if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
          echo "environment=production" >> $GITHUB_OUTPUT
          echo "api_url=https://api.yourapp.com" >> $GITHUB_OUTPUT
        elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then
          echo "environment=staging" >> $GITHUB_OUTPUT
          echo "api_url=https://staging-api.yourapp.com" >> $GITHUB_OUTPUT
        else
          echo "environment=test" >> $GITHUB_OUTPUT
          echo "api_url=https://test-api.yourapp.com" >> $GITHUB_OUTPUT
        fi

  build_and_test:
    needs: determine_environment
    runs-on: ubuntu-latest
    environment: ${{ needs.determine_environment.outputs.environment }}
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Set up Flutter
      uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        channel: 'stable'
        cache: true
        
    - name: Create environment config
      run: |
        cat > lib/config/environment.dart << EOF
        class Environment {
          static const String apiUrl = '${{ needs.determine_environment.outputs.api_url }}';
          static const String environment = '${{ needs.determine_environment.outputs.environment }}';
          static const String apiKey = '${{ secrets.API_KEY }}';
          static const bool isProduction = ${{ needs.determine_environment.outputs.environment == 'production' }};
        }
        EOF
        
    - name: Install dependencies
      run: flutter pub get
      
    - name: Run tests
      run: flutter test
      
    - name: Build for environment
      run: |
        if [[ "${{ needs.determine_environment.outputs.environment }}" == "production" ]]; then
          flutter build apk --release --flavor production -t lib/main_production.dart
        elif [[ "${{ needs.determine_environment.outputs.environment }}" == "staging" ]]; then
          flutter build apk --release --flavor staging -t lib/main_staging.dart
        else
          flutter build apk --debug --flavor development -t lib/main_development.dart
        fi

# GitHub Repository Secrets Setup:
# ANDROID_KEYSTORE_BASE64          - Base64 encoded Android keystore
# ANDROID_KEY_ALIAS                - Android key alias
# ANDROID_STORE_PASSWORD           - Android keystore password
# ANDROID_KEY_PASSWORD             - Android key password
# IOS_CERTIFICATE_BASE64           - Base64 encoded iOS certificate
# IOS_CERTIFICATE_PASSWORD         - iOS certificate password
# APPSTORE_ISSUER_ID               - App Store Connect issuer ID
# APPSTORE_KEY_ID                  - App Store Connect key ID
# APPSTORE_PRIVATE_KEY             - App Store Connect private key
# GOOGLE_PLAY_SERVICE_ACCOUNT_JSON - Google Play Console service account
# API_KEY                          - Your app's API key
# FIREBASE_CONFIG_ANDROID          - Firebase config for Android
# FIREBASE_CONFIG_IOS              - Firebase config for iOS

# Environment-specific secrets:
# For each environment (production, staging, test), create:
# - API_URL
# - DATABASE_URL
# - ANALYTICS_KEY
# - Feature flags configuration

安全最佳实践:

  • 所有敏感数据均存放在 GitHub Secrets
  • 为每个环境使用独立配置
  • 定期轮换密钥
  • 仅允许必要的工作流访问密钥

CI/CD 最佳实践

1

快速反馈循环: 将构建时间控制在 10 分钟以内。利用缓存、并行任务和增量构建加快流水线速度。

2

快速失败: 先运行静态分析和单元测试。若基本质量检查不通过,就不要在昂贵的集成测试上浪费时间。

3

分阶段部署: 先部署到预发布环境,再部署到生产环境。使用功能开关(feature flags)和灰度发布,将风险降到最低。

4

全面监控: 跟踪构建成功率、测试覆盖率、部署频率及变更前置时间。

5

回滚策略: 时刻准备好快速回滚部署的方案。保留旧版本随时可用,并定期测试回滚流程。

// 自动化

准备好自动化你的 FLUTTER 部署了吗?

我们的团队可以帮助你搭建稳健的 CI/CD 流水线,节省时间、减少错误,并提升应用质量。一起把你的开发流程自动化!

联系我们
// 扩散信号

分享本指南