带有Flutter Android应用程序CI/CD

这篇文章发布于一年多前,信息可能已过时。
CI/CD pipeline是软件开发必不可少的部分。本文介绍了如何Flutter CI/CD pipeline以构建Android应用程序。我们将基于GitHub Actions构建管道,而不使用fastlane 。
1. 在本地构建
GitHub创建管道之前,让我们在本地构建一个应用程序。
您可以通过官方网站-构建并发布Android应用准备构建环境。
我们将在这里解释一些内容。
1-1. key.jks和key.properties
构建Android应用需要密钥进行签名。 key.jks包含密钥,而key.properties包含密码。
key.jks可以通过以下命令生成。
视窗
1keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias keyMac / Linux
1keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key使用这些命令时,需要key password和store password 。
将这些密码记录在key.properties文件中。
1storePassword=<your password>2keyPassword=<your password>3keyAlias=key4storeFile=C:/Users/USER_NAME/key.jks // depends on your environment然后,修改Android gradle文件以使用这些文件对您的应用进行签名。
1-2. app / bundle.gradle
修复bundle.gradle文件。请注意,项目中有两个bundle.gradle文件。
app目录下的文件。
1def keystoreProperties = new Properties()2def keystorePropertiesFile = rootProject.file('key.properties')3if (keystorePropertiesFile.exists()) {4 keystoreProperties.load(new FileInputStream(keystorePropertiesFile))5}6 7android {8 signingConfigs {9 release {10 keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : "key"11 keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : "$System.env.KEY_PASSWORD"12 storeFile file("../key.jks")13 storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : "$System.env.STORE_PASSWORD"14 }15 }16 17 buildTypes {18 release {19 signingConfig signingConfigs.release20 }21 }22}第一块使应用程序能够读取key.properties文件。
rootProject.file('key.properties')表示在项目根目录下key.properties
如果要更改文件名或目录,则可以修改此表达式。
接下来,我们可以在android块下看到两个块。
signingConfigs从字面上描述了签名的配置。 您可以在此处key.properties
例如, keystoreProperties['keyPassword']提供key.properties文件中的keyPassword值。
$System.env.KEY_PASSWORD意味着您可以从系统环境中获取值。 file("../key.jks")意味着您可以直接从文件中加载密钥数据。
您可以选择任何喜欢的表情。
接下来,让我们构建应用程序。
1flutter build apk --release您可以通过此命令apk
该命令可能会警告您使用app bundle或拆分apk以避免胖apk ,但请立即忽略它们。稍后再解释。
2.在GitHub
GitHub注册必要的秘密。我们至少需要以下秘密。请参阅加密的机密以了解如何在GitHub注册机密。
- 关键密码
- 储存密码
- 键
key.jks类的二进制文件时,您需要base64编码来生成秘密字符串。在管道中使用它时,请不要忘记对其进行解码。
您还可以使用其他密钥管理服务,例如Azure KeyVault和GCP SecretManager 。
无论如何,您必须将key.jks和其他任何秘密与源代码存储库分开,并确保它们的安全。
3.添加工作流程
使用yaml文件添加工作流程。
创建yaml文件,如.github/workflows/xxx.yaml 。
1name: CICD2 3on:4 pull_request:5 branches: [master]6 workflow_dispatch:7 8jobs:9 dev:10 runs-on: ubuntu-latest11 12 steps:13 # checkout source code14 - uses: actions/checkout@v215 # setup java16 - name: set up JDK 1.817 uses: actions/setup-java@v118 with:19 java-version: 1.820 # setup flutter21 - name: Setup flutter22 uses: subosito/flutter-action@v123 with:24 flutter-version: "2.0.1"25 # pub get26 - run: flutter pub get27 # analyze28 - run: flutter analyze29 # test30 - run: flutter test31 # build32 - name: build dev33 env:34 KEY_JKS: ${{ secrets.KEY_JKS }}35 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}36 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}37 GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}38 run: echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks && flutter build apk --release我将跳过有关Java设置, Flutter设置, pub get , analyze和test 。
构建任务由一些与&&连接的命令组成。让我们一一看。
1echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks此命令意味着根据机密信息key.jks正如我所解释的, key.jks被注册为base64编码的字符串,因此在这里我们需要对其进行解码并重新生成key.jks文件。通过生成文件,当我们将存储文件配置为file("../key.jks") ,应用程序可以从文件中加载密钥信息。
在构建任务中,我没有生成key.properties文件。这是因为我们将keyPassword和storePassword配置为$System.env.KEY_PASSWORD 。
我们将环境设置如下,因此该应用可以从系统环境中加载密码。
如果要从key.properties读取密码,则可以在构建任务中在此处bundle.gradle并生成key.properties
1- name: build dev2 env:3 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}4 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}接下来,让我们检查构建命令。
1flutter build apk --release这与本地版本完全相同。因此,您将看到警告,告诉您应使用app bundle或拆分apk以避免胖apk 。
根据官方网站构建要发布的应用程序,建议使用app bundle因此,在发布应用程序时,最好构建app bundle ,但是在设备上测试应用程序或使用Firebase App Distribute时,则需要apk 。
因此,我建议根据目的选择构建应用程序的方式。例如,作为日常CI构建,您可以生成apk ,该APK可让您轻松地在设备上进行测试;作为发行版本,您可以生成app bundle并将该应用注册到Google Play Store 。
以下示例提供了两种类型的构建。
App Distribution注册一个应用
1name: CICD2 3on:4 pull_request:5 branches: [master]6 workflow_dispatch:7 8jobs:9 dev:10 runs-on: ubuntu-latest11 12 steps:13 # checkout source code14 - uses: actions/checkout@v215 # setup java16 - name: set up JDK 1.817 uses: actions/setup-java@v118 with:19 java-version: 1.820 # setup flutter21 - name: Setup flutter22 uses: subosito/flutter-action@v123 with:24 flutter-version: "2.0.1"25 # pub get26 - run: flutter pub get27 # analyze28 - run: flutter analyze29 # test30 - run: flutter test31 # build32 - name: build33 env:34 KEY_JKS: ${{ secrets.KEY_JKS }}35 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}36 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}37 GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}38 run: echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks && echo $GOOGLE_SERVICES > android/app/google-services.json && flutter build apk --release39 # app distribution40 - name: Firebase App Distribution41 uses: wzieba/Firebase-Distribution-Github-Action@v1.2.242 with:43 appId: ${{secrets.FIREBASE_APP_ID}}44 token: ${{secrets.FIREBASE_APP_TOKEN}}45 file: build/app/outputs/apk/release/app-release.apk46 groups: TesterGoogle Play Store注册应用
1name: Release2 3on:4 workflow_dispatch:5 6jobs:7 dev:8 runs-on: ubuntu-latest9 10 steps:11 # checkout source code12 - uses: actions/checkout@v213 # setup java14 - name: set up JDK 1.815 uses: actions/setup-java@v116 with:17 java-version: 1.818 # setup flutter19 - name: Setup flutter20 uses: subosito/flutter-action@v121 with:22 flutter-version: "2.0.1"23 # pub get24 - run: flutter pub get25 # analyze26 - run: flutter analyze27 # test28 - run: flutter test29 # build30 - name: build31 env:32 KEY_JKS: ${{ secrets.KEY_JKS }}33 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}34 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}35 GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}36 run: echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks && flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release37 # google play38 - name: Create service account json39 env:40 SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON}}41 run: echo $SERVICE_ACCOUNT_JSON > android/app/service-account.json42 - uses: r0adkll/upload-google-play@v143 with:44 serviceAccountJson: android/app/service-account.json45 packageName: your app id46 releaseFiles: build/app/outputs/bundle/release/app-release.aab47 track: alpha48 whatsNewDirectory: release-notes49 mappingFile: build/app/outputs/mapping/release/mapping.txt4.其他选择
4-1. Firebase google-services.json
1echo $GOOGLE_SERVICES > android/app/google-services.json为了连接Firebase ,您需要google-services.json 。
您可以通过以上命令生成json。
有关更多详细信息,请参见FlutterFire-android安装。
4-2. App Distribution
App Distribution Firebase提供的功能之一。
您可以通过此功能将应用交付给测试人员。如果要使用此功能,只需将以下任务添加到管道中。
1- name: Firebase App Distribution2 uses: wzieba/Firebase-Distribution-Github-Action@v1.2.23 with:4 appId: your app id5 token: ${{secrets.FIREBASE_APP_TOKEN}}6 file: build/app/outputs/apk/release/app-release.apk7 groups: Tester有关更多信息,请参见Firebase Distribution Github Action。
请注意, App Distribution仅允许使用apk文件。
4-3. Product Flavor
当您在Flutter product flavor时,请指定--flavor <flavor name>在构建命令中。
参见build variants了解product flavor
1flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release --flavor jp使用flavor ,您可以使用相同的代码生成不同的应用程序ID。
1android {2 3 defaultConfig {4 applicationId "xxx.yyy.zzz"5 ...6 }7 8 flavorDimensions "targetArea"9 productFlavors {10 jp {11 applicationIdSuffix ".jp"12 dimension "targetArea"13 }14 eu {15 applicationIdSuffix ".eu"16 dimension "targetArea"17 }18 }使用flavor ,应用程序ID将是applicationId和applicationIdSuffix的组合。
例如,如果指定--flavor jp ,则应用程序ID为xxx.yyy.zzz.jp
您还可以使用flavor修改清单文件。
假设您将使用Google AdMob ,那么您需要将applicationId添加到清单文件中。
您可以通过设置清单文件来做到这一点,如下所示。
1android/app/src2 |- main/AndroidManifest.xml3 |- jp/AndroidManifest.xml4 |- eu/AndroidManifest.xml将基本清单文件放在android/app/src/main ,然后将每个flavor清单文件放在android/app/src/<flavor name> 。
然后,已构建的应用程序将同时包含主要清单和风味清单。
1<manifest xmlns:android="http://schemas.android.com/apk/res/android"2 package="xxx.yyy.zzz">3 <uses-permission android:name="android.permission.INTERNET" />4 <application>5 <meta-data6 android:name="com.google.android.gms.ads.APPLICATION_ID"7 android:value="your admob app id"/>8 </application>9</manifest>4-4. --dart-define
此选项使您可以将环境变量传递给应用程序。
1flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release --flavor jp --dart-define=REGION=asia-northeast1例如,此命令生成名为REGION环境,该环境描述了后端服务器的区域。
该应用可以通过以下代码读取环境。
1String region = String.fromEnvironment('REGION'); // get 'asia-northeast1'4-5. 将应用程序注册到Google Play Store
当您将应用程序注册到Google Play Store ,请使用此任务。
1- name: Create service account json2 env:3 SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON}}4 run: echo $SERVICE_ACCOUNT_JSON > android/app/service-account.json5- uses: r0adkll/upload-google-play@v16 with:7 serviceAccountJson: android/app/service-account.json8 packageName: your app id9 releaseFiles: build/app/outputs/bundle/release/app-release.aab10 track: alpha11 whatsNewDirectory: release-notes12 mappingFile: build/app/outputs/mapping/release/mapping.txt第一个任务生成service-account.json ,这是从CI服务 器Play Store
然后在第二个任务中指定此文件。
有关更多信息,请参见上传google play。




