Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to automatically increase and release signed apk in Android Studio using Gradle script

I'm trying to automatically update the versionName and VersionCode parameters in Android Manifest and use them in the output file name instead of "app-release.apk".

From this site I added this code in my build.gradle file:

import java.util.regex.Pattern
import com.android.builder.core.DefaultManifestParser

def mVersionCode
def mNextVersionName
def newName

task ('increaseVersionCode') << {
    def manifestFile = file("src/main/AndroidManifest.xml")
    def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
    def manifestText = manifestFile.getText()
    def matcher = pattern.matcher(manifestText)
    matcher.find()
    mVersionCode = Integer.parseInt(matcher.group(1))
    def manifestContent = matcher.replaceAll("versionCode=\"" + ++mVersionCode + "\"")
    manifestFile.write(manifestContent)

}

task ('incrementVersionName') << {
    def manifestFile = file("src/main/AndroidManifest.xml")
    def patternVersionNumber = Pattern.compile("versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\"")
    def manifestText = manifestFile.getText()
    def matcherVersionNumber = patternVersionNumber.matcher(manifestText)
    matcherVersionNumber.find()
    def majorVersion = Integer.parseInt(matcherVersionNumber.group(1))
    def minorVersion = Integer.parseInt(matcherVersionNumber.group(2))
    def pointVersion = Integer.parseInt(matcherVersionNumber.group(3))
    def buildVersion = Integer.parseInt(matcherVersionNumber.group(4))
    mNextVersionName = majorVersion + "." + minorVersion + "." + pointVersion + "." + (buildVersion + 1)
    def manifestContent = matcherVersionNumber.replaceAll("versionName=\"" + mNextVersionName + "\"")
    manifestFile.write(manifestContent)

}

tasks.whenTaskAdded { task ->
    if (task.name == 'generateReleaseBuildConfig') {
        task.dependsOn 'increaseVersionCode'
        task.dependsOn 'incrementVersionName'
    }
}

this code works perfectly, the 2 tasks run and correctly update the manifest file. Now I want to use the 2 variables mVersionCode and mNextVersionName in the release block inside the buildTypes like this:

newName = defaultConfig.applicationId + "-" + mNextVersionName + " (" + mVersionCode + ").apk"
applicationVariants.all { variant ->
                variant.outputs.each {output ->
                    def file = output.outputFile
                    output.outputFile = new File(file.parent, file.name.replace("app-release.apk", newName))
                }
            }

but the returned value of the 2 is null.

I also tried setting properties and extra properties:

task.setProperty("vName", mNextVersionName) 
ext.vName = mNextVersionName 
extensions.extraProperties.set("vName", mNextVersionName)

in the 2 tasks and getting them in the release block without luck.

Does someone has ideas on how to accomplish this?

like image 685
Alex Cortinovis Avatar asked Jul 07 '15 09:07

Alex Cortinovis


People also ask

What is signing config in gradle?

In Android Studio, you can configure your project to sign the release version of your app automatically during the build process by creating a signing configuration and assigning it to your release build type. A signing configuration consists of a keystore location, keystore password, key alias, and key password.

What does Gradlew assembleRelease do?

gradlew assembleRelease produces warnings for basic react-native app with enabled Hermes.


2 Answers

No one answered, but I found a working solution. I don't like it, it can be done in a better way, but for now is the only workaround I found.

I'm still looking for a better solution, if you have one, I'll be happy to read and use it instead of mine.


So, I added a new task in the gradle.build file, it just do a copy of the 'app-release.apk' file and rename it using the values in the manifest file:

task copia{
    dependsOn('assembleRelease')
    def manifestFile = file("src/main/AndroidManifest.xml")
    def patternVersionNumber = Pattern.compile("versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)\"")
    def manifestText = manifestFile.getText()
    def matcherVersionNumber = patternVersionNumber.matcher(manifestText)
    matcherVersionNumber.find()
    def majorVersion = Integer.parseInt(matcherVersionNumber.group(1))
    def minorVersion = Integer.parseInt(matcherVersionNumber.group(2))
    def pointVersion = Integer.parseInt(matcherVersionNumber.group(3))
    def buildVersion = Integer.parseInt(matcherVersionNumber.group(4))
    def mVersionName = majorVersion + "." + minorVersion + "." + pointVersion + "." + (buildVersion)

    def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
    def matcher = pattern.matcher(manifestText)
    matcher.find()
    def myVersionCode = Integer.parseInt(matcher.group(1))
    copy {
        from 'app-release.apk'
        rename { String fileName ->
            fileName.replace('app-release.apk', 'com.walker93.catpadova-' + mVersionName + ' (' + myVersionCode + ').apk')
        }
        into 'apk_outputs'
    }
}

