GradleスクリプトによるManifestファイルの管理

11072 ワード

コンパイル時に異なる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ファイルの解析と自動生成
この部分は本文の重点です!上記の手順から見ると、manifestファイルを2部用意しているのは明らかで、普段のメンテナンス時にも一緒に修正する必要があります.実際のビジネスではdebugとreleaseのmanifestの内容があまり悪くない可能性があります.一部のコンポーネントノードの属性が異なるだけで、手動で変更するのも面倒です.
gradleスクリプトでmanifestファイルを動的に変更して生成できますか?もちろんいいです.本質的にはXMLファイルを処理します.AndroidManifestは標準的なXMLファイルです.ちょうど、GroovyはXMLを処理するのがとても簡単です.ここでは実際の例で話します.
たとえば、debugテストでは、デスクトップアイコンが正常に表示され、releaseリリースでは非表示にする必要があります.では、2つのmanifestファイルはこうです.

<manifest ...>
    <application ...>
        
        <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:

<manifest ...>
    <application ...>
        
        <activity
            ...
            android:name=".TestActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                
                <data 
                    android:host="localhost" 
                    android:scheme="${applicationId}" />
            intent-filter>
        activity>
...

そして、通常の開発では、4つのコンポーネントを追加すると、2つのファイルが同時に増加しますが、違いはエントリActivityだけです.私たちが望んでいるのはreleaseバージョンのmanifestが自動的に生成される(生成時にそのdataノードを挿入する)ことであり、普段はdebugバージョンを変更するファイルを開発するだけでよい.
主な考え方は簡単です.
  • GroovyのXML解析ライブラリを介してdebugのmanifestファイルを読み込み、ノードを巡ってエントリActivityを見つけます.
  • dataノードをエントリActivityの下に挿入する.
  • は、releaseバージョンのファイルに新しい内容を書き込む.

  • まず直接見てください.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の構文は不思議で、ここではノード名で配列を直接取得することができます.例えばアプリケーション、0を取るのはもちろん最初です.一般的に私たちのmanifestにはアプリケーションノードがあるので、空のポインタ異常は発生しません.eachは配列の関数で、配列の各ノードを遍歴し、compパラメータは私がカスタマイズした名前です.アプリケーションのサブノードを手に入れることができて、後ですべていくつかの論理処理で、言うまでもありません.
    いくつかの小さな問題
    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
    ', '
    '))

    ここでXmlUtilツールクラスのserializeメソッドの戻りタイプは実際にStringなので、このように直接replaceすることができます.