Aplicación de Android CI/CD con Flutter

⏱️5 min
Compartir:

CI/CD pipeline es una parte esencial del desarrollo de software. Este artículo explica cómo construir una CI/CD pipeline para crear una aplicación de Android Flutter . Construiremos una canalización basada en GitHub Actions y no usaremos fastlane .

1. Construye localmente

Antes de crear una canalización en GitHub , creemos una aplicación localmente. Puede preparar el entorno de compilación con el sitio web oficial: Cree y publique una aplicación de Android. Explicaremos algunos de los contenidos aquí.

1-1. key.jks y key.properties

La creación de una Android requiere la clave para firmar. key.jks contiene la clave y key.properties contiene la contraseña. key.jks puede ser generado por los siguientes comandos.

Ventanas

bash
1keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key

Mac / Linux

bash
1keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

Cuando usa estos comandos, necesita key password y una store password . Registre estas contraseñas en el archivo key.properties

properties
1storePassword=<your password>
2keyPassword=<your password>
3keyAlias=key
4storeFile=C:/Users/USER_NAME/key.jks // depends on your environment

Luego, modifique los Android para usar estos archivos para firmar su aplicación.

1-2. app / bundle.gradle

bundle.gradle archivo bundle.gradle. Tenga en cuenta que hay dos bundle.gradle en el proyecto. Usamos el archivo en el directorio de la app

gradle
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.release
20 }
21 }
22}

El primer bloque permite que la aplicación lea el archivo key.properties rootProject.file('key.properties') significa leer key.properties en el directorio raíz del proyecto. Si desea cambiar el nombre del archivo o el directorio, puede modificar esta expresión.

A continuación, podemos ver dos bloques debajo del bloque de android

signingConfigs describe literalmente la configuración para firmar. Puede recuperar el contenido de key.properties aquí. Por ejemplo, keystoreProperties['keyPassword'] proporciona keyPassword valor de key.properties archivo. $System.env.KEY_PASSWORD significa que puede obtener el valor del entorno del sistema. file("../key.jks") significa que puede cargar datos clave directamente desde el archivo. Puede elegir la expresión que desee.

A continuación, creemos la aplicación.

bash
1flutter build apk --release

Puede generar un apk con este comando. El comando puede advertirle que use el app bundle o la apk dividida para evitar la apk grasa, pero ignórelos ahora mismo. Te lo explicaré más tarde.

2. Registrar secretos en GitHub

Registre los secretos necesarios en GitHub . Necesitamos al menos los secretos a continuación. Consulte Secretos cifrados para aprender cómo registrar secretos en GitHub .

  • contraseña clave
  • almacenar contraseña
  • key.jks

Cuando registra archivos binarios como key.jks , necesita codificación base64 para generar una cadena secreta. No olvide decodificarlo cuando lo use en la tubería.

También puede usar otros servicios de administración de secretos como Azure KeyVault y GCP SecretManager . De todos modos, debe separar key.jks y cualquier otro secreto del repositorio de código fuente y mantenerlos seguros.

3. Agregar flujo de trabajo

Agregue flujo de trabajo usando el archivo yaml. Cree un archivo yaml como .github/workflows/xxx.yaml .

yaml
1name: CICD
2
3on:
4 pull_request:
5 branches: [master]
6 workflow_dispatch:
7
8jobs:
9 dev:
10 runs-on: ubuntu-latest
11
12 steps:
13 # checkout source code
14 - uses: actions/checkout@v2
15 # setup java
16 - name: set up JDK 1.8
17 uses: actions/setup-java@v1
18 with:
19 java-version: 1.8
20 # setup flutter
21 - name: Setup flutter
22 uses: subosito/flutter-action@v1
23 with:
24 flutter-version: "2.0.1"
25 # pub get
26 - run: flutter pub get
27 # analyze
28 - run: flutter analyze
29 # test
30 - run: flutter test
31 # build
32 - name: build dev
33 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

Saltaré la explicación sobre Java configuración de Java, la configuración de Flutter pub get , el analyze y la test . La tarea de compilación consta de algunos comandos conectados con && . Veamos uno por uno.

