Автоматизация публикации приложения в Google Play — прямо из Android Studio

от автора

Всем привет! Пол года назад на хабре была статья о том, как автоматизировать загрузку обновлений приложения в Google Play. Первый комментарий к статье и ответ на него гласил одну неприятную вещь:

Но я с радостью готов сообщить, что это — не правда. Публиковать приложение прямо из Android Studio можно! Более того, можно делать это вообще без Android Studio на вашем CI — так как делаться это будет с помощью обычного Gradle task.

Мое решение похоже на то, что описано в предыдущей статье, но вместо java я использовал groovy-скрипт.

Для того, чтобы публиковать приложения из скрипта, нужно создать пользователя с доступом для публикации и получить .json-файл, который будем использовать в нашем коде для аутентификации. Как его получить и что нужно сделать для активации доступа к Google Play Developer API можно посмотреть в указанной статье, или же можете прочитать мою публикацию о работе с Google Play Billing на стороне сервера, где в части 3 описано создание Service-account-ов для доступа к Google Play.

С данного момента будем считать, что у вас уже есть заветный .json файлик с service account secret.

Для начала подготовим нам проект. Будем работать с build.gradle нашего корневого проекта, а не app. Приведем root/build.gradle к такому виду:

// активируем груви в нашем проекте apply plugin: 'groovy'  buildscript {     repositories {         jcenter()     }     dependencies {         classpath 'com.android.tools.build:gradle:2.2.2'     } }  allprojects {     repositories {         jcenter()     } }  dependencies {     // импортируем груви в наш проект     compile 'org.codehaus.groovy:groovy-all:2.4.7'     // и библиотеку, с помощью которой будем делать publish     compile 'com.google.apis:google-api-services-androidpublisher:v2-rev38-1.22.0' } 

Что сделано:
1. apply plugin: ‘groovy’
Активируем groovy-компилятор в нашем проекте.
2. dependencies — compile ‘org.codehaus.groovy:groovy-all:2.4.7’
Импортируем последнюю версию groovy в наше проект
3. dependencies — compile ‘com.google.apis:google-api-services-androidpublisher:v2-rev38-1.22.0’
Импортируем библиотеку от Google, которая, собственно, и предоставляет нам возможность работать с публикациями в Google play (и не только).

Теперь мы можем писать groovy-скрипты и groovy-классы и использовать их в нашем проекте.
Но для начала создадим source dir для наших groovy-классов и организуем другие файлы, которые нам будут нужны:

root/     app/         ... наше приложение         .gitignore - добавим файл keystore.jks сюда, чтобы не хранить его в репозитории         keystore.jks         build.gradle     gradle/     iam/         .gitignore - добавим файл publisher.json сюда, чтобы не хранить его в репозитории         publisher.json - файл с service account secret     src/         main/             groovy/                 ... тут будем писать классы и скрипты     .gitignore - добавим файл signing.properties сюда, чтобы не хранить его в репозитории     build.gradle     gradle.properties     gradlew     gradlew.bat     local.properties     signing.properties - тут будем хранить пароли нашего keystore     settings.gradle 

Для того, чтобы опубликовать приложение в Google Play, нужно подписать его release сертификатом. Но ведь мы не хотим хранить наш keystore, явки и пароли в репозитории? Ипользуйте .gitignore. Сами же пароли поместим в файл root/signing.properties:

keystore.file=keystore.jks keystore.password=<пароль> key.alias=<имя_ключа> key.password=<пароль_ключа> 

Прочитаем эти пароли из файла с создадим подходящий signing config в root/app/build.gradle

... android {     ...     Properties signingProperties = new Properties()     def file = project.rootProject.file('signing.properties')     if (fixe.exists()) {         signingProperties.load(file.newDataInputStream())     }     def prodSigning_keystoreFile = properties.getProperty('keystore.file')     def prodSigning_keystorePassword = properties.getProperty('keystore.password')     def prodSigning_keyAlias = properties.getProperty('key.alias')     def prodSigning_keyPassword = properties.getProperty('key.password')     ...     signingConfigs {         ...         production {             storeFile file(prodSigning_keystoreFile )             storePassword prodSigning_keystorePassword              keyAlias prodSigning_keyAlias              keyPassword prodSigning_keyPassword          }     }     productFlavors {         ...         prod {             ...         }     }     buildTypes {         ...         release {             signingConfig production         }     } } 

Теперь мы можем использовать gradle assembleProdRelease чтобы получить apk-файл, который к загрузке в Google Play.

