編譯時區分不同的manifest
很多Android項目都會區分debug和release的manifest文件,以便調試,一些組件化的項目甚至有多個manifest文件來調試不同的組件。舉個簡單的例子,在app的build.gradle文件中:
android {
defaultConfig {
applicationId "com.xxx.xxx"
}
sourceSets {
main {
if(是否爲debug打包) {
manifest.srcFile "${projectDir}/src/main/debug/AndroidManifest.xml"
} else {
manifest.srcFile "${projectDir}/src/main/release/AndroidManifest.xml"
}
}
}
}
這個地方的if條件,一般可以是一個變量,或者一個方法:
// 這樣
def isDebug = true
// 或者
def isDebug() {
// 一些邏輯
return true
}
// 以上均遵循Groovy語法規則,Android項目中的Gradle腳本都是用Groovy語言(一門JVM動態語言,很容易上手)編寫的。
android { ... }
然後此處的 ${projectDir} 變量對應的是當前模塊所在的路徑,這裏就是app的路徑,不是整個工程的路徑。這樣一來,我們就能編譯不同的manifest文件了。
自動判斷編譯類型
但是,這樣每次編譯都需要手動改那個debug變量,挺麻煩的,尤其是一些技術團隊可能是用的公司服務器在線編譯,每次都要提交代碼到倉庫。其實我們可以用gradle插件的特性,這樣來判斷:
// 這裏app是當前模塊名,assembleDebug是gradle編譯debug包時的默認task
if(gradle.startParameter.taskNames.contains(":app:assembleDebug")) {
manifest.srcFile "${projectDir}/src/main/debug/AndroidManifest.xml"
} else {
manifest.srcFile "${projectDir}/src/main/release/AndroidManifest.xml"
}
不用改代碼,打release包和debug包就自動區分了,當然你也可以增加新的條件,去對應不同的編譯task。
解析並自動生成manifest文件
這部分是本文重點啦!從上面的步驟來看,我們顯然是準備了2份manifest文件,平時維護時也需要一起修改。實際業務中很可能debug和release的manifest內容差不多,可能只是某些組件節點的屬性不同,手動改也挺麻煩的。
能不能通過gradle腳本動態地來修改並生成manifest文件呢?當然可以,本質上就是處理XML文件。AndroidManifest是標準的XML文件。正好,Groovy處理XML又非常的簡單。這裏我們還是以一個實際例子來講。
比如我們的需求是,在debug測試時,應用正常顯示桌面圖標,在release發佈時,應用需要隱藏圖標。那麼,兩個manifest文件就是這樣的:
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<application ...>
<!--App入口頁面-->
<activity
...
android:name=".TestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
release版本的manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<application ...>
<!--App入口頁面-->
<activity
...
android:name=".TestActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!--在入口Activity添加如下data節點即可隱藏桌面圖標-->
<data
android:host="localhost"
android:scheme="${applicationId}" />
</intent-filter>
</activity>
...
然後平時我們開發過程中,如果新增了一些四大組件,2個文件都要同時增加,而區別卻只有入口Activity。我們想要的是release版本的manifest是自動生成的(在生成時插入那個data節點),平時只需要開發改動debug版本的文件即可。
主要思路比較簡單:
- 通過Groovy的XML解析庫讀取debug的manifest文件,遍歷節點找到入口Activity;
- 將data節點插入到入口Activity下面;
- 把新的內容寫入release版本的文件當中。
先直接看build.gradle腳本源碼:
import groovy.xml.XmlUtil
def getManifestPath(buildType) {
return "${projectDir}/src/main/$buildType/AndroidManifest.xml"
}
def handleManifestXml(manifest) {
if (gradle.startParameter.taskNames.contains(":app:assembleDebug")) {
// debug build
def debugPath = getManifestPath('debug')
manifest.srcFile debugPath
println("Manifest path: $debugPath")
} else {
// release build
def releasePath = getManifestPath('release')
println("Manifest path: $releasePath")
manifest.srcFile releasePath
def debugPath = getManifestPath('debug')
def debugFile = new File(debugPath)
def releaseFile = new File(releasePath)
def debugXml = new XmlParser(false, false).parse(debugFile)
// 修改debug的manifest,自動生成release版本
debugXml.application[0].each { comp ->
if (comp.name() == 'activity') {
comp.each { filter ->
// 搜索入口Activity
if (filter.toString().contains('android.intent.category.LAUNCHER')) {
// 添加data節點,隱藏桌面icon
filter.appendNode('data', ['android:host': 'localhost', 'android:scheme': '${applicationId}'])
return true // break each閉包
}
}
}
}
releaseFile.write(XmlUtil.serialize(debugXml))
}
}
android {
defaultConfig {
applicationId "com.xxx.xxx"
}
sourceSets {
main {
// 此處會自動處理manifest文件
// 注意:平時開發時只需修改debug下的manifest即可,請勿手動修改release的
handleManifestXml(manifest)
}
}
...
關鍵邏輯從 def debugXml = new XmlParser(false, false).parse(debugFile)
開始,這裏的XmlParser構造方法可以不傳任何參數,我這裏傳false主要是爲了讓manifest根節點自動添加 xmlns ,這樣最後生成文件內容會簡潔一點。
然後就是 debugXml.application[0].each { comp -> ... }
循環塊,Groovy的語法很神奇,這裏可以直接通過節點名稱來獲取數組,比如application,取0當然就是第一個,一般我們的manifest裏就一個application節點,所以也不會出現空指針異常。each是數組的函數,遍歷數組的每一個節點,comp參數是我自定義的命名。能拿到application的子節點,後面就都是一些邏輯處理了,不贅述。
一些小問題
如果是Windows開發者,最後寫文件時,需要注意換行符和編碼的問題。
1、最後的write方法,加上參數:
releaseFile.write(xmlStr, "UTF-8")
或者直接改全局配置,修改整個工程下的gradle.properties文件,在gradle的JVM參數後面追加如下:
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
2、換行符問題,沒有JVM參數可供修改,只能手動處理,還是最後write時:
releaseFile.write(XmlUtil.serialize(debugXml).replaceAll('\r\n', '\n'))
這裏XmlUtil工具類的serialize方法返回類型實際上就是String,所以可以這樣直接replace。