Gradle外掛學習筆記(三)

mymdeep發表於2018-01-27

今天要聊的就跟android有關了,但是在介紹的時候會引用到前兩篇的知識,所以沒有看過前兩篇文章的朋友,建議先看一下前面提到的概念: Gradle外掛學習筆記(一) Gradle外掛學習筆記(二)


android編譯任務

要講到android的編譯,肯定要先看看android在執行assemble都執行了什麼,這個我們可以列印一下任務(如何列印任務?這個我們後面再說,也是個groovy外掛):

app工程的preBuild任務開始執行
app工程的preDebugBuild任務開始執行
app工程的checkDebugManifest任務開始執行
app工程的preReleaseBuild任務開始執行
app工程的prepareComAndroidSupportAnimatedVectorDrawable2600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportAppcompatV72600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportConstraintConstraintLayout102Library任務開始執行
app工程的prepareComAndroidSupportSupportCompat2600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportSupportCoreUi2600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportSupportCoreUtils2600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportSupportFragment2600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportSupportMediaCompat2600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportSupportV42600Alpha1Library任務開始執行
app工程的prepareComAndroidSupportSupportVectorDrawable2600Alpha1Library任務開始執行
app工程的prepareDebugDependencies任務開始執行
app工程的compileDebugAidl任務開始執行
app工程的compileDebugRenderscript任務開始執行
app工程的generateDebugBuildConfig任務開始執行
app工程的generateDebugResValues任務開始執行
app工程的generateDebugResources任務開始執行
app工程的mergeDebugResources任務開始執行
app工程的processDebugManifest任務開始執行
app工程的processDebugResources任務開始執行
app工程的generateDebugSources任務開始執行
app工程的incrementalDebugJavaCompilationSafeguard任務開始執行
app工程的javaPreCompileDebug任務開始執行
app工程的compileDebugJavaWithJavac任務開始執行
app工程的compileDebugNdk任務開始執行
app工程的compileDebugSources任務開始執行
app工程的mergeDebugShaders任務開始執行
app工程的compileDebugShaders任務開始執行
app工程的generateDebugAssets任務開始執行
app工程的mergeDebugAssets任務開始執行
app工程的transformClassesWithDexForDebug任務開始執行
app工程的mergeDebugJniLibFolders任務開始執行
app工程的transformNativeLibsWithMergeJniLibsForDebug任務開始執行
app工程的transformNativeLibsWithStripDebugSymbolForDebug任務開始執行
app工程的processDebugJavaRes任務開始執行
app工程的transformResourcesWithMergeJavaResForDebug任務開始執行
app工程的validateSigningDebug任務開始執行
app工程的packageDebug任務開始執行
app工程的assembleDebug任務開始執行
複製程式碼

這是執行assembleDebug列印的所有任務,當然你要執行assembleRelease任務肯定是一致的。 那接下來介紹一下常用的幾個任務:

  • mergeDebugResources 任務的作用是將所有依賴的aar或library module中的資源合併到app/build/intermediates/res/merged/debug目錄裡

  • processDebugManifest任務是將所有依賴的aar或library module中AndroidManifest.xml中的節點,合併到專案的AndroidManifest.xml中

  • processDebugResources的作用是呼叫aapt生成專案和所有aar依賴的R.java,同時生成資源索引檔案,把符號表輸出到app/build/intermediates/symbols/debug/R.txt

  • compileDebugJavaWithJavac這個任務是用來把java檔案編譯成class檔案,輸出的路徑是app/build/intermediates/classes/debug 編譯的輸入目錄有

  • transformClassesWithJarMergingForDebug的作用是把compileDebugJavaWithJavac任務的輸出app/build/intermediates/classes/debug,和app/build/intermediates/exploded-aar中所有的classes.jar和libs裡的jar包作為輸入,合併起來輸出到app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar,我們在開發中依賴第三方庫的時候有時候報duplicate entry:xxx 的錯誤,就是因為在合併的過程中在不同jar包裡發現了相同路徑的類

  • transformClassesWithDexForDebug這個任務的作用是把包含所有class檔案的jar包轉換為dex,class檔案越多轉換的越慢

使用android提供的外掛

如果想在自己的外掛中產生干預android編譯的行為,肯定要依賴android的gradle外掛。這裡要說明兩種情況:

  • 如果使用buildSrc(不明白是什麼的,請檢視前兩篇文章)的方式,不需要做額外的依賴。
  • 如果是新建java Library的形式需要新增依賴:
dependencies {
    compile gradleApi()
    compile localGroovy()
    compile 'com.android.tools.build:gradle:2.3.3'
}
複製程式碼

看看原始碼

上一篇文章說到了可以通過android的額外屬性名訪問Project的android屬性,從而訪問android的編譯。 我們今天會通過原始碼看一下,android到底是如何實現的。 首先是AppPlugin.java檔案(即外掛的主檔案),裡面有這麼一行程式碼:

