如何在 gradle 中更改 Android 项目的混淆器映射文件名

How to change the proguard mapping file name in gradle for Android project

我有基于 gradle 的 android 项目,我想在为我的构建生成文件名后更改 mapping.txt 文件名。怎么做到的?

更新

如何在 build.gradle 中完成?因为我可以在那里访问我的口味和其他僵硬的东西,所以我想根据 flavor/build 变体版本创建映射文件名。

在你的 proguard-rules.pro 文件中使用这个命令:

-printmapping path/to/your/file/file_name.txt

文件将写入 {root}/path/to/your/file 部分,名称为 file_name.txt

如果你想为不同的口味设置不同的设置,你可以为它们定义许多proguard-rules

我发现了另一种想法,但我不确定它是否正确。

您可以在口味中定义您的路径:

productFlavors {
    test1 {
        applicationId "com.android.application.test"
        project.ext."${name}Path" = 'path/one/mapp.txt'
    }
    test2 {
        project.ext."${name}Path" = 'path/two/mapp.txt'
    }
}

接下来您可以在 $asseble{variant.name.capitalize()} 任务之前定义新任务,如下所示:

android.applicationVariants.all { variant ->
    def envFlavor = variant.productFlavors.get(0).name

    def modifyProguardPath = tasks.create(name: "modifyProguardFor${variant.name.capitalize()}", type: Exec) {
        def pathToMap = project."${envFlavor}Test1"
        doFirst {
            println "==== Edit start: $pathToMap ===="
        }
        doLast {
            println "==== Edit end: $pathToMap ===="
        }
        executable "${rootDir}/utils/test.bash"
        args pathToMap
    }

    project.tasks["assemble${variant.name.capitalize()}"].dependsOn(modifyProguardPath);
}

并且在脚本 ${root}/utils/test.bash 中 - 您可以修改 proguard-rules.pro.

但我认为存在更好的解决方案。

非常感谢 Sergii Pechenizkyi 帮助我找到了这个好的解决方案。

要为每种风格实现混淆器映射文件的复制,我们可以为每种风格创建 "root" 任务 copyProguardMappingTask 和动态任务数

def copyProguardMappingTask = project.tasks.create("copyProguardMapping")
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        ...
        if (variant.getBuildType().isMinifyEnabled()) {
            def copyProguardMappingVariantTask = project.tasks.create("copyProguardMapping${variant.name.capitalize()}", Copy)

            def fromPath = variant.mappingFile;
            def intoPath = output.outputFile.parent;

            copyProguardMappingVariantTask.from(fromPath)
            copyProguardMappingVariantTask.into(intoPath)
            copyProguardMappingVariantTask.rename('mapping.txt', "mapping-${variant.name}.txt")

            copyProguardMappingVariantTask.mustRunAfter variant.assemble
            copyProguardMappingTask.dependsOn copyProguardMappingVariantTask
        }
    }
}

之后我们应该 运行 在组装我们的项目后执行此任务。我使用 jenkins,我的任务选项看起来像

gradle clean assembleProjectName copyProguardMapping

它就像一个魅力。

更简单的解决方案。

applicationVariants.all { variant ->
        if (variant.getBuildType().isMinifyEnabled()) {
            variant.assemble.doLast {
                copy {
                    from variant.mappingFile
                    into "${rootDir}/proguardTools"
                    rename { String fileName ->
                        "mapping-${variant.name}.txt"
                    }
                }
            }
        }
    }

自上次更新后 variant.mappingFile 不再可用。 (我用的是ProGuard 4.7版,AndroidStudio 2.0)

这是我的 build.gradle 文件的(部分):

import java.util.regex.Matcher
import java.util.regex.Pattern

def getCurrentFlavor() {
    Gradle gradle = getGradle()
    String  tskReqStr = gradle.getStartParameter().getTaskRequests().toString()

    Pattern pattern;

    if( tskReqStr.contains( "assemble" ) )
        pattern = Pattern.compile("assemble(\w+)(Release|Debug)")
    else
        pattern = Pattern.compile("generate(\w+)(Release|Debug)")

    Matcher matcher = pattern.matcher( tskReqStr )

    if( matcher.find() )
        return matcher.group(1).toLowerCase()
    else
    {
        println "NO MATCH FOUND"
        return "";
    }
}