As you can see it's just a copy and edited version of the code in the 2 previous tasks with a copy {} function (here the docs), it reads the versionName and versionCode values without increment them, then it put them in the new filename i'm going to replace.

This task still requires the 2 other task to be there, so finally you will have 3 tasks. In the first line of this task there is dependsOn(assembleRelease). This is very important, it will run all the others default release build tasks (included our 2 tasks that increment and write in the manifest) that generate the updated and signed apk.

Now if you try to run the "generate signed APK..." you will notice that this task will not run. Instead you will have to manually launch it:

Add your task in the "run configurations" like this and then you should be able to run the task from here or here when you want to publish the signed apk with the custom name. If you want to generate a signed apk with default name (and still increment versionName and versionCode) just use the "generate signed APK..." option like before.

Using this method there is no need to add code in the buildTypes part of the file.

like image 143
Alex Cortinovis Avatar answered Nov 14 '22 23:11

Alex Cortinovis


Allright, here is my code of build.gradle of application module :

apply plugin: 'com.android.application'

apply from: 'versionalization.gradle'

def genVersionName = VersionInfo.versionName
def genVersionCode = VersionInfo.versionCode

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.vincestyling.exerciseapk"
        minSdkVersion 10
        targetSdkVersion 22
        versionName genVersionName
        versionCode genVersionCode
    }
}

android.applicationVariants.all { variant ->
    def taskSuffix = variant.name.capitalize()
    def assembleTaskName = "assemble${taskSuffix}"

    if (tasks.findByName(assembleTaskName)) {
        def processAPKTask = tasks.create(name: "process${taskSuffix}Apk", type: Copy) {
            variant.outputs.each { output ->
                from output.outputFile
                into output.outputFile.parent

                def newApkName = android.defaultConfig.applicationId + "-" + variant.buildType.name + "-" + genVersionName + " (" + genVersionCode + ").apk"
                rename ~/(.+)/, newApkName
            }
        }
        tasks[assembleTaskName].finalizedBy processAPKTask
    }
}

the versionalization.gradle which applied at head is what I use to increase the VersionInfo, then return two values to use.

task VersionInfo {
    String FACTOR_KEY = "BUILD_NUMBER_FACTOR"

    File buildDir = file("build")
    buildDir.mkdir()

    File factorPropFile = new File(buildDir, "kbuildfactor.prop")

    Properties props = new Properties()
    if (factorPropFile.exists()) {
        props.load(new FileInputStream(factorPropFile))
    }

    int buildNumberFactor = props.get(FACTOR_KEY) as Integer ?: 0
    buildNumberFactor += 1

    props.put(FACTOR_KEY, buildNumberFactor as String)
    props.store(new FileOutputStream(factorPropFile), null)



    String BASE_VERSION_NAME = "BASE_VERSION_NAME"
    String BASE_VERSION_CODE = "BASE_VERSION_CODE"


    File versionPropFile = file("versioning.properties")
    props.load(new FileInputStream(versionPropFile))


    String baseVersionName = props.get(BASE_VERSION_NAME) as String
    Integer baseVersionCode = props.get(BASE_VERSION_CODE) as Integer

    ext.versionName = baseVersionName + "." + buildNumberFactor
    ext.versionCode = baseVersionCode * 1000 + buildNumberFactor
}

it's quite straightforward, read two files to take the necessary fields to constructing the version informations.

we copy/rename the final APK at last.

besides, I achieved the copy/rename part as below first, but it won't work when you have any product flavors, I paste here as another choice.

android.applicationVariants.all { variant ->
    variant.outputs.each {output ->
        def newApkName = android.defaultConfig.applicationId + "-" + variant.buildType.name + "-" + genVersionName + " (" + genVersionCode + ").apk"

        def oldApkName = "app-${variant.buildType.name}.apk"

        def file = output.outputFile

        output.outputFile = new File(file.parent, file.name.replace(oldApkName, newApkName))
    }
}
like image 20
VinceStyling Avatar answered Nov 15 '22 00:11

VinceStyling