project.getExtensions()
                .create(
                        "android",
                        AppExtension.class,
                        project,
                        instantiator,
                        androidBuilder,
                        sdkHandler,
                        buildTypeContainer,
                        productFlavorContainer,
                        signingConfigContainer,
                        extraModelInfo);
複製程式碼

可以看出AppExtension就是android擴充屬性的檔案,這裡要說明的是如果是編譯Library,使用的就是LibraryPlugin外掛,對應的擴充檔案為LibraryExtension。好了我們還是繼續說兩個檔案對比一下不難發現,兩個檔案都是差不多的,都是繼承TestedExtension,只是屬性變數不太一樣。今天我們還是隻說AppExtension。 在這個檔案中有一個這樣的陣列ApplicationVariant:

Gradle外掛學習筆記(三)
接著再看看ApplicationVariant是什麼,ApplicationVariant只是一個介面,同時有繼承了別的類,然後別的類有分別繼承了別的類,這裡的繼承關係有些複雜,我畫一個圖可能比較好說明。
Gradle外掛學習筆記(三)

圖有些大了,但是應該可以看明白BaseVariant中的方法比較多,我省略了一下,省略的部分主要是獲取上一節列印的Task的方法。暫時應該不會用到。 這裡有個重要的方法BaseVariantOutput,我們常用的替換佔位符(多渠道打包等)都會用到,這裡做個簡單的介紹:

Gradle外掛學習筆記(三)

以上介紹的方法和類都很重要,想更深瞭解的朋友不妨仿照下面的demo,多列印些log看看輸出的內容都是什麼,

寫個小demo

如果上面的圖認真的看一下,就可以瞭解variant中包含了android編譯的主要功能引數,所以要下手,只能從這裡下手了。 在這裡我們先舉個簡單的小例子(在之前的demo中做個修改,不熟悉的朋友請參考前兩章):

 void apply(Project project) {
        project.extensions.create("deep", MyExtension)
        project.afterEvaluate {
            MyExtension extension = project['deep'];
            String a = extension.aaa
            String b = extension.bbb
            println("deep:${a},${b}")
            project.tasks.getByName("preDebugBuild") {
                it.doFirst {
                    project.android.applicationVariants.all { variant ->
                        variant.outputs.each { output ->
                            output.processManifest.doLast {
                                def manifestFile = "${project.getProjectDir().absolutePath}/build/intermediates/manifests/full/${variant.dirName}/AndroidManifest.xml"
                                def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll("vvvvv", "ccccc")
                                new File(manifestFile).write(updatedContent, 'UTF-8')
                            }
                        }
                    }
                }
            }
        }
    }
複製程式碼

這段程式碼的作用很明顯是修改AndroidManifest中的字串vvvvv,改成ccccc。說白了作用就是替換字串,跟替換佔位符是一個作用。看看上面有個重要的寫法:

output ->
                            output.processManifest.doLast {}
複製程式碼

這是什麼意思呢,根據上面我們介紹的原始碼,可以知道這個就是在processManifest Task之後再去執行,再由上面介紹到的內容processManifest是合併AndroidManifest的任務,在合併之後,馬上去修改AndroidManifest檔案,保證了佔位符的替換。 再修改一下這個檔案,我們們看看output的file是什麼:

project.tasks.getByName("preDebugBuild") {
                it.doFirst {
                    project.android.applicationVariants.all { variant ->
                        variant.outputs.each { output ->
                            println("out put="+output.getOutputFile())
                        }
                    }
                }
            }
複製程式碼

看看輸出:

Gradle外掛學習筆記(三)
有時候我們可能需要修改一下apk的名字,根據上面的原始碼,我們找到了一個方法就是setOutputFile,來我們在打打log,看一下:

  project.tasks.getByName("preDebugBuild") {
                it.doFirst {
                    project.android.applicationVariants.all { variant ->
                        variant.outputs.each { output ->
                                println("getDirName:"+output.getDirName())
                                output.setOutputFile(new File("${project.getProjectDir().absolutePath}/build/${output.getName()}_aaa.apk"))
                                println("out put=" + output.getOutputFile())
                        }
                    }
                }
            }
複製程式碼

看看結果:

Gradle外掛學習筆記(三)
還有生成的apk:
Gradle外掛學習筆記(三)

總結

這次介紹的內容比較多,要想多掌握,可能需要多除錯幾次,多打打log才能全部掌握,喜歡的使用者可以根據我上面提到的方法,多試幾次。 接下來的文章可能會側重介紹groovy的語法,因為有使用者微信公眾號給我留言說寫的程式碼,看著有些彆扭。還有可能會介紹一些其他gradle的外掛,比如上面提到的列印任務日誌等等。 喜歡的朋友可以關注我的公眾號,第一時間看到文章:

Gradle外掛學習筆記(三)

相關文章