buildTypes {
    release {
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        minifyEnabled true

        applicationVariants.all { variant ->
            variant.outputs.each { output ->
                output.outputFile = new File(output.outputFile.parent, "${variant.name}_v${variant.versionName}.apk")
            }
            def mappingFile = "${rootDir}\app\build\outputs\mapping\${getCurrentFlavor()}\release\mapping.txt"
            println("mappingFile:  ${mappingFile}")
            if (variant.getBuildType().isMinifyEnabled()) {
                variant.assemble.doLast {
                    copy {
                        from "${mappingFile}"
                        into "${rootDir}"
                        rename { String fileName ->
                            "mapping-${variant.name}.txt"
                        }
                    }
                }
            }
        }

    }

    debug {
        minifyEnabled false
        useProguard false

        applicationVariants.all { variant ->
            variant.outputs.each { output ->
                output.outputFile = new File(output.outputFile.parent, "${variant.name}_v${variant.versionName}.apk")
            }
        }
    }

}

Pinhassi 的上述解决方案效果很好,并且符合最新的 Gradle 更改。虽然有几件事我必须改变:

  1. 模块名称是硬编码的 ("app"),这并不理想,因为在很多情况下(包括我的)情况并非如此。动态检测模块名比较好
  2. 映射文件也仅通过向后转义斜杠(“\”)符合 Windows 文件系统。如果您使用的是像 Linux 或 Mac 这样的 *NIX 系统,您需要将它们替换为前向非转义斜杠 ("/")
  3. 稍微更改了 .apk 文件的重命名以包含项目名称,并在末尾添加了一个 date/time 标记。

这是完成的代码:

import java.util.regex.Matcher
import java.util.regex.Pattern

buildTypes {
        release {
        debuggable false
        minifyEnabled true
        proguardFiles 'proguard.cfg'

        // Rename the apk file and copy the ProGuard mapping file to the root of the project
        applicationVariants.all { variant ->
            if (variant.getBuildType().name.equals("release")) {
                def formattedDate = new Date().format('yyyyMMddHHmmss')
                def projectName = ""
                variant.outputs.each { output ->
                    def fullName = output.outputFile.name
                    projectName = fullName.substring(0, fullName.indexOf('-'))
                    // ${variant.name} has the value of "paidRelease"
                    output.outputFile = new File((String) output.outputFile.parent, (String) output.outputFile.name.replace(".apk", "-v${variant.versionName}-${formattedDate}.apk"))
                }
                def mappingFile = "${rootDir}/${projectName}/build/outputs/mapping/${getCurrentFlavor()}/release/mapping.txt"
                println("mappingFile:  ${mappingFile}")
                if (variant.getBuildType().isMinifyEnabled()) {
                    variant.assemble.doLast {
                        copy {
                            from "${mappingFile}"
                            into "${rootDir}"
                            rename { String fileName ->
                                "mapping-${variant.name}.txt"
                            }
                        }
                    }
                }
            }
        }
    }

        debug {
            debuggable true
        }
    }

def getCurrentFlavor() {
    Gradle gradle = getGradle()
    String  tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
    Pattern pattern;

    if( tskReqStr.contains( "assemble" ) )
        pattern = Pattern.compile("assemble(\w+)(Release|Debug)")
    else
        pattern = Pattern.compile("generate(\w+)(Release|Debug)")

    Matcher matcher = pattern.matcher( tskReqStr )

    if( matcher.find() )
        return matcher.group(1).toLowerCase()
    else {
        println "NO MATCH FOUND"
        return "";
    }
}
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        if (variant.getBuildType().isMinifyEnabled()) {
            variant.assemble.doLast{
                copy {
                    from variant.mappingFile
                    into "${rootDir}/mapping"
                    rename { String fileName ->
                        "mapping-${variant.name}-${new Date().format('yyyy_MM_dd')}.txt"
                    }
                }
            }
        }
    }
}

