Copying APK file in Android Gradle project

2019-01-16 06:57发布

问题:

I'm trying to add a custom task to my Android project's build.gradle to copy the final APK and Proguard's mapping.txt into a different directory. My task depends on the assembleDevDebug task:

task publish(dependsOn: 'assembleDevDebug') << {
    description 'Copies the final APK to the release directory.'

    ...
}

I can see how to do a file copy using the standard Copy task type, as per the docs:

task(copy, type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}

but that assumes you know the name and location of the file you want to copy.

How can I find the exact name and location of the APK file which was built as part of the assembleDevDebug task? Is this available as a property? It feels as if I should be able to declare the files as inputs to my task, and declare them as outputs from the assemble task, but my Gradle-fu isn't strong enough.

I have some custom logic to inject the version number into the APK filename, so my publish task can't just assume the default name and location.

回答1:

If you can get the variant object associated with devDebug you could query it with getOutputFile().

So if you wanted to publish all variants you'd something like this:

def publish = project.tasks.create("publishAll")
android.applicationVariants.all { variant ->
  def task = project.tasks.create("publish${variant.name}Apk", Copy)
  task.from(variant.outputFile)
  task.into(buildDir)

  task.dependsOn variant.assemble
  publish.dependsOn task
}

Now you can call gradle publishAll and it'll publish all you variants.

One issue with the mapping file is that the Proguard task doesn't give you a getter to the file location, so you cannot currently query it. I'm hoping to get this fixed.



回答2:

The following code is what I'm using to archive apk and proguard mapping into a zip file for each variant with 'release' build type:

def releasePath = file("${rootDir}/archive/${project.name}")

def releaseTask = tasks.create(name: 'release') {
    group 'Build'
    description "Assembles and archives all Release builds"
}

android.applicationVariants.all { variant ->
    if (variant.buildType.name == 'release') {
        def build = variant.name.capitalize()

        def releaseBuildTask = tasks.create(name: "release${build}", type: Zip) {
            group 'Build'
            description "Assembles and archives apk and its proguard mapping for the $build build"
            destinationDir releasePath
            baseName variant.packageName
            if (!variant.buildType.packageNameSuffix) {
                appendix variant.buildType.name
            }
            if (variant.versionName) {
                version "${variant.versionName}_${variant.versionCode}"
            } else {
                version "$variant.versionCode"
            }
            def archiveBaseName = archiveName.replaceFirst(/\.${extension}$/, '')
            from(variant.outputFile.path) {
                rename '.*', "${archiveBaseName}.apk"
            }
            if (variant.buildType.runProguard) {
                from(variant.processResources.proguardOutputFile.parent) {
                    include 'mapping.txt'
                    rename '(.*)', "${archiveBaseName}-proguard_\$1"
                }
            }
        }
        releaseBuildTask.dependsOn variant.assemble

        variant.productFlavors.each { flavor ->
            def flavorName = flavor.name.capitalize()
            def releaseFlavorTaskName = "release${flavorName}"
            def releaseFlavorTask
            if (tasks.findByName(releaseFlavorTaskName)) {
                releaseFlavorTask = tasks[releaseFlavorTaskName]
            } else {
                releaseFlavorTask = tasks.create(name: releaseFlavorTaskName) {
                    group 'Build'
                    description "Assembles and archives all Release builds for flavor $flavorName"
                }
                releaseTask.dependsOn releaseFlavorTask
            }
            releaseFlavorTask.dependsOn releaseBuildTask
        }
    }
}

It creates tasks like the following:

  • release - Assembles and archives all Release builds
  • releaseFree - Assembles and archives all Release builds for flavor Free
  • releaseFreeRelease - Assembles and archives apk and its proguard mapping for the FreeRelease build
  • releasePaid - Assembles and archives all Release builds for flavor Paid
  • releasePaidRelease - Assembles and archives apk and its proguard mapping for the PaidRelease build

Content of archive/projectName/packageName-buildType-versionName_versionCode.zip would be:

  • packageName-buildType-versionName_versionCode.apk
  • packageName-buildType-versionName_versionCode-proguard_mapping.txt


回答3:

This is how I copy mappings.txt whenever proguard runs

tasks.whenTaskAdded { task ->
    if (task.name.startsWith("proguard")) {//copy proguard mappings
        task << {
            copy {
                from buildDir.getPath() + "/proguard"
                into '../proguard'
                include '**/mapping.txt'
            }
            println "PROGUARD FILES COPIED"
        }

    } 
}


回答4:

I've got some good pointers here but also had a hard time to get done as I wanted. Here's my final version:

def archiveBuildTypes = ["distribute"];
def archiveFlavors = ["googleplay"]

android.applicationVariants.all { variant ->
    if (variant.buildType.name in archiveBuildTypes) {
        variant.productFlavors.each { flavor ->
            if (flavor.name in archiveFlavors) {
                def taskSuffix = variant.name.capitalize()
                def version = "${android.defaultConfig.versionCode} (${android.defaultConfig.versionName})" // assumes that versionName was especified here instead of AndroidManifest.xml
                def destination = "${rootDir}/${project.name}/archive/${version}"

                def assembleTaskName = "assemble${taskSuffix}"
                if (tasks.findByName(assembleTaskName)) {
                    def copyAPKTask = tasks.create(name: "archive${taskSuffix}", type:org.gradle.api.tasks.Copy) {
                        description "Archive/copy APK and mappings.txt to a versioned folder."
                        from ("${buildDir}") {
                            include "**/proguard/${flavor.name}/${variant.buildType.name}/mapping.txt"
                            include "**/apk/${variant.outputFile.name}"
                        }
                        into destination
                        eachFile { file->
                            file.path = file.name // so we have a "flat" copy
                        }
                        includeEmptyDirs = false
                    }
                    tasks[assembleTaskName].finalizedBy = [copyAPKTask]
                }
            }
        }
    }
}


回答5:

def publish = project.tasks.create("publishAll")// publish all task
applicationVariants.all { variant ->
    if (variant.buildType.name.equals("release")) {// Only Release  
        File outDir = file("//192.168.4.11/Android/Release")
        File apkFile = variant.outputs[0].outputFile
        File mapFile = variant.mappingFile

        def task = project.tasks.create("publish${variant.name.capitalize()}Apk", Copy)
        task.from apkFile, mapFile
        task.into outDir
        task.rename "mapping.txt", "${apkFile.name.substring(0, apkFile.name.length() - 3)}mapping.txt"// Rename mapping.txt
        task.doLast{
            println ">>>publish ${variant.name} success!" +
                    "\ndir: ${outDir}" +
                    "\napk: ${apkFile.name}"
        }

        task.dependsOn variant.assemble
        publish.dependsOn task
    }
}


回答6:

Usually the android plugin will put the apks in the APP/build/apk directory.

So, run assembleDebug then ls APP/build/apk and you should see:

  • APP-debug-unaligned.apk
  • APP-release-unaligned.apk
  • APP-release-unsigned.apk
  • APP-release.apk etc