Android 开发进程 0.35 升级编译版本Android12

Android12升级

工作需要升级到编译版本31 在这里记录一下遇到的问题。
错误:Manifest merger failedManifest merger failed 这个问题通常搜到的答案是manifest文件格式错误,但这是由于升级编译版本的原因,在Android SDK 31中,需要明确声明组件的 exported属性 android:exported="true" 官方文档如下:
Android 开发进程 0.35  升级编译版本Android12
这意味这我们的manifest中每个组件的字段都需要添加exported的属性,包括所有依赖的库和module,主工程和module可以自己修改,但如果依赖的库没有写规范,结果会编译成功后在Android12版本上不能正确安装,而远程依赖的库和插件是不好修改的。此时要用到Android构建特性,Android在打包过程中,所有的组件和依赖产生的manifest文件将集合到一起,同时主manifest即我们app的manifest文件将会被重写,所以可以用gradle文件Groovy脚本实现。
具体在GitHub上有实现,笔者在此只是转载:https://github.com/phamtdat/AndroidSnippets/blob/master/Android12AndroidManifestAddMissingAndroidExported/build.gradle
具体代码如下:

import org.w3c.dom.Element
import org.w3c.dom.Node

import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.transform.TransformerFactory
import javax.xml.transform.Transformer

def addAndroidExportedIfNecessary(File manifestFile) {
    def manifestAltered = false
    def reader = manifestFile.newReader()
    def document = groovy.xml.DOMBuilder.parse(reader)
    def application = document.getElementsByTagName("application").item(0)
    if (application != null) {
        println "Searching for activities, services and receivers with intent filters..."
        application.childNodes.each { child ->
            def childNodeName = child.nodeName
            if (childNodeName == "activity" || childNodeName == "activity-alias" ||
                    childNodeName == "service" || childNodeName == "receiver") {
                def attributes = child.getAttributes()
                if (attributes.getNamedItem("android:exported") == null) {
                    def intentFilters = child.childNodes.findAll {
                        it.nodeName == "intent-filter"
                    }
                    if (intentFilters.size() > 0) {
                        println "found ${childNodeName} ${attributes.getNamedItem("android:name").nodeValue} " +
                                "with intent filters but without android:exported attribute"

                        def exportedAttrAdded = false
                        for (def i = 0; i < intentFilters.size(); i++) {
                            def intentFilter = intentFilters[i]
                            def actions = intentFilter.childNodes.findAll {
                                it.nodeName == "action"
                            }
                            for (def j = 0; j < actions.size(); j++) {
                                def action = actions[j]
                                def actionName = action.getAttributes().getNamedItem("android:name").nodeValue
                                if (actionName == "com.google.firebase.MESSAGING_EVENT") {
                                    println "adding exported=false to ${attributes.getNamedItem("android:name")}..."
                                    ((Element) child).setAttribute("android:exported", "false")
                                    manifestAltered = true
                                    exportedAttrAdded = true
                                }
                            }
                        }
                        if (!exportedAttrAdded) {
                            println "adding exported=true to ${attributes.getNamedItem("android:name")}..."
                            ((Element) child).setAttribute("android:exported", "true")
                            manifestAltered = true
                        }
                    }
                }
            }
        }
    }
    if (manifestAltered) {
        document.setXmlStandalone(true)
        Transformer transformer = TransformerFactory.newInstance().newTransformer()
        DOMSource source = new DOMSource(document)
        FileWriter writer = new FileWriter(manifestFile)
        StreamResult result = new StreamResult(writer)
        transformer.transform(source, result)
        println "Done adding missing android:exported attributes to your AndroidManifest.xml. You may want to" +
                "additionally prettify it in Android Studio using [command + option + L](mac) or [CTRL+ALT+L](windows)."
    } else {
        println "Hooray, your AndroidManifest.xml did not need any change."
    }
}

def getMissingAndroidExportedComponents(File manifestFile) {
    List<Node> nodesFromDependencies = new ArrayList<>()
    def reader = manifestFile.newReader()
    def document = groovy.xml.DOMBuilder.parse(reader)
    def application = document.getElementsByTagName("application").item(0)
    if (application != null) {
        println "Searching for activities, services and receivers with intent filters..."
        application.childNodes.each { child ->
            def childNodeName = child.nodeName
            if (childNodeName == "activity" || childNodeName == "activity-alias" ||
                    childNodeName == "service" || childNodeName == "receiver") {
                def attributes = child.getAttributes()
                if (attributes.getNamedItem("android:exported") == null) {
                    def intentFilters = child.childNodes.findAll {
                        it.nodeName == "intent-filter"
                    }
                    if (intentFilters.size() > 0) {
                        println "found ${childNodeName} ${attributes.getNamedItem("android:name").nodeValue} " +
                                "with intent filters but without android:exported attribute"

                        def exportedAttrAdded = false
                        for (def i = 0; i < intentFilters.size(); i++) {
                            def intentFilter = intentFilters[i]
                            def actions = intentFilter.childNodes.findAll {
                                it.nodeName == "action"
                            }
                            for (def j = 0; j < actions.size(); j++) {
                                def action = actions[j]
                                def actionName = action.getAttributes().getNamedItem("android:name").nodeValue
                                if (actionName == "com.google.firebase.MESSAGING_EVENT") {
                                    println "adding exported=false to ${attributes.getNamedItem("android:name")}..."
                                    ((Element) child).setAttribute("android:exported", "false")
                                    exportedAttrAdded = true
                                }
                            }
                        }
                        if (!exportedAttrAdded) {
                            println "adding exported=true to ${attributes.getNamedItem("android:name")}..."
                            ((Element) child).setAttribute("android:exported", "true")
                        }
                        nodesFromDependencies.add(child)
                    }
                }
            }
        }
    }
    return nodesFromDependencies
}

