在 Gradle 任务 processManifest.doLast 中编辑 AndroidManifest.xml 在



我在编译时使用以下 Gradle 脚本对 AndroidManifest 进行一些修改.xml。在此示例中,我想注入一个<meta-data>元素。代码基于此答案。

android {
    // ...
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.processManifest.doLast {
                def manifestOutFile = output.processManifest.manifestOutputFile
                def newFileContents = manifestOutFile.getText('UTF-8').replace("</application>", "<meta-data ... /></application>")
                manifestOutFile.write(newFileContents, 'UTF-8')
            }
        }
    }
}

当我在 Android Studio 中进行 Gradle 同步或从命令行进行干净的构建时,这可以按预期工作:元数据可以从应用程序内访问。

但是当我从Android Studio运行▶应用程序时,修改后的清单似乎被忽略了,因为插入的元数据不是APK中编译清单的一部分,并且应用程序本身在运行时也找不到它,元数据根本不存在。

在所有情况下,合并的中间 AndroidManifest.xml(在/build/intermediates/manifests/ 中)确实包含更改,但由于某种原因,如果我运行该应用程序,它看起来会被忽略。

为了使它更加明显,我尝试插入一些无效的XML: 在这种情况下,由于清单中的语法错误,Gradle 同步和干净构建按预期失败。但是我仍然能够从Android Studio运行该应用程序,因此修改实际上被忽略了。

重现此问题的最简单方法是先清理项目(在 Android Studio 中),这会导致重新处理清单(如果出现语法错误,我按预期失败),然后运行该应用程序,即使使用无效的清单也可以运行。

请注意,doLast中的任务每次都会执行:打印任务中的println(),中间清单包含更改。

就好像在我的任务执行之前,清单被编译到 APK 中一样。

问题出在哪里?

我正在使用带有Android Gradle Plugin 2.0.0的Android Studio 2.0。

我发现它与Android Studio 2.0中引入的即时运行功能有关。如果我关闭它,一切都按预期工作。但是由于我想使用即时运行,所以我进一步挖掘。

问题是,启用即时运行后,中间的AndroidManifest.xml文件将位于另一个位置,即/build/intermediates/bundles/myflavor/instant-run/。这意味着我有效地编辑了错误的文件。另一个清单文件可以使用属性 instantRunManifestOutputFile 访问,可以使用它代替 manifestOutputFile

为了使它在所有用例中都能正常工作,我检查两个临时清单文件是否存在,如果存在,请修改它们:

applicationVariants.all { variant ->
    variant.outputs.each { output ->
        output.processManifest.doLast {
            [output.processManifest.manifestOutputFile,
             output.processManifest.instantRunManifestOutputFile
            ].forEach({ File manifestOutFile ->
                if (manifestOutFile.exists()) {
                    def newFileContents = manifestOutFile.getText('UTF-8').replace("</application>", "<meta-data ... /></application>")
                    manifestOutFile.write(newFileContents, 'UTF-8')
                }
            })
        }
    }
}

实际上没有instantRunManifestOutputFile的文档.我得到的唯一谷歌搜索结果是Android Gradle插件源代码。但后来我还发现了第三个潜在的清单文件属性aaptFriendlyManifestOutputFile,我也不知道它是关于什么的......

我想

为这个问题添加一些额外的信息。@Floern的答案有点过时了。该代码适用于旧的 Gradle 版本。新版本的Gradle表示manifestOutputFile已被弃用,并将很快被删除。 instantRunManifestOutputFile根本不存在。因此,以下是新 Gradle 版本的示例:

applicationVariants.all { variant ->                
    variant.outputs.each { output ->
        output.processManifest.doLast {
            def outputDirectory = output.processManifest.manifestOutputDirectory                
            File manifestOutFile = file(new File(outputDirectory, 'AndroidManifest.xml'))
            if(manifestOutFile.exists()){
    
                // DO WHATEVER YOU WANT WITH MANIFEST FILE. 
    
            }
        }
    }
}

编辑:以下是 Gradle 5.4.1 和 Grudle 插件 3.5.1 的较新变体:

