在之前的文章Android 自定义Gradle插件(二):修正AndroidManifest文件中介绍了如何在自定义的Gradle插件中修正AndroidManifest
文件。解析和修正AndroidManifest
首要用到了groovy供给的XmlParser
和XmlUtil
。
适配问题
应用到公司实践的项目中时发现,当编译插件运用的gradle版别和引进插件的项目的gradle版别不同的情况下,编译时会呈现问题,示例如下:
编译插件时运用的是gradle-6.5
,引进插件的项目运用的是gradle-7.0.2
,执行打包命令时会呈现过错,如图:
其时运用gradle-6.5
和gradle-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
}
}
作用如图:
示例
演示代码已在示例Demo中增加。
ExampleDemo github
ExampleDemo gitee