def addManifestFileComponents(File manifestFile, List<Node> components) {
    def reader = manifestFile.newReader()
    def document = groovy.xml.DOMBuilder.parse(reader)
    def application = document.getElementsByTagName("application").item(0)
    if (application != null) {
        println "Adding missing components with android:exported attribute to ${manifestFile.absolutePath} ..."
        components.each { node ->
            Node importedNode = document.importNode(node, true)
            application.appendChild(importedNode)
        }
    }
    if (components.size() > 0) {
        document.setXmlStandalone(true)
        Transformer transformer = TransformerFactory.newInstance().newTransformer()
        DOMSource source = new DOMSource(document)
        FileWriter writer = new FileWriter(manifestFile)
        StreamResult result = new StreamResult(writer)
        transformer.transform(source, result)
        println "Added missing app-dependencies components with android:exported attributes to your " +
                "AndroidManifest.xml.You may want to additionally prettify it in Android Studio using " +
                "[command + option + L](mac) or [CTRL+ALT+L](windows)."
    }
    println "----"
}

task doAddAndroidExportedIfNecessary {
    doLast {
        def root = new File(project.rootDir, "")
        if (root.isDirectory()) {
            def children = root.listFiles()
            for (def i = 0; i < children.size(); i++) {
                File child = children[i]
                if (child.isDirectory()) {
                    File srcDirectory = new File(child, "src")
                    if (srcDirectory.exists() && srcDirectory.isDirectory()) {
                        def srcChildren = srcDirectory.listFiles()
                        for (def j = 0; j < srcChildren.size(); j++) {
                            File manifestFile = new File(srcChildren[j], "AndroidManifest.xml")
                            if (manifestFile.exists() && manifestFile.isFile()) {
                                println "found manifest file: ${manifestFile.absolutePath}"
                                addAndroidExportedIfNecessary(manifestFile)
                                println "-----"
                            }
                        }
                    }
                }
            }
        }
    }
}

task doAddAndroidExportedForDependencies {
    doLast {
        List<Node> missingComponents = new ArrayList<>()
        def root = new File(project.rootDir, "")
        if (root.isDirectory()) {
            def children = root.listFiles()
            for (def i = 0; i < children.size(); i++) {
                File child = children[i]
                if (child.isDirectory()) {
                    File mergedManifestsDirectory = new File(child, "build/intermediates/merged_manifests")
                    if (mergedManifestsDirectory.exists() && mergedManifestsDirectory.isDirectory()) {
                        def manifestFiles = mergedManifestsDirectory.listFiles().findAll { directoryChild ->
                            directoryChild.isDirectory() &&
                                    (new File(directoryChild, "AndroidManifest.xml")).exists()
                        }.stream().map { directoryWithManifest ->
                            new File(directoryWithManifest, "AndroidManifest.xml")
                        }.toArray()

                        if (manifestFiles.size() > 0) {
                            File mergedManifest = manifestFiles[0]
                            if (mergedManifest.exists() && mergedManifest.isFile()) {
                                missingComponents = getMissingAndroidExportedComponents(mergedManifest)

                                if (missingComponents.size() > 0) {
                                    File srcDirectory = new File(child, "src")
                                    if (srcDirectory.exists() && srcDirectory.isDirectory()) {
                                        def srcChildren = srcDirectory.listFiles()
                                        for (def j = 0; j < srcChildren.size(); j++) {
                                            File manifestFile = new File(srcChildren[j], "AndroidManifest.xml")
                                            if (manifestFile.exists() && manifestFile.isFile()) {
                                                addManifestFileComponents(manifestFile, missingComponents)
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

具体使用方法将此代码加入app中的gradle中,也可以抽出为独立gradle文件,在build后执行doAddAndroidExportedIfNecessary 任务,如果成功后会在app中的manifest文件中找到遍历出来未明确表明的exported得组件声明。
如果失败的话可以尝试将SDK版本降到30在执行构建任务再提高编译版本。之后就可以开启12的特新测试了。在此感谢提供脚本的大佬。

上一篇:Android12系统源码分析:NativeTombstoneManager


下一篇:一文为你详解Unique SQL原理和应用