在之前的文章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
。
添加依赖
在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中添加。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END