📜 ⬆️ ⬇️

Automate the publication of the application on Google Play - directly from Android Studio

Hello! Half a year ago on Habré there was an article about how to automate the download of application updates on Google Play. The first comment on the article and the answer to it read one unpleasant thing:



But I am happy to announce that this is not true. You can publish the application directly from Android Studio! Moreover, you can do it without Android Studio at all on your CI - since this will be done using the usual Gradle task.

My solution is similar to what was described in the previous article, but instead of java, I used a groovy script.
')
In order to publish applications from a script, you need to create a user with access to publish and get a .json file that we will use in our code for authentication. How to get it and what needs to be done to activate access to the Google Play Developer API can be viewed in this article , or you can read my publication on working with Google Play Billing on the server side, where Part 3 describes the creation of service account for access to google play.

From now on, we will assume that you already have the coveted .json file with the service account secret.

To begin, we will prepare a project. Let's work with the build.gradle of our root project, not the app. We give root / build.gradle to this form:

//      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' } 

What is done:

1. apply plugin: 'groovy'
Activate the groovy compiler in our project.

2. dependencies - compile 'org.codehaus.groovy: groovy-all: 2.4.7'
Import the latest version of groovy into our project.

3. dependencies - compile 'com.google.apis: google-api-services-androidpublisher: v2-rev38-1.22.0'

We import the library from Google, which, in fact, gives us the opportunity to work with publications in Google play ( and not only ).

Now we can write groovy scripts and groovy classes and use them in our project. But first, let's create a source dir for our groovy classes and organize the other files we need:

 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 

In order to publish an application on Google Play, you need to sign it with a release certificate. But we don’t want to store our keystore, turnout and passwords in the repository? Use .gitignore. We will put the passwords in the root / signing.properties file :

 keystore.file=keystore.jks keystore.password=<> key.alias=<_> key.password=<_> 

Let's read these passwords from the file with create a suitable signing signing in 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 } } } 

Now we can use gradle assembleProdRelease to get the apk-file, which is downloadable on Google Play.

Let's start creating the script itself, which will publish our apk. Create a root / src / main / groovy / ApkPublisher.groovy file :

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

The second file is 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() 

The script for downloading the file is ready. Now let's move on to creating the 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") } } 

Now it is enough to execute the gradle assembleAndPublishApk command to publish apk to the alpha channel. And this can be easily done even after each commit in development. In addition, we immediately load the proguard-mapping file.

PS What else to read?
1. My previous article - Android In-app Billing: from a mobile application to server validation and testing
2. Google Play Developer API reference
3. Example from Google on GitHub

Source: https://habr.com/ru/post/316302/


All Articles