Приступим к созданию самого скрипта, который опубликует наш apk. Создадим файл root/src/main/groovy/ApkPublisher.groovy:

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport import com.google.api.client.http.FileContent import com.google.api.client.json.jackson2.JacksonFactory import com.google.api.services.androidpublisher.AndroidPublisher import com.google.api.services.androidpublisher.AndroidPublisherScopes import com.google.api.services.androidpublisher.model.Track  class ApkPublisher {      // имя пакета     String packageName;     // имя приложения (в теории оно необязательно, но без него будут warnings)     String name;     // имя apk.файла     String apkName;     // имя файла proguard-mapping     String mappingName;      void publish() {         assert packageName != null         assert name != null         assert apkName != null         assert mappingName != null          println "PUBLISHING [ ${packageName} / ${name} ]"          def dir = new File("assemble")          // загрузка service account secret для аутентификации         def inputStream = new FileInputStream("iam/publisher.json");         def transport = GoogleNetHttpTransport.newTrustedTransport();         def credential = GoogleCredential.fromStream(inputStream)             .createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER));         def builder = new AndroidPublisher.Builder(transport,             JacksonFactory.getDefaultInstance(), credential);         builder.setApplicationName(name)         def androidPublisher = builder.build();         def edits = androidPublisher.edits();          // создаем запрос на редактирование         def editRequest = edits.insert(packageName, null);         def edit = editRequest.execute();         // получаем уникальный id, который нужен нам чтобы выполнять другие действия         final String editId = edit.getId();         println " - edit_id = ${editId}"          // выполняем загрузку apk         def apkFilePath = new File(dir, apkName)         println " - apk file = ${apkFilePath}"         def apkFile = new FileContent("application/vnd.android.package-archive", apkFilePath);         def apkUploadRequest = edits.apks().upload(packageName, editId, apkFile);         def apkUploadResult = apkUploadRequest.execute();         // в ответе получаем текущий verfsionCode         int versionCode = apkUploadResult.getVersionCode()         println " - version code ${versionCode} has been uploaded"          // загружаем proguard mapping         def mappingFilePath = new File(dir, mappingName)         println " - mapping file = ${mappingFilePath}"         def mappingFile = new FileContent("application/octet-stream", mappingFilePath);         def mappingUploadRequest = edits.deobfuscationfiles()             .upload(packageName, editId, versionCode, "proguard", mappingFile);         mappingUploadRequest.execute();         println " - mapping for version ${versionCode} has been uploaded"          // теперь нужно опубликовать загруженный apk         // в данном примере мы публикуем его в альфа-тестирование         List apkVersionCodes = [versionCode]         def track = new Track().setVersionCodes(apkVersionCodes)         def updateTrackRequest = edits.tracks().update(packageName, editId, "alpha", track);         def updatedTrack = updateTrackRequest.execute();         println " - track code ${updatedTrack.getTrack()} has been updated"          // после того, как все действия выполнены         // нужно подтвердить, что запрос на редактирование завершен и "закоммитить"         // его, как транзакцию         def commitRequest = edits.commit(packageName, editId);         def appEdit = commitRequest.execute();         println " - app edit with id ${appEdit.getId()} has been comitted"         println "APP [ ${packageName} / ${name} / v${versionCode} ] SUCCESSFULLY PUBLISHED"     }  } 

Второй файл root/src/main/groovy/PublishApk.groovy:

def void moveToAssemble(String folder, String name, String newName) {     def from = new File(folder, name)     def to = new File("assemble", newName)     from.renameTo(to)     println "moved ${from} to ${to}" }  // переместим файлы в папку root/assemble // предварительно создадим ее если ее не было // и удалим старые файлы, если они там были def destDir = new File("assemble") destDir.mkdir() for (def item : destDir.listFiles()) {     item.delete() } moveToAssemble("app/build/outputs/apk", "app-prod-release.apk", "myapp.apk") moveToAssemble("app/build/outputs/mapping/prod/release", "mapping.txt", "myapp-mapping.txt")  // а теперь опубликуем приложение new ApkPublisher(         packageName: "com.example.myapp",         name: "My app",         apkName: "myapp.apk",         mappingName: "myapp-mapping.txt" ).publish() 

Скрипт для загрузки файла готов. Теперь перейдем к созданию Gradle task:

root/build.gradle

 // запускаем сборку apk task assembleApk(dependsOn: [         ':app:assembleProdRelease' ]) << {     println("APK assembled") }  // компилируем и выполняем скрипт task publishApk(dependsOn: 'classes', type: JavaExec) {     main = 'PublishApk'     classpath = sourceSets.main.runtimeClasspath }  task assembleAndPublishApk() {     dependsOn 'assembleApk'     dependsOn 'publishApk'     tasks.findByName('publishApk').mustRunAfter 'assembleApk'     doLast {         println("APK successfilly published, find it in /assemble dir")     } } 

Теперь достаточно выполнить команду gradle assembleAndPublishApk для публикации apk в альфа-канал. И это можно легко сделать хоть после каждого коммита в development. В дополнение мы сразу загружаем proguard-mapping файл.

P. S. Что еще почитать?
1. Мою предыдущую статью — Android In-app Billing: от мобильного приложения до серверной валидации и тестирования
2. Google Play Developer API reference
3. Пример от Google на GitHub
ссылка на оригинал статьи https://habrahabr.ru/post/316302/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *