在Android使用Gradle进行编译打包时,有时候需要动态更改AndroidManifest.xml中application、activity等节点属性,大多数情况下一般通过占位符替换即可完成相应的功能,但存在一些比较复杂的情况,如果还是采用占位符替换,显得就不再方便或者不能达到所想要的目的。下面我将介绍另一种通过Groovy动态更改AndroidManifest.xml节点属性的方法。

       先说一下我遇到的问题,我的问题是我想打两种不同的aar包,给不同应用使用,其中一个需要包用来跨进程使用,因此需要给AndroidManifest中的一些activity、service等增加android:process属性,正常情况下肯定不能打包的时候手动修改增加改属性,所以希望通过控制开关或者属性来动态控制打包结果。这里如果使用占位符的方式来做,就需要在所有需要多进程的activity或者service等中都需要添加android:process属性,这不方便阅读和后续版本开发,因为如果这样去做,开发的时候都需要注意添加这个属性,觉得很不方便,类似这样的需求还有例如替换theme等等。面对这样的需求如果能够在打包编译的时候动态替换那就即可以完成需求又能解决后续开发存在的问题。

       通过参考网上的一些资料和gradle编译过程,了解到在打包生成变体时会在processManifest时生成一个缓存AndroidManifest.xml文件,然后打包时使用的manifest文件就是该文件,因此在该task执行完成之后通过解析改XML文件,然后动态添加和替换属性即可。代码如下:

android.getLibraryVariants().all { variant ->
    variant.outputs.each() {
        //single_process 用于控制独立进程开关
        it.processManifest.doLast { task ->
            if(project.hasProperty("single_process") && project.property("single_process").equals("2")){
                //受gradle版本影响,AndroidManifest.xml文件位置有所不同
                def manifestFile = new File("${buildDir}/intermediates/bundles/${it.dirName}/AndroidManifest.xml")
                if(!manifestFile.exists()){
                    def dir=''
                    it.baseName.split('-').each {
                        dir=dir+it+'/'
                    }
                    manifestFile=new File("${buildDir}/intermediates/manifests/full/${dir}AndroidManifest.xml")
                }
                def XmlParser = new XmlParser()
                def manifest = XmlParser.parse(manifestFile)
                def processTag = "android:process"
                def android = new Namespace('http://schemas.android.com/apk/res/android', 'android')
                def nodes=manifest.application[0].'*'.findAll{
                    (it.name()=='activity'||it.name()=='service')&&it.attribute(android.process)==null //选择要修改的节点
                }
                nodes.each{
                    it.attributes().put(processTag,":wallet_sdk")
                }
                PrintWriter pw = new PrintWriter(manifestFile)
                pw.write(groovy.xml.XmlUtil.serialize(manifest))
                pw.close()
            }
        }
    }
}

值得注意的是,受gradle版本问题,所以打aar包时manifest的文件位置存在不一致,我曾测试使用3.1.2的代码放到2.2中发现找不到manifest文件,同时it.dirName属性(product flavor输出目录)也在3.1.2中获取为空值。如果是想对打包APK时进行替换,AndroidManifest.xml都能在intermediates/manifests/full目录下找到,需要修改的是因plugin不同对应的variant不一样,将getLibraryVariants()替换成applicationVariants,如下:

android.applicationVariants.all { variant ->
    variant.outputs.each() {
        it.processManifest.doLast { task ->
            //manifest操作
        }
    }
}

关于Groovy对XML更多的操作,可查看相关文档。

参考文档:

1. http://www.groovy-lang.org/api.html