Легко разверните свое приложение Flutter в магазине Google Play
CI/CD, что означает «непрерывная интеграция/непрерывная доставка», предполагает автоматизацию процесса выпуска приложения. Это упрощает традиционно выполняемые вручную и подверженные ошибкам шаги, делая их более эффективными.
Непрерывная интеграция (CI) автоматически выполняет такие задачи, как создание приложений, тестирование и объединение изменений кода с основной базой кода. Непрерывная доставка (CD) управляет развертыванием этих изменений кода в производственной среде. Непрерывное развертывание (CD) делает еще один шаг вперед, автоматически выпуская приложения конечным пользователям.
Внедряя CI/CD, мы стремимся повысить эффективность процесса выпуска приложений, сохраняя при этом его качество и целостность.
В этом исследовании я выбрал GitHub Actions из-за его широкого использования и обширной поддержки. Я опишу шаги, необходимые для интеграции GitHub Actions с сервисами Google Play для Android.
Хотя первоначальная настройка может показаться немного сложной, она значительно упростит процесс доставки приложений нашим клиентам.
Оглавление
- Подписание APK
- Настройка рабочего процесса
- Управление версиями
- APK Сборка
- Подготовка к выпуску
- Развертывание Google Play
- План реализации
- Заключение
Подписание APK
Предполагая, что у вас есть готовое к выпуску приложение, хранящееся на GitHub, первым шагом в процессе выпуска является подписание APK. Это предполагает создание хранилища ключей для процесса загрузки, которое служит доказательством подлинности вашего приложения.
Для пользователей Windows выполните следующую команду:
keytool -genkey -v -keystore <path>\upload-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload
В назначенном <path>
вы сохраните файл хранилища ключей. Если оставить этот раздел пустым, хранилище ключей будет храниться в корневой папке вашего проекта. При появлении запроса укажите и запомните storePassword и keyPassword для последующих шагов. Выполнение приведенной выше команды создаст новый файл с именем upload-keystore.jks
в корневом каталоге вашего проекта. Переместите этот файл в папку android > app
.
Затем в каталоге Android создайте новый файл с именем key.properties
. Обязательно добавьте этот файл в свой .gitignore
, чтобы Git не отслеживал его. Заполните файл следующим образом:
storePassword=#{STORE_PASSWORD}#
keyPassword=#{KEY_PASSWORD}#
keyAlias=upload
storeFile=./upload-keystore.jks
После этого перейдите в свой репозиторий GitHub и откройте Settings > Secrets and variables > Actions
. Нажмите кнопку New repository secrets
и создайте два секрета: один с именем STORE_PASSWORD, а другой — KEY_PASSWORD. Заполните их паролями, которые вы установили при создании файла upload-keystore.jks
.
Чтобы Gradle мог использовать хранилище ключей для процесса подписи APK, откройте файл android/app/build.gradle
и вставьте следующий код над блоком кода Android. Этот дополнительный код инструктирует Gradle прочитать файл key.properties
, который вы создали ранее.
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
...
}
Разумеется, укажите конкретный код, который вы хотите добавить, перед блоком кода buildTypes
в файле android/app/build.gradle
.
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
...
}
После этого обновите содержимое блока кода buildTypes
, используя следующий код. Эта модификация гарантирует, что наше приложение будет подписано во время сборки выпуска.
buildTypes {
release {
signingConfig signingConfigs.release
}
}
Чтобы нумерация версий выполнялась автоматически, вам также необходимо изменить содержимое файла pubspec.yaml
следующим образом.
name: app_name
description: App description
publish_to: "none"
version: 99.99.99+99
Обратите внимание, что мы изменили переменную версии на 99.99.99+99
. Мы установили его в качестве заполнителя, что позволяет удобно изменять номер версии в процессе развертывания.
Подготовка рабочего процесса
Создайте файл YAML с выбранным вами именем в каталоге .github/workflows/
. Затем заполните файл следующим образом:
name: Flutter Workflow
on:
push:
branches: [main]
Управление версиями
Начальный шаг этого рабочего процесса включает в себя создание номеров версий на основе номеров тегов git
. Чтобы получить доступ к информации репозитория, необходимо включить в секреты токен GitHub. Вы можете создать токен GitHub, перейдя по этой ссылке. Имейте в виду, что секретные имена не могут начинаться с термина GITHUB
. Например, вы можете сохранить свой токен GitHub как TOKEN_GITHUB
.
После того как вы добавили токен GitHub в секреты, мы можем приступить к процессу генерации номера версии. Добавьте следующую строку ниже существующей.
jobs:
version:
name: Version Number
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Retrieve Tags and Branch History
run: |
git config remote.origin.url @github.com/${{github.repository">https://x-access-token:${{secrets.TOKEN_GITHUB}}@github.com/${{github.repository}}
git fetch --prune --depth=10000
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.9.7
with:
versionSpec: "5.x"
- name: Use GitVersion
id: gitversion
uses: gittools/actions/gitversion/execute@v0.9.7
- name: Creating version.txt with nuGetVersion
run: echo ${{steps.gitversion.outputs.nuGetVersion}} > version.txt
- name: Upload version.txt
uses: actions/upload-artifact@v2
with:
name: gitversion
path: version.txt
Результатом этого этапа будет файл version.txt
, в котором будет храниться количество тегов, которое будет служить номером версии нашего приложения на последующих шагах.
В дальнейшем мы настроим действие для создания APK для загрузки в Google Play Store. Прежде чем приступить к этапу сборки APK, обязательно сохраните файл upload-keystore.jks
и, если применимо, .env
и key.properties
в репозитории секретов.
Сначала мы зашифруем файл с помощью gpg
и надежно сохраним его содержимое в хранилище секретов. Кроме того, мы создадим секреты для безопасного хранения паролей, связанных с каждым файлом, используемым во время шифрования.
Вы можете зашифровать файл с помощью следующей команды:
gpg -c --armor file_name
Команда создаст файл с расширением .asc
. Откройте этот файл, скопируйте его содержимое, а затем вставьте в хранилище секретов. Повторите этот процесс и для других файлов. Ниже приведен готовый пример для справки.
После завершения процесса шифрования и хранения добавьте следующий блок сборки под блоком версии.
build:
name: Build APK and Creating Release
needs: [version]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: |
echo "${{secrets.RELEASE_KEYSTORE}}" > upload-keystore.jks.asc
echo "${{secrets.RELEASE_ENV}}" > .env.asc
echo "${{secrets.RELEASE_PROP}}" > key.properties.asc
gpg -d --passphrase "${{secrets.RELEASE_KEYSTORE_PASSWORD}}" --batch upload-keystore.jks.asc > android/app/upload-keystore.jks
gpg -d --passphrase "${{secrets.RELEASE_ENV_PASSWORD}}" --batch .env.asc > .env
gpg -d --passphrase "${{secrets.RELEASE_PROP_PASSWORD}}" --batch key.properties.asc > android/key.properties
- name: Get version.txt
uses: actions/download-artifact@v2
with:
name: gitversion
- name: Create New File Without Newline Char from version.txt
run: tr -d '\n' < version.txt > version1.txt
- name: Read Version
id: version
uses: juliangruber/read-file-action@v1
with:
path: version1.txt
- name: Update Version in YAML
run: sed -i 's/99.99.99+99/${{steps.version.outputs.content}}+${{github.run_number}}/g' pubspec.yaml
- name: Update Keystore Password in Gradle Properties
run: sed -i 's/#{STORE_PASSWORD}#/${{secrets.STORE_PASSWORD}}/g' android/key.properties
- name: Update Keystore Key Password in Gradle Properties
run: sed -i 's/#{KEY_PASSWORD}#/${{secrets.KEY_PASSWORD}}/g' android/key.properties
- uses: actions/setup-java@v1
with:
java-version: "12.x"
- uses: subosito/flutter-action@v1
with:
channel: "stable"
- run: flutter clean
- run: flutter pub get
- run: flutter build apk --release --split-per-abi --obfuscate --split-debug-info=symbols
- run: flutter build appbundle --release --obfuscate --split-debug-info=symbols
- name: Create a Release in GitHub
uses: ncipollo/release-action@v1
with:
artifacts: "build/app/outputs/apk/release/*.apk,build/app/outputs/bundle/release/app-release.aab"
token: ${{secrets.TOKEN_GITHUB}}
tag: ${{steps.version.outputs.content}}
commit: ${{github.sha}}
- name: Upload App Bundle
uses: actions/upload-artifact@v2
with:
name: appbundle
path: build/app/outputs/bundle/release/app-release.aab
Как показано, мы повторяем секреты для создания файла .asc
с соответствующим именем. Затем мы расшифровываем файл .asc
, возвращая его в исходное состояние. Это позволяет нам безопасно использовать файлы без необходимости включать их в репозиторий Git.
В этом блоке build
мы также заменяем заполнитель номера версии на тот, который мы создали на предыдущем шаге. Кроме того, мы заменяем заполнители для STORE_PASSWORD
и KEY_PASSWORD
учетными данными, хранящимися в хранилище секретов.
Кроме того, мы выполняем параметр --obfuscate
для сборки APK и AAB с повышенными мерами безопасности. Однако важно отметить, что на данном этапе мы не можем включить файл символов в запутанный APK или AAB, поскольку мы не предприняли никаких конкретных шагов для его сохранения.
Подготовка к выпуску
Поздравляем с достижением этого рубежа! На предыдущем этапе мы успешно установили нумерацию версий и провели сборку APK с обфускацией. Кроме того, мы надежно сохранили файлы конфигурации и хранилище ключей в хранилище секретов. После завершения этой подготовки мы готовы установить соединение между Google Cloud Platform и разработчиком Google Play, гарантируя плавный процесс выпуска.
Чтобы начать подготовку, создайте проект и учетную запись службы в Google Cloud. Вы можете создать учетную запись службы через меню IAM & Admin > Service Accounts
. После создания учетной записи службы нажмите кнопку выбора справа и откройте меню «Управление ключами». Создайте новый ключ и используйте формат JSON. Скопируйте содержимое файла JSON и сохраните его в хранилище секретов, например, с именем PLAYSTORE_ACCOUNT_KEY
, как в примере ниже.
Далее установите соединение между проектом в Google Cloud и соответствующим проектом в консоли Google Play. Убедитесь, что вы включили следующие API как в Google Cloud Platform, так и в консоли Google Play:
- API разработчика Google Play для Android
- API отчетов для разработчиков Google Play
- API публикации игровых сервисов Google Play
Для дополнительной безопасности перейдите в меню User and Permissions
в консоли Google Play. Затем выберите адрес электронной почты, связанный с созданной вами учетной записью службы. В разделе App permissions
выберите конкретный проект приложения, над которым вы работаете. Этот шаг помогает обеспечить правильный доступ и разрешения для учетной записи службы.
Кроме того, в этом меню у вас есть возможность настроить разрешения учетной записи. Ниже приведен пример разрешений, которые я обычно использую.
Версия Google Play
Примечания. Прежде чем запускать весь этот рабочий процесс, обязательно вручную загрузите файл APK/AAB в консоль Google Play.
На этом заключительном этапе рабочего процесса вы загрузите файл AAB в Play Store через выбранный вами трек. На данный момент доступны два трека: производственный и внутренний. Чтобы продолжить, добавьте блок release
ниже существующих блоков.
release:
name: Release app to production track
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get appbundle from artifacts
uses: actions/download-artifact@v2
with:
name: appbundle
- name: Release app to production track
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{secrets.PLAYSTORE_ACCOUNT_KEY}}
packageName: com.app.package_name
releaseFiles: app-release.aab
track: production
status: completed
Это этапы CI/CD Play Store с использованием GitHub Actions. Ниже приводится содержимое написанного нами YAML-скрипта в целом.
name: Flutter Stream
on:
push:
branches: [main]
jobs:
version:
name: Create Version Number
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Feth Histories for All Tags and Branches
run: |
git config remote.origin.url @github.com/${{github.repository">https://x-access-token:${{secrets.TOKEN_GITHUB}}@github.com/${{github.repository}}
git fetch --prune --depth=10000
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.9.7
with:
versionSpec: "5.x"
- name: Use GitVersion
id: gitversion
uses: gittools/actions/gitversion/execute@v0.9.7
- name: Create version.txt with nuGetVersion
run: echo ${{steps.gitversion.outputs.nuGetVersion}} > version.txt
- name: Upload version.txt
uses: actions/upload-artifact@v2
with:
name: gitversion
path: version.txt
build:
name: Build APK and Create Release
needs: [version]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: |
echo "${{secrets.RELEASE_KEYSTORE}}" > upload-keystore.jks.asc
echo "${{secrets.RELEASE_ENV}}" > .env.asc
echo "${{secrets.RELEASE_PROP}}" > key.properties.asc
gpg -d --passphrase "${{secrets.RELEASE_KEYSTORE_PASSWORD}}" --batch upload-keystore.jks.asc > android/app/upload-keystore.jks
gpg -d --passphrase "${{secrets.RELEASE_ENV_PASSWORD}}" --batch .env.asc > .env
gpg -d --passphrase "${{secrets.RELEASE_PROP_PASSWORD}}" --batch key.properties.asc > android/key.properties
- name: Get version.txt
uses: actions/download-artifact@v2
with:
name: gitversion
- name: Create New File Without Newline Char from version.txt
run: tr -d '\n' < version.txt > version1.txt
- name: Read Version
id: version
uses: juliangruber/read-file-action@v1
with:
path: version1.txt
- name: Update Version in YAML
run: sed -i 's/99.99.99+99/${{steps.version.outputs.content}}+${{github.run_number}}/g' pubspec.yaml
- name: Update Keystore Password in Gradle Properties
run: sed -i 's/#{STORE_PASSWORD}#/${{secrets.STORE_PASSWORD}}/g' android/key.properties
- name: Update Keystore Key Password in Gradle Properties
run: sed -i 's/#{KEY_PASSWORD}#/${{secrets.KEY_PASSWORD}}/g' android/key.properties
- uses: actions/setup-java@v1
with:
java-version: "12.x"
- uses: subosito/flutter-action@v1
with:
channel: "stable"
- run: flutter clean
- run: flutter pub get
- run: flutter build apk --release --split-per-abi --obfuscate --split-debug-info=symbols
- run: flutter build appbundle --release --obfuscate --split-debug-info=symbols
- name: Create a Release in GitHub
uses: ncipollo/release-action@v1
with:
artifacts: "build/app/outputs/apk/release/*.apk,build/app/outputs/bundle/release/app-release.aab"
token: ${{secrets.TOKEN_GITHUB}}
tag: ${{steps.version.outputs.content}}
commit: ${{github.sha}}
- name: Upload App Bundle
uses: actions/upload-artifact@v2
with:
name: appbundle
path: build/app/outputs/bundle/release/app-release.aab
release:
name: Release App to Production Track
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get Appbundle from Artifacts
uses: actions/download-artifact@v2
with:
name: appbundle
- name: Release App to Production Track
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{secrets.PLAYSTORE_ACCOUNT_KEY}}
packageName: com.package.name
releaseFiles: app-release.aab
track: production
status: completed
Заключение
Внедрив описанный выше конвейер CI/CD, компании могут значительно сократить количество ручных усилий, необходимых для развертывания приложений. Кроме того, использование внутреннего трека Google Play обеспечивает структурированный подход к распространению APK, облегчая такие этапы тестирования, как системное интеграционное тестирование (SIT) или пользовательское приемочное тестирование (UAT). Этот оптимизированный процесс в конечном итоге приводит к более эффективным и надежным выпускам приложений.