bash
1echo $KEY_JKS | base64 --decode --ignore-garbage > android/key.jks

Este comando significa generar key.jks partir de secretos. Como expliqué, key.jks está registrado como una cadena codificada en base64, por lo que aquí debemos decodificarlo y volver a generar el archivo key.jks Al generar el archivo, la aplicación puede cargar información clave desde el archivo mientras configuramos el archivo de la tienda para que sea un file("../key.jks") .

En la tarea de compilación, no key.properties archivo key.properties. Esto se debe a que configuramos keyPassword y storePassword como $System.env.KEY_PASSWORD . Hemos configurado el entorno como se muestra a continuación, por lo que la aplicación puede cargar la contraseña desde el entorno del sistema. Si desea leer la contraseña de key.properties , puede modificar bundle.gradle y generar el key.properties aquí en la tarea de compilación.

yaml
1- name: build dev
2 env:
3 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
4 STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}

A continuación, verifiquemos el comando de compilación.

bash
1flutter build apk --release

Esto es completamente igual que la construcción local. Entonces verá advertencias que le indican que debe usar el app bundle o la apk dividida para evitar la apk grasa. Según el sitio oficial Creación de la aplicación para su lanzamiento, se recomienda el app bundle Entonces, cuando lanza la aplicación, es mejor crear un app bundle , sin embargo, cuando prueba la aplicación en su dispositivo o usa App Distribute de Firebase , necesita apk .

Por lo que recomiendo elegir la forma de construir su aplicación de acuerdo con el propósito. Por ejemplo, como CI diaria, puede generar apk que le permite probar fácilmente en su dispositivo, y como compilación de lanzamiento, puede generar un app bundle y registrar la aplicación en Google Play Store . Los ejemplos siguientes proporcionan ambos tipos de construcción.

Registre una aplicación con App Distribution

yaml
1name: CICD
2
3on:
4 pull_request:
5 branches: [master]
6 workflow_dispatch:
7
8jobs:
9 dev:
10 runs-on: ubuntu-latest
11
12 steps:
13 # checkout source code
14 - uses: actions/checkout@v2
15 # setup java
16 - name: set up JDK 1.8
17 uses: actions/setup-java@v1
18 with:
19 java-version: 1.8
20 # setup flutter
21 - name: Setup flutter
22 uses: subosito/flutter-action@v1
23 with:
24 flutter-version: "2.0.1"
25 # pub get
26 - run: flutter pub get
27 # analyze
28 - run: flutter analyze
29 # test
30 - run: flutter test
31 # build
32 - name: build
33 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 --release
39 # app distribution
40 - name: Firebase App Distribution
41 uses: wzieba/Firebase-Distribution-Github-Action@v1.2.2
42 with:
43 appId: ${{secrets.FIREBASE_APP_ID}}
44 token: ${{secrets.FIREBASE_APP_TOKEN}}
45 file: build/app/outputs/apk/release/app-release.apk
46 groups: Tester

Registre una aplicación en Google Play Store

yaml
1name: Release
2
3on:
4 workflow_dispatch:
5
6jobs:
7 dev:
8 runs-on: ubuntu-latest
9
10 steps:
11 # checkout source code
12 - uses: actions/checkout@v2
13 # setup java
14 - name: set up JDK 1.8
15 uses: actions/setup-java@v1
16 with:
17 java-version: 1.8
18 # setup flutter
19 - name: Setup flutter
20 uses: subosito/flutter-action@v1
21 with:
22 flutter-version: "2.0.1"
23 # pub get
24 - run: flutter pub get
25 # analyze
26 - run: flutter analyze
27 # test
28 - run: flutter test
29 # build
30 - name: build
31 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 --release
37 # google play
38 - name: Create service account json
39 env:
40 SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON}}
41 run: echo $SERVICE_ACCOUNT_JSON > android/app/service-account.json
42 - uses: r0adkll/upload-google-play@v1
43 with:
44 serviceAccountJson: android/app/service-account.json
45 packageName: your app id
46 releaseFiles: build/app/outputs/bundle/release/app-release.aab
47 track: alpha
48 whatsNewDirectory: release-notes
49 mappingFile: build/app/outputs/mapping/release/mapping.txt