android.applicationVariants.all { variant -> 
    variant.outputs.each { output ->
        def processManifest = output.getProcessManifestProvider().get()
        processManifest.doLast { task ->
            def outputDir = task.getManifestOutputDirectory()
            File outputDirectory
            if (outputDir instanceof File) {
                outputDirectory = outputDir
            } else {
                outputDirectory = outputDir.get().asFile
            }
            File manifestOutFile = file("$outputDirectory/AndroidManifest.xml")
            if (manifestOutFile.exists() && manifestOutFile.canRead() && manifestOutFile.canWrite()) {
                // DO WHATEVER YOU WANT WITH MANIFEST FILE. 
            }
        }
    }
}

更新:

对于 Gradle 7.0.2

和 Gradle 插件 7.0.2

而不是task.getManifestOutputDirectory()使用task.getMultiApkManifestOutputDirectory()

希望这会帮助某人。

各种gradle版本有所不同,对我来说,我使用了gradle-5.5-rc-3com.android.tools.build:gradle:3.4.1所以这将起作用:

def static setVersions(android, project, channelId) {
    android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def processorTask = output.processManifestProvider.getOrNull()
            processorTask.doLast { task ->
                def directory = task.getBundleManifestOutputDirectory()
                def srcManifestFile = "$directory/AndroidManifest.xml"
                def manifestContent = new File(srcManifestFile).getText()
                def xml = new XmlParser(false, false).parseText(manifestContent)
                xml.application[0].appendNode("meta-data", ['android:name': 'channelId', 'android:value': '\' + channelId])
                def serializeContent = groovy.xml.XmlUtil.serialize(xml)
                def buildType = getPluginBuildType(project)
                new File("${project.buildDir}/intermediates/merged_manifests/$buildType/AndroidManifest.xml").write(serializeContent)
            }
        }
    }
}
def static getPluginBuildType(project) {
    def runTasks = project.getGradle().startParameter.taskNames
    if (runTasks.toString().contains("Release")) {
        return "release"
    } else if (runTasks.toString().contains("Debug")) {
        return "debug"
    } else {
        return ""
    }
}

中间体/merged_manifests的位置是从模块的构建目录中找到的,可能是其他位置取决于 android 插件版本,只需查看构建目录并找到您的。

我需要编辑合并的清单文件,这对我有用

android.applicationVariants.all { variant ->
variant.outputs.each { output ->
    def processManifest = output.getProcessManifestProvider().get()
    processManifest.doLast { task ->
        def outputDir = task.getManifestOutputDirectory()
        File manifestOutFile = file("$outputDir/AndroidManifest.xml")
        def newManifest = manifestOutFile.getText().replace("@string/app_name", "Broccoli")
        manifestOutFile.write(newManifest, 'UTF-8')
   }
}
}

在这个简单的情况下,我将应用程序名称替换为西兰花。

如果您需要更多信息,我写了一篇关于它的博客文章

https://androidexplained.github.io/android/gradle/2020/09/28/editing-manifest.html

替换清单文件中某些内容的正确方法是使用清单占位符。

该属性采用键值对的映射。

android {
    defaultConfig {
        manifestPlaceholders = [
            hostName:"www.example.com",
            hostName2:"www.example2.com"
        ]
    }
    ...
}

如果使用多个风格,则可以按风格定义属性。

flavorDimensions "environment"
productFlavors {
    staging {
        dimension "environment"
        applicationIdSuffix ".test"
        manifestPlaceholders = [
                hostName:"www.example.com",
                hostName2:"www.example2.com"
        ]
    }
    prod {
        dimension "environment"
        manifestPlaceholders = [
                hostName:"www.example.com",
                hostName2:"www.example2.com"
        ]
    }
}

然后,您可以将其中一个占位符作为属性值插入清单文件中,如下所示

<intent-filter ... >
    <data android:scheme="http" android:host="${hostName}" ... />
    <data android:scheme="http" android:host="${hostName2}" ... />
    ...
</intent-filter>

相关内容

  • 没有找到相关文章

最新更新