所有这些答案都使用复制重命名文件。 但是我不想移动文件,我只是想更改它的名称,同时记住构建类型和风格。

我根据此处其他海报的代码进行了一些修改。 由于 Minify 可能是假的,同时仍然使用混淆器,我只是检查文件是否存在。

下面的代码就完成了这一点。

android {
    applicationVariants.all { variant ->
        variant.assemble.doLast {
            def mappingFolderUrl = "${project.buildDir.path}/outputs/mapping/"

            if (variant.buildType.name) {
                mappingFolderUrl += variant.buildType.name + "/"
            }

            if (variant.flavorName) {
                mappingFolderUrl += variant.flavorName + "/"
            }

            def mappingFileUrl = mappingFolderUrl + "mapping.txt"
            logger.lifecycle("mappingFile path: ${mappingFileUrl}")

            File mappingFile = file(mappingFileUrl)
            if (mappingFile.exists()) {
                def newFileName = mappingFolderUrl + "mapping-${variant.name}.txt"
                mappingFile.renameTo(newFileName)
            }
        }
    }
}

注意

您也可以使用此代码来移动文件。

.renameTo() 方法需要一个完整路径,如果您更改路径,我想您有效地将文件移动到另一个地方。

这是 but renames mapping.txt to match the apk's name exactly including the app version name. I've combined this with code to name the APK with a version number as described in this answer 的变体。我不得不窥探 gradle 源代码以找到 $variant.baseName

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"
    defaultConfig {
        applicationId "com.company.app"
        minSdkVersion 13
        targetSdkVersion 21
        versionCode 14       // increment with every release
        versionName '1.4.8'   // change with every release
        archivesBaseName = "MyCompany-MyAppName-$versionName"
    }
    applicationVariants.all { variant ->
        if (variant.getBuildType().isMinifyEnabled()) {
            variant.assemble.doLast {
                (new File(variant.mappingFile.parent, "$archivesBaseName-$variant.baseName-mapping.txt")).delete();
                variant.mappingFile.renameTo(variant.mappingFile.parent +
                        "/$archivesBaseName-$variant.baseName-mapping.txt")
            }
        }
    }
}

对于 Android Studio Gradle 插件版本 4.1.0 及更高版本(自 2020 年 5 月左右开始)

此版本修复了以下警告:

WARNING: API 'variant.getMappingFile()' is obsolete and has been replaced with 'variant.getMappingFileProvider()'.

applicationVariants.all { variant ->
    variant.assembleProvider.get().doLast {
        def mappingFileProvider = variant.getMappingFileProvider().get()
        if (mappingFileProvider != null) {
            try {
                def mappingFiles = mappingFileProvider.getFiles()

                for (mappingFile in mappingFiles) {
                    if (mappingFile != null && mappingFile.exists()) {
                        def newMappingFileName = "$archivesBaseName-$variant.baseName-$mappingFile.name"
                        project.logger.lifecycle("Renaming '${mappingFile.name}' to '${newMappingFileName}'")
                        def newMappingFile = new File(mappingFile.parent, newMappingFileName)

                        newMappingFile.delete()
                        mappingFile.renameTo(newMappingFile)
                    }
                }
            } catch (Exception ignored) {
                project.logger.lifecycle("No mapping files found to rename")
            }
        }
    }
}

对于 Android Studio Gradle 插件版本 3.3.0(2019 年 1 月)至 2020 年 5 月左右 这克服了以前 Android 3.0/Android Gradle 插件 3.0 弃用 BuildType.isMinifyEnabled() 和 gradle 插件弃用 variant.getAssemble( ).

applicationVariants.all { variant ->
    variant.assembleProvider.get().doLast {
        if (variant.mappingFile != null && variant.mappingFile.exists()) {
            def mappingFilename = "$archivesBaseName-$variant.baseName-mapping.txt"
            (new File(variant.mappingFile.parent, mappingFilename)).delete()
            variant.mappingFile.renameTo(variant.mappingFile.parent +
                    "/" + mappingFilename)
        }
    }
}

variant.assemble 现已弃用,建议的解决方案结合了之前的修改:

