在之前的文章Android 自定义Gradle插件(二):修正AndroidManifest文件中介绍了如何在自定义的Gradle插件中修正AndroidManifest文件。解析和修正AndroidManifest首要用到了groovy供给的XmlParserXmlUtil

适配问题

应用到公司实践的项目中时发现,当编译插件运用的gradle版别和引进插件的项目的gradle版别不同的情况下,编译时会呈现问题,示例如下:

编译插件时运用的是gradle-6.5,引进插件的项目运用的是gradle-7.0.2,执行打包命令时会呈现过错,如图:

Android — 使用dom4j解析、修改AndroidManifest

其时运用gradle-6.5gradle-7.0.2别离生成了插件,以供不同的项目依据项目装备选用合适的版别。可是每逢插件调整后需求重新发布时都要切换gradle版别进行编译,比较麻烦。所以想用其他xml解析库来解析和修正AndroidManifest,处理适配问题。

dom4j

dom4j是一个根据Java的XML解析器,供给了一种易于运用的办法来处理XML文档。在解析大规模和复杂的XML文档时表现出色,具有高性能和低内存占用的优势。所以决定运用dom4j来解析和修正AndroidManifest

dom4j github

增加依靠

在app module下的build.gradle中增加代码,如下:

dependencies {
    implementation("org.dom4j:dom4j:2.1.4")
}

运用dom4j解析和修正Manifest

通过为包括intent-filter的四大组件增加exported特点,简略演示一下dom4j的运用办法。为了方便演示,将示例AndroidManifest文件放在assets下。

示例AndroidManifest内容如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:name="com.chenyihong.exampledemo.base.ExampleApplication">
        <activity android:name="com.chenyihong.exampledemo.home.HomeActivity">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>
        <activity android:name="com.chenyihong.exampledemo.tripartite.dom4j.Dom4jExampleActivity" />
        <receiver android:name="com.chenyihong.exampledemo.exampleReciver1">
            <intent-filter>
            </intent-filter>
        </receiver>
        <service android:name="com.chenyihong.exampledemo.exampleService1">
            <intent-filter>
            </intent-filter>
        </service>
        <receiver android:name="com.chenyihong.exampledemo.exampleReciver2" />
        <service android:name="com.chenyihong.exampledemo.exampleService2" />
    </application>
</manifest>

解析Manifest,获取四大组件元素

读取assets下的AndroidManifest文件,解析获取四大组件元素,代码如下:

class Dom4jExampleActivity : AppCompatActivity() {
    // Android命名空间
    private val android = Namespace("android", "http://schemas.android.com/apk/res/android")
    // android:name对应的QName
    private val androidName = QName.get("name", android)
    // android:exported对应的QName
    private val androidExported = QName.get("exported", android)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: LayoutDom4jExampleActivityBinding = DataBindingUtil.setContentView(this, R.layout.layout_dom4j_example_activity)
        binding.includeTitle.tvTitle.text = "dom4j Example"
        val reader = SAXReader()
        // 读取xml文件,解析为Document对象。
        // read办法传入参数有File、URL、InputStream、Reader、InputSource等。
        val manifestDocument = reader.read(assets.open("AndroidManifest.xml"))
        // 获取Document的根元素,即Manifest
        val manifestElement = manifestDocument.rootElement
        // 获取application元素
        val applicationElement = manifestElement.element("application")
        // 获取application元素下,除了provider外的其他四大组件的所有元素。
        // Element.elements(String name) 能够获取元素中包括的所有同名的子元素。
        // Element.element(String name) 能够获取元素中第一个同名的子元素。
        val componentElement = ArrayList<Element>().apply {
            addAll(applicationElement.elements("activity"))
            addAll(applicationElement.elements("receiver"))
            addAll(applicationElement.elements("service"))
        }
    }
}

判别并增加android:exported特点

class Dom4jExampleActivity : AppCompatActivity() {
    ...
    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        componentElement.forEach {
            if (hadIntentFilter(it) && !hadExportedAttribute(it)) {
                // 有intent-filter可是没有android:exported特点,需求增加
                // 发动页需求设置为true,其他设置为false
                it.addAttribute(androidExported, mainLauncherActivity(it).toString())
            }
        }
    }
    // 判别是否包括intent-filter
    private fun hadIntentFilter(componentElement: Element): Boolean {
        return componentElement.element("intent-filter") != null
    }
    // 判别是否包括android:exported
    private fun hadExportedAttribute(componentElement: Element): Boolean {
        return componentElement.attribute(androidExported) != null
    }
    // 判别Activity是否为发动页,是的话exported特点需求设置为true
    private fun mainLauncherActivity(componentElement: Element): Boolean {
        return if (componentElement.name == "activity") {
            var hadLauncherCategory = false
            var hadMainAction = false
            componentElement.element("intent-filter").elements().forEach {
                when (it.attribute(androidName).value) {
                    "android.intent.category.LAUNCHER" -> hadLauncherCategory = true
                    "android.intent.action.MAIN" -> hadMainAction = true
                }
            }
            hadLauncherCategory && hadMainAction
        } else {
            false
        }
    }
}

输出修正后的Manifest

class Dom4jExampleActivity : BaseGestureDetectorActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        binding.tvResultValue.text = writeManifestToString(manifestDocument)
    }
    ...
    private fun writeManifestToString(document: Document): String {
        var resultValue = ""
        try {
            val format = OutputFormat.createPrettyPrint()
            // 设置缩进为4
            format.setIndentSize(4)
            val stringWriter = StringWriter()
            val xmlWriter = XMLWriter(stringWriter, format)
            xmlWriter.write(document)
            xmlWriter.flush()
            resultValue = stringWriter.toString()
            xmlWriter.close()
            stringWriter.close()
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return resultValue
    }
}

作用如图:

Android — 使用dom4j解析、修改AndroidManifest

示例

演示代码已在示例Demo中增加。

ExampleDemo github

ExampleDemo gitee