作者:李金濤 團隊:騰訊移動品質中心TMQ
筆者語:Gradle是一個類似于Ant和Maven的自動化建構工具,是Android App天然的建構工具。
本文總結了項目從Ant遷移到Gradle的實踐經驗和相關技巧,供大家參考。
由于Gradle的種種優點(大家可以參考網上的資料,這裡不多說了),前一段時間項目組打算将原來的Ant編譯打包方式遷移到Gradle編譯打包方式。現在遷移基本完成,我這裡将遷移過程遇到的坑以及經驗做一個總結,希望能給大家在Ant轉Gradle的時候帶來一些提示。
這裡,我們先來看一下Ant腳本的一個片段:

這段腳本執行的流程是:
上述編譯打包關鍵任務的說明:
(1)compile:編譯工程代碼;
(2)genMainDexFilelist:生成主dex的類清單;
(3)genSecondDexFilelist:生成從dex的類清單;
(4)package_res_with_assets:打包資源檔案;
(5)package:将代碼和資源一起打包成apk。
從Ant腳本和流程可以看出,Ant的任務都是直接在腳本中實作,然後按照腳本定義的執行順序來依次執行任務。
這裡先給出一個Gradle腳本的最簡單的模版,如下:
這個最簡單模版即可完成apk的編譯、打包、簽名等過程。它是怎麼做到的呢?
原來,在Gradle中,官方已經為我們定義好了一個專門編譯打包App的Gradle插件,該插件包含了Apk編譯打包的基本任務,我們就不用自己再費力去重寫了,可以直接複用。這個添加的插件就是上面Gradle腳本的第一行:
apply plugin: 'com.android.application'
在該插件中,預設的編譯打包流程如下:
(1)compileReleaseJavaWithJavac、compileReleaseSources:主要完成了Java代碼的編譯,生成class檔案。
(2)transformClassesAndResourcesWithProguardForRelease:主要完成了Java代碼的混淆,生成混淆後的jar包和mapping檔案。
(3)transformClassesWithDexForRelease:主要完成了将class檔案和第三方庫一起打包生成Dex位元組碼檔案。
(4)packageRelease:主要完成了将Dex位元組碼檔案和其他資源檔案一起打包。
在這個插件中,代碼編譯、打包等基本任務已經有了,但是我們還有一部分自定義的任務怎麼辦呢?隻能從Ant移植過去!
因為打包方式從Ant移植到Gradle後,最重要的是保證打包的功能和最終效果保持不變,做到平滑的移植。是以,這裡我們就應該平滑的将Ant任務改造成Gradle任務,然後移植到Gradle腳本中。
這裡Gradle跟Ant有一個很明顯的差別就是,Ant的任務基本上都是自定義的,代碼直接可見,是以我們想要添加、插入、删除任務都比較友善。但是Gradle的基本任務都在插件中,代碼不可見,那麼我們自定義任務以後該怎麼跟插件的任務融合在一起呢?
接下來我們具體說明。
下面就以dex分包過程中生成從dex的類清單為例,來說明如何将Ant中自定義的任務移植到Gradle。
Ant任務代碼示例:
這是一個shell腳本任務,目的是分包時生成從dex的類清單。
将Ant任務改造成Gradle任務時,為了平滑改造以及減少改造的工作量,我們仍然采用這個shell腳本。由于Gradle的方式也可以直接調用shell腳本,是以我們的Gradle任務定義如下:
以上兩種方式均可達到同樣的效果。
上述代碼中的main_dex_filelist、tempDir、binDir是該task中用到的自定義局部變量,它們可以跟單獨的task就近定義,也可以多個任務一起集中定義。
任務定義好了,放在Gradle腳本的什麼位置呢?直接放在腳本檔案後面就好,跟android定義塊分開。具體例子如下:
Gradle的任務定義好以後,我們還需要将它加入到Gradle的編譯打包流程中才可以被執行。
正如前面所說,由于Gradle的App編譯打包插件已經有一個基本的、完整的流程,我們自定義的任務必須插入到這個流程中合适的位置,這一步也稱作任務的注入。
任務注入的方法是利用Gradle任務之間的依賴關系。
比如,我們這個任務genSecondDexFilelist需要放在代碼編譯之後、apk打包之前,怎麼做呢?可以放在afterEvaluate塊中注入。具體代碼如下:
下面對這段代碼進行解釋:
(1)afterEvaluate說明是在Gradle腳本解析完之後、task開始執行之前插入任務;
(2)第一個dependsOn是使“genSecondDexFilelist”任務依賴于“transformClassesAndResourcesWithProguardForRelease”所有的依賴;第二個dependsOn是使“transformClassesAndResourcesWithProguardForRelease”依賴于“genSecondDexFilelist”。這樣一來,就将“genSecondDexFilelist”置于“transformClassesAndResourcesWithProguardForRelease”之前了。
在完成genSecondDexFilelist任務的注入以後,我們的Gradle腳本變成如下的樣子:
這樣,一個Ant任務就改造成Gradle任務并注入完成了。
其他的Ant任務可以同樣移植過來。
1、dex分包
Gradle自帶dex分包插件,但是缺點是定制比較麻煩。我們在Ant下已經有現成的自己定制的dex分包腳本和相關配置,如果能直接在Gradle中使用,那就好了。怎麼做呢?
根據上面自定義任務和插入任務的做法,我們隻需将Ant下已有的分包任務改寫成Gradle任務,已有的shell腳本照搬過來,然後再把任務注入到Gradle插件的編譯打包流程中即可。
前面已經示範了如何把生成從dex類清單的任務改造、注入Gradle任務流程中,其他任務可用類似的方法來實作移植。
2、代碼混淆
代碼混淆在我們的移植過程中也是一個坑。
Gradle也自帶代碼混淆插件,但是它預設的混淆跟我們Ant下混淆出來的結果很不相同。要想讓Gradle下的混淆跟我們Ant下的混淆完全一樣,則需要重寫混淆配置檔案和調試,這個過程比較麻煩。如果能把我們在Ant下已有的混淆配置拿過來直接用,那肯定是最好的。怎麼做呢?方法就是棄用Gradle自帶的混淆任務,使用我們自定義的混淆任務。
棄用Gradle混淆任務的方法是在Gradle腳本的buildTypes中設定minifyEnabled為false,然後自定義混淆任務并注入到編譯打包流程的适當位置。
自定義混淆任務時,混淆的配置可以放在一個配置檔案中,然後在任務中引用;也可以直接放在任務體的代碼中。這兩種形式展現在代碼上有所不同,具體舉例如下:
第一種形式:混淆配置放在配置檔案中
第二種形式:混淆配置直接放在任務體中
這兩種形式本質上是一樣的,但是它們還是有些使用上的不同:
第一種形式的優點是Proguard的配置都放在一個獨立的配置檔案中,可以減少Gradle腳本的代碼量,代碼看起來更清爽。不過,它有一個缺點就是不好傳入和使用Gradle腳本中定義的通用變量,這在某些情況下可能不太友善(比如,Proguard使用的jar包路徑可能包含編譯時的環境變量,我們就無法在配置檔案中使用該環境變量)。
第二種形式的優缺點正好跟第一種形式相反。
我們在使用的時候可以根據情況來選擇使用哪種形式。
以上講述了我們從Ant到Gradle的移植方法和案例。無論是Ant腳本還是Gradle腳本,其中關鍵的地方還是在于如何定義任務、如何讓任務做正确的事,這才是真正考驗我們代碼能力的地方。
歡迎大家一起讨論交流!
擷取更多測試幹貨,請搜尋微信公衆号:騰訊移動品質中心TMQ!