archivesBaseName = "MyCompany-MyAppName-$versionName"
...
applicationVariants.all { variant ->
    variant.assembleProvider.get().doLast {
        if (variant.mappingFile != null && variant.mappingFile.exists()) {
            def mappingFilename = "$archivesBaseName-$variant.baseName-mapping.txt"
        (new File(variant.mappingFile.parent, mappingFilename)).delete()
        variant.mappingFile.renameTo(variant.mappingFile.parent +
                "/" + mappingFilename)
        }
    }
}

如果使用应用程序包 (aab) 而不是 apk,请将此添加到 之后的 android 部分:

afterEvaluate {
    bundleRelease.doLast {
        android.applicationVariants.all { variant ->
            if (variant.buildType.name == 'release') {
                tasks.create(name: "renameMappingFile") {
                    if (variant.mappingFile != null && variant.mappingFile.exists()) {
                        variant.mappingFile.renameTo(variant.mappingFile.parent + "/$variant.baseName-$versionName-${new Date().format('yyyy-MM-dd_HHmm')}-mapping.txt")
                    }
                }
            }
        }
    }
}

在最后一个例子中也将 bundleRelease 换成 assembleRelease for apks。

更新:但是,如果您尝试直接对您的 phone 构建正常调试,那么最后一个将不起作用。错误:

无法为 org.gradle.api.Project.

类型的项目“:app”获取未知 属性 'bundleRelease'

适合我的完整解决方案

applicationVariants.all { variant ->
        def variantType = variant.buildType.name
        if (variantType == "release") {
            variant.assemble.doLast {
                def mappingFile = variant.mappingFile
                mappingFile.renameTo(mappingFile.parent + "/mapping-${variant.name}.txt")       
            }
        }
    }

截至今天(2020 年 5 月) 以前使用 variant.mappingFile 的解决方案在新的 Android Gradle 插件中不再有效( Android Studio) 3.6 及更高版本。

而是 variant.mappingFile returns null 并且日志中显示以下内容:

WARNING: API 'variant.getMappingFile()' is obsolete and has been replaced with 'variant.getMappingFileProvider()'.

我正在分享我的工作解决方案,它使用新的 api:


    applicationVariants.all { variant ->
        variant.assembleProvider.get().doLast {
            def mappingFiles = variant.getMappingFileProvider().get().files

            for (file in mappingFiles) {
                if (file != null && file.exists()) {
                    def nameMatchingApkFile = "$archivesBaseName-$variant.baseName-$file.name"
                    def newMappingFile = new File(file.parent, nameMatchingApkFile)

                    newMappingFile.delete() //clean-up if exists already
                    file.renameTo(newMappingFile)
                }
            }
        }
    }

请注意,由于我们使用的是 DexGuard,因此未使用 variant.getBuildType().isMinifyEnabled()

上面的代码使 映射文件的 名称与 apk 的文件名相匹配。

以防万一,如果您需要更改 apk 名称 - 可以使用以下内容:

android {
    defaultConfig {
        //resulting apk will looks like: "archive base name" + -<flavour>-<buildType>.apk
        archivesBaseName = "$applicationId-$versionName"
    }
}

这是对我有帮助的解决方案:

  • compileSdkVersion 30

  • Java版本.VERSION_1_8

  • kotlin_version = '1.5.31'

  • com.android.tools.build:gradle:7.0.2

    buildTypes {
     release {
         minifyEnabled true
         shrinkResources true
         proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    
         applicationVariants.all { variant ->
             // Generating apk file for each flavour release build
             variant.outputs.all {
                 outputFileName = "${variant.flavorName}-${variant.versionCode}.apk"
             }
    
             // Generating mapping file for each flavour release build
             if (variant.getBuildType().isMinifyEnabled()) {
                 variant.assembleProvider.get().doLast {
                     def files = variant.getMappingFileProvider().get().getFiles()
                     for (file in files) {
                         if (file != null && file.exists()) {
                             def newName = "mapping-${variant.flavorName}-${variant.versionCode}.txt"
                             def newFile = new File(file.parent, newName)
                             newFile.delete()
                             file.renameTo(newName)
                         }
                     }
                 }
             }
         }
     }