4. Otras opciones

4-1. Firebase google-services.json

bash
1echo $GOOGLE_SERVICES > android/app/google-services.json

Para conectarse con Firebase , necesita google-services.json . Puede generar el json con el comando anterior. Para obtener más detalles, consulte FlutterFire - instalación de Android.

4-2. App Distribution

App Distribution es una de las funciones que ofrece Firebase Puede entregar la aplicación a los probadores mediante esta función. Si desea utilizar esta función, simplemente agregue la siguiente tarea a la canalización.

yaml
1- name: Firebase App Distribution
2 uses: wzieba/Firebase-Distribution-Github-Action@v1.2.2
3 with:
4 appId: your app id
5 token: ${{secrets.FIREBASE_APP_TOKEN}}
6 file: build/app/outputs/apk/release/app-release.apk
7 groups: Tester

Consulta Acción de Github de distribución de Firebase para obtener más información. Tenga en cuenta que solo se permite el archivo apk App Distribution .

4-3. Product Flavor

Cuando use product flavor con Flutter , especifique --flavor <flavor name> en el comando de construcción. Consulte variantes de compilación para conocer product flavor

bash
1flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release --flavor jp

Con flavor , puede generar diferentes ID de aplicación con el mismo código.

gradle
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 }

Con flavor , el ID de la aplicación será la combinación de applicationId y applicationIdSuffix . Por ejemplo, si especifica --flavor jp , el ID de la aplicación será xxx.yyy.zzz.jp

También puede modificar el archivo de manifiesto con flavor . Suponga que va a utilizar Google AdMob , luego debe agregar applicationId al archivo de manifiesto. Puede hacer esto configurando archivos de manifiesto como se muestra a continuación.

txt
1android/app/src
2 |- main/AndroidManifest.xml
3 |- jp/AndroidManifest.xml
4 |- eu/AndroidManifest.xml

Coloque el archivo de manifiesto base en android/app/src/main , luego coloque cada archivo de manifiesto dependiente del flavor android/app/src/<flavor name> . Luego, la aplicación compilada contendrá tanto el manifiesto principal como el manifiesto de sabor.

xml
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-data
6 android:name="com.google.android.gms.ads.APPLICATION_ID"
7 android:value="your admob app id"/>
8 </application>
9</manifest>

4-4. --dart-define

Esta opción le permite pasar variables de entorno a la aplicación.

bash
1flutter build appbundle --obfuscate --split-debug-info=build/app/outputs/symbols --release --flavor jp --dart-define=REGION=asia-northeast1

Este comando genera un entorno llamado REGION que describe la región del servidor backend, por ejemplo. La aplicación puede leer el entorno mediante el siguiente código.

dart
1String region = String.fromEnvironment('REGION'); // get 'asia-northeast1'

4-5. Registra una aplicación en Google Play Store

Cuando registre su aplicación en Google Play Store , utilice esta tarea.

yaml
1- name: Create service account json
2 env:
3 SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON}}
4 run: echo $SERVICE_ACCOUNT_JSON > android/app/service-account.json
5- uses: r0adkll/upload-google-play@v1
6 with:
7 serviceAccountJson: android/app/service-account.json
8 packageName: your app id
9 releaseFiles: build/app/outputs/bundle/release/app-release.aab
10 track: alpha
11 whatsNewDirectory: release-notes
12 mappingFile: build/app/outputs/mapping/release/mapping.txt

La primera tarea genera service-account.json que es necesario para acceder a Play Store desde el servidor CI Luego especifique este archivo en la segunda tarea. Consulte upload google play para obtener más información.

Referencias

Cree y publique una aplicación de Android

Secretos cifrados

Creación de la aplicación para su lanzamiento

FlutterFire - instalación de Android

Firebase CLI

Acción de Github de distribución de Firebase

variantes de compilación

cargar google play

Compartir:

Artículos relacionados