Gradle 插件練習-動態移除權限

1. 開始以前

Gradle DSL 文檔html

Gradle基於Groovy,而Groovy基於Java,最後始終得運行在JVM之上.Gradle、build.gradle、settings.gradle之類的最終都會被搞成一個對象,而後才能執行.java

  • Gradle 對象: 每次執行gradle taskName時,Gradle都會默認構造出一個Gradle對象.在執行過程當中,只有這麼一個Gradle對象,通常不多去定製它.
  • Project對象: 一個build.gradle就對應着一個Project對象.
  • Settings對象: 一個settings.gradle就對應着一個Settings對象.

它們的生命週期節點以下:node

2. 建立Plugin

先建立buildSrc這個module,用於開發插件.(不清楚的能夠看我以前發的Gradle系列(四) Gradle插件). 而後新建一個插件類: ManifestDemoPlugin.android

import org.gradle.api.Plugin
import org.gradle.api.Project
class ManifestDemoPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        
    }
}
複製代碼

上面這份代碼是標準代碼,寫插件都得繼承自Plugin.git

3. 分析

需求: 假設是移除android.permission.READ_PHONE_STATE權限(有時三方庫aar裏面可能會定義一些權限,可是又不能讓app有這些權限,就須要移除掉.這裏僅僅是爲了練習Gradle,其實有更好的方式移除權限tools:remove).github

思路: 咱們須要拿到合併以後的AndroidManifest.xml文件,且在打包以前修改這個AndroidManifest.xml文件,將android.permission.READ_PHONE_STATE內容移除.api

可是咱們怎麼hook這個合併AndroidManifest.xml文件的時機,從而拿到清單文件內容呢?首先經過./gradlew tasks --all命令看看有哪些task,由於合併清單文件確定是一個task裏面作的,咱們只須要在這個task以後執行咱們寫的代碼邏輯便可.markdown

//task實在太多了,這裏只是節選.
> Task :tasks

......
app:makeApkFromBundleForDebug
app:makeApkFromBundleForRelease
app:mergeDebugAndroidTestAssets
app:mergeDebugAndroidTestGeneratedProguardFiles
app:mergeDebugAndroidTestJavaResource
app:mergeDebugAndroidTestJniLibFolders
app:mergeDebugAndroidTestNativeLibs
app:mergeDebugAndroidTestResources
app:mergeDebugAndroidTestShaders
app:mergeDebugAssets
app:mergeDebugGeneratedProguardFiles
app:mergeDebugJavaResource
app:mergeDebugJniLibFolders
app:mergeDebugNativeLibs
app:mergeDebugResources
app:mergeDebugShaders
app:mergeDexRelease
app:mergeExtDexDebug
app:mergeExtDexDebugAndroidTest
app:mergeExtDexRelease
app:mergeLibDexDebug
app:mergeLibDexDebugAndroidTest
app:mergeProjectDexDebug
app:mergeProjectDexDebugAndroidTest
app:packageDebug
app:packageDebugAndroidTest
app:packageDebugBundle
app:packageDebugUniversalApk
app:packageRelease
app:packageReleaseBundle
app:packageReleaseUniversalApk
app:parseDebugIntegrityConfig
app:parseReleaseIntegrityConfig
app:preBuild
app:preDebugAndroidTestBuild
app:preDebugBuild
app:preDebugUnitTestBuild
prepareKotlinBuildScriptModel
app:prepareKotlinBuildScriptModel
app:prepareLintJar
app:prepareLintJarForPublish
......

複製代碼

其中有一個task叫app:mergeDebugResources,翻譯過來就是合併資源嘛,看起來就是咱們要找的.下面是Android Plugin Task的大體含義.網絡

4. 開始寫代碼

class ManifestDemoPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        //在afterEvaluate,配置完成以後才能拿到那些task完整的有向圖
        project.afterEvaluate {
            //1. 找到mergeFreeDebugResources這個task
            def mergeDebugResourcesTask = project.tasks.findByName("mergeFreeDebugResources")
            if (mergeDebugResourcesTask != null) {
                //2. 建立一個task
                def parseDebugTask = project.tasks.create("ParseDebugTask", ParseDebugTask.class)
                //3. 添加一個mergeDebugResourcesTask結束後立馬執行的task: parseDebugTask
                mergeDebugResourcesTask.finalizedBy(parseDebugTask)
            }
        }
    }
}
複製代碼
  1. 咱們須要在Project配置完成以後才能拿到全部的task,由於這個時候才真正生成了完整的有向圖task依賴.
  2. 其次是經過API: project.tasks拿到全部的task(API文檔地址在這裏),而後再findByName方法找到這個task.
  3. 這時咱們得建立一個本身的task並在mergeFreeDebugResources執行完成以後立馬開始執行.

來看看咱們的Task該怎麼寫:app

class ParseDebugTask extends DefaultTask {

    @TaskAction
    void doAction() {
        //1. 找到清單文件這個file
        def file = new File(project.buildDir, "/intermediates/merged_manifests/freeDebug/AndroidManifest.xml")
        if (!file.exists()) {
            println("文件不存在")
            return
        }

        //2. 得到文件內容
        def fileContent = file.getText()

        removePermission(file, fileContent)
    }

    /** * 動態給清單文件移除權限 * @param rootNode Node * @param file 清單文件 */
    void removePermission(File file,String fileContent) {
        //方案1 這樣會把全部權限都移除了,暫時沒找到合適的辦法
        //def rootNode = new XmlParser().parseText(fileContent)
        //def node = new Node(rootNode, "uses-permission"/*,["android:name" : "android.permission.READ_PHONE_STATE"]*/)
        //rootNode.remove(node)
        //def updateXmlContent = XmlUtil.serialize(rootNode)
        //println(updateXmlContent)

        //方案2 讀取到xml內容以後,將制定權限的字符串給替換掉,,妙啊 妙啊
        fileContent = fileContent.replace("android.permission.READ_PHONE_STATE", "")
        println(fileContent)
        //將字符串寫入文件
        file.write(fileContent)
    }

}
複製代碼
  1. 首先Task得繼承自DefaultTask
  2. 合併以後的清單文件是在build/intermediates/merged_manifests/freeDebug/目錄下,先獲得這個文件
  3. 經過file.getText()獲取文件內容,再將內容裏面的字符串"android.permission.READ_PHONE_STATE"移除掉.
  4. 而後再將字符串寫入清單文件(待會兒打包的時候就是用的這個文件進行打包的).

順便,咱還能夠再來一個,動態添加一個權限:android.permission.INTERNET

/** * 動態給清單文件添加權限 * @param rootNode Node * @param file 清單文件 */
void addPermission(File file,,String fileContent) {
    def rootNode = new XmlParser().parseText(fileContent)
    //3. 添加網絡權限 這裏得加上xmlns:android
    //<uses-permission android:name="android.permission.INTERNET"/>
    //xmlns:android="http://schemas.android.com/apk/res/android"
    rootNode.appendNode("uses-permission", ["xmlns:android": "http://schemas.android.com/apk/res/android",
                                            "android:name" : "android.permission.INTERNET"])

    //還能夠動態將meta-data加到Application中 
    //rootNode.application[0].appendNode("meta-data", ['android:name': 'appId', 'android:value': 546525]) 

    //4. 拿到修改後的xml內容
    def updateXmlContent = XmlUtil.serialize(rootNode)
    println(updateXmlContent)

    //5. 將修改後的xml 寫入file中
    file.write(updateXmlContent)
}
複製代碼

5. 總結

剛開始的時候API很是不熟悉,咱得瘋狂地查API.儘可能想一些需求作練習,多寫寫代碼,多查查API,熟悉這個過程.Gradle插件很是重要.

就這?

相關文章
相關標籤/搜索