Android中Gradle實用指南
Gradle讓Android中的依賴管理、庫管理、渠道管理以及一些動態地編譯配置變得極為方便!!
本文是對Gradle在Android專案中進階使用的知識點整理和簡要講解
較為詳細的Gradle教科書Github
Gradle指令碼配置文件:Google Github
目錄
- Android工程中的Gradle
- 常用的Gradle Task
- 動態引數配置
- Project:build.gradle
- 全域性屬性配置
- 整體結構和描述
- Module: build.gradle
- defaucltConfig
- signConfigs
- buidlTypes
- sourceSets
- productFlavors
- compileOptions
- lintOptions
- build.gradle 圖形化配置
- dependencies專案依賴
- 自定義方法def
- Gradle編譯提速優化
- 效能檢測
- 禁用Task達到提速
- AAPT
- Gradle編譯優化
- 認識Task
- 自定義Plugin
- build.gradle中直接定義
- 當前工程中定義
- 獨立Module自定義外掛
- 最後
Android工程中的Gradle
下面簡述對我們工程最重要的幾個Gradle檔案,後續也會圍繞他們進行詳細講解和補充
(請仔細看程式碼中的註釋哈)
- 工程Project 中的
build.gradle
: 工程控制Gradle編譯配置 - 模組module中的
build.gradle
: 控制每個Module的編譯過程 gradle.properties
: gradle動態引數的配置檔案local.properties
: 本地的配置,如:SDK位置gradle-wrapper.properties
:gradle本地代理,宣告瞭指向目錄和版本distributionUrl
: 指定gradle版本不存在時,就從Value的地址中去下載。很多時候,我們只要版本換成我們本地存在的gradle版本就可以了
settings.gradle
: 配置Gradle中的Module管理常用Gradle Task
~
表示 gradlew
gradlew是包裝器,自動下載包裝裡定義好的gradle 版本,保證編譯環境統一
gradle 是用本地的gradlegradlew task -all
: 羅列出所有Task ,同時攜帶具體作用和相互關係gradlew assembleDebug
: 匯出所有渠道測試包~ assembleRelease
: 匯出所有渠道正式包~ assembleBaiduDebug --stacktrace
: 匯出指定渠道測試包,同時攜帶異常資訊
~ --stop
: 立即停止編譯~ check
: 檢查任務~ build
: 執行了 check和assemble~ clean
: 清除所有中間編譯結果
動態引數配置
在Gradle中動態配置資源引數
我們可以根據各自的需求在不同的領域(如:buildType 的debug, defaultConfig ...)下去動態替換或配置我們專案中所使用到的資源,如 log 開關, 針對不同渠道的對應內容欄位,不同版本定義引入的不同值等等
首先說清一點,對於動態資源在build.gradle
中多個領域中的使用,會遵循一下順序來進行覆蓋:
buildType > productFlavor > defaultConfig > Manifest中的配置 > 依賴的第三方庫的配置 > 任意領域中的預設值(也就是沒有設定值)
Manifest
佔位符: 可以動態配置Manifest的引數
在Manifest的Application節點下
//這裡以友盟為例
<!-- 友盟統計相關meta-data -->
<meta-data
android:name="UMENG_APPKEY"
android:value="balabalabala" />
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
在build.gradle中對引數進行動態配置
productFlavors {
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
}複製程式碼
gradle.properties
的使用- 系統屬性:
配置:systemProp.proName=123
使用:System.properties['proName']
- 自定義屬性 :
配置: 有key:ray.proName=123
,無key :proName = 123
使用: 有key:project。property('proName')
, 無key :proName
- 系統屬性:
gradle.properties中的配置
#使用系統引數配置
systemProp.keyStore=ray.jks
#使用key/value鍵值對配置
ray.keyPassword=123456
#屬性配置
mKeyAlias=ray
build.gradle中的使用
//簽名打包
release {
//簽名檔案所在路徑
storeFile file(System.properties['keyStore'])
//簽名密碼
storePassword "111111"
//別名
keyAlias mKeyAlias
keyPassword project.property('ray.keyPassword')
}複製程式碼
BuildConfig
檔案- 在
app/build/generated/source/buildConfig
資料夾下面 - 使用:
buildConfigField "String" , "key" , "\"value\""
配置buildConfig
中的屬性引數String
: 引數型別(int,boolean...),key
: 屬性的名字,value
: 屬性的值,\
為轉義字元
- 在
resValue
動態修改工程資源- 和buildConfig 類似,
resValue("string","key","value")
- 其中
string
表示 會在app/build/generated/res/resValue/.../generated.xml
中生成對應的String 的 key 和Value, 程式碼中可以直接getResources().getString(R.string.key);
獲取到value - 注意: 因為Gradle編譯的時候會將指令碼配置和string.xml檔案中配置進行merge,所以string.xml中已經存在的key在編譯前要刪除,否則會報錯
- 和buildConfig 類似,
//下面模擬在不同渠道下修改資源引數
productFlavors{
baidu{
buildConfigField "String" , "productCode" , "\"baidu 1.0\""
resValue("string","productName","baidu")
}
}複製程式碼
Project : build.gradle
全域性屬性配置
- 在project :
build.gradle
的buildscript
中宣告ext
和自定義屬性,然後在其他module中就可以直接使用這個屬性了
如下:
Project : build.gradle
buildscript {
//自定義工程使用的屬性
ext {
kotlin_version = '1.1.0'
compile_version = 25
}
//宣告依賴Android Gradle 外掛版本
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Module : build.gradle 中使用
Android
{
compileSdkVersion compile_version
}
dependencies
{
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}複製程式碼
- 新建自己的Gradle屬性檔案
text.gradle
, 然後在Project
中引入,最後就可以在其他的module中直接使用了,具體應用可以參考印象筆記Github
如下:
text.gradle 編寫如下:
ext {
kotlinVersion = "1.1.0"
rxjavaLibVersion = "1.2.0"
dependencies = [
// Rx
rxJava: "io.reactivex:rxjava:$rxjavaLibVersion"
]
}
Project : build.gradle 中引入
//就是引入他的相對根目錄路徑
apply from: 'config/text.gradle'
Module : build.gradle 中使用
rootProject.ext.XXXXX
dependencies {
def dependencies=rootProject.ext.dependencies
compile dependencies.rxJava
compile "org.jetbrains.kotlin:kotlin-stdlib:$rootProject.ext.kotlinVersion"
}複製程式碼
整體結構與描述
//宣告引入的引數配置檔案
apply from: 'config/dependencies.gradle'
apply from: 'config/text.gradle'
//編譯配置
buildscript {
//自定義引數
ext {
kotlin_version = '1.1.0'
compile_version = 25
}
//Gradle指定使用jcenter程式碼倉庫
repositories {
jcenter()
}
//宣告依賴Android Gradle 外掛版本
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
//這裡可以為專案整體配置屬性
allprojects{
repositories {
jcenter()
}
}
//任務:每次構建的時候刪除指定目錄
task clean(type: Delete) {
delete rootProject.buildDir
}複製程式碼
Module : build.gradle
控制每個Module的編譯過程以及具體的引數配置
defaultConfig
基本配置資訊
//預設配置
defaultConfig {
//包名
applicationId "com.rayhahah.gradledemo"
//最低版本
minSdkVersion 19
//目標版本
targetSdkVersion 25
//版本程式碼
versionCode getVersinCode()
//版本
versionName "1.0"
//自動化測試
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resValue "int","test","1"
}複製程式碼
signConfigs
簽名配置資訊
signingConfigs {
//debug模式簽名檔案
debug {}
//簽名打包
release {
//簽名檔案所在路徑
storeFile file("ray.jks")
//簽名密碼
storePassword "111111"
//別名
keyAlias "rayhahah"
keyPassword "111111"
}
//自定義簽名配置
ray{
//和上面的屬性一致,根據個人需求實現不同配置
}
}複製程式碼
buildTypes
編譯型別 : 指定編譯不同型別情況下的不同配置資訊
這裡是列舉了部分屬性和方法,全部的方法和屬性請看官網文件
//構建配置
buildTypes {
release {
//是否啟用資源優化
minifyEnabled
//啟用捨棄無用資源,只有當開啟混淆才能夠啟用
shrinkResources false
//指定混淆檔案
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//指定我們release包的輸出檔名就是我們的渠道名字
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith(".apk")) {
def fileName = "${variant.productFlavors[0].name}" + ".apk"
output.outputFile = new File(outputFile.parent, fileName);
}
}
}
}
debug {
}
//繼承
// rayhahah.initWith(debug)
//自定義buildType
rayhahah {
//指定簽名配置檔案
signingConfig signingConfigs.debug
//包名增加字尾
applicationIdSuffix ".ray"
}
}複製程式碼
sourceSets
配置資源邏輯組
- 通過指定資源 資料夾路徑構建自己的工程目錄
指定Android所需要資料夾所在具體路徑
sourceSets {
//這樣的配置適用於將Eclipse中的專案結構遷移到AndroidStudio中
main {
//指定src資源目標目錄
java.srcDirs = ['src']
//指定asset的目標目錄
assets.srcDirs = ['assets']
//指定res的目標目錄
res.srcDirs = ['res']
//指定依賴C檔案的目標目錄
jni.srcDirs = ['jni']
//指定依賴so檔案的目標目錄
jniLibs.srcDirs = ['libs']
//指定Manifest的目標檔案路徑
manifest.srcFile 'AndroidManifest.xml'
}
}複製程式碼
- 常用! 給自己的layout分包管理!!
- 可以參考博文
1. 在res下新建資料夾 layouts(其實叫什麼都無所謂)
2. 然後在 layouts下 新建你要分的包 如: activity,fragment 或按照業務模組來分
3. 在分包內新建Android resource directory -> layout 不要改名字
4. 在module:build gradle 如下配置
5. 然後將以前的layout檔案拷貝到對應分包的layout下就可以使用了
PS:只有在Project目錄才能看到,Android目錄結構是看不到的
sourceSets {
main {
res.srcDirs = [
'src/main/res/layouts/activity',
'src/main/res/layouts/fragment',
'src/main/res/layouts',
'src/main/res'
]
}
}複製程式碼
productFlavors
打包渠道配置資訊(仔細看程式碼註釋)
//多渠道打包配置
//利用Manifest佔位符動態引數配置
productFlavors {
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
wandoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
}
googleplayer {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "googleplayer"]
}
//不想每一個都去配置渠道名稱也可以用下面這個函式
//這個函式可以將 Manifest中的佔位符替換成 每個渠道的名字
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}複製程式碼
compileOptions
//編譯配置項
//主要配置Java編譯版本
compileOptions {
sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8
targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8
}複製程式碼
lintOptions
//lint配置項
lintOptions {
//啟用出錯停止grgradle構建
abortOnError false
// true--檢查所有問題點,包含其他預設關閉項
checkAllWarnings true
// 關閉指定問題檢查
disable 'TypographyFractions','TypographyQuotes'
// 開啟指定問題檢查
enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
// 僅檢查指定問題
check 'NewApi', 'InlinedApi'
// true--生成HTML報告(帶問題解釋,原始碼位置,等)
htmlReport true
// html報告可選路徑(構建器預設是lint-results.html )
htmlOutput file("lint-report.html")
// 忽略指定問題的規則(同關閉檢查)
ignore 'TypographyQuotes'
}複製程式碼
build.gradle 圖形化配置
AndroidStuido 給我們提供了十分友好地對於Build.gradle 圖形化配置的介面,使用如下:
- File -> Project Structure -> 選擇需要配置的 Module —> 選擇需要配置的領域
如圖十分直觀:
dependencies 專案依賴
- build.gradle中dependencies中的配置解析
//dependencies : 當前Android Module構建過程中所依賴的所有庫
dependencies {
//依賴指定目錄下所有指定字尾的檔案
compile fileTree(dir: 'libs', include: ['*.jar'])
//測試工具依賴
testCompile 'junit:junit:4.12'
//遠端庫的依賴 (當從遠端庫中下載一次過後,就會快取到本地了)
//預設遠端庫配置為 jcenter()
compile 'com.android.support:appcompat-v7:25.2.0'
//依賴指定檔案(這裡依賴的是jar包)
compile file('libs/test-1.0.0.jar')
//依賴本地專案庫
compile project(':testLibrary')
//格式: groupId: com.squareup.retrofit2
// artifactId : retrofit
// version: 2.1.0
// SNAPSHOT : 表示依賴 retrofit 及其依賴的所有專案,如果他所依賴的專案在本專案中重複出現依賴,則只依賴retrofit專案中的。
// @aar : 表示只依賴retrofit,不依賴他所依賴的專案
compile ('com.squareup.retrofit2:retrofit:2.1.0-SNAPSHOT@aar')
{
//強制重新整理遠端庫,避免遠端庫重新整理,本地未更新
transitive = true
//exclude : 單獨去除okhttp3的依賴
exclude module : 'com.squareup.okhttp3:okhttp:3.3.0'
}
}複製程式碼
so庫依賴
- 在
src/main
目錄下建立jniLibs
,然後將so檔案拷貝進去就可以了 - 當然也可以通過
sourceSet
指定jniLib
的目標目錄來自定義管理依賴的so檔案存放
- 在
本地Module依賴
- 在
build.gradle
的dependencies
領域中新增compile project(':testLibrary')
- 在
setting.gradle
中新增module到include
中 如:include ':app',':testLibrary'
- 在
方法def定義
在Gradle中你可以寫方法供 配置資訊動態呼叫
//自定義函式
def getVersinCode() {
// ......
}
Android{
defaultConfig{
versionCode getVersinCode()
}
}複製程式碼
Gradle編譯提速優化
檢測
gradlew build -profile
: 編譯工程同時生成編譯效能分析檔案,在根目錄build/reports/profile/profile-xxxx.xxx....html
,通過瀏覽器開啟以後
如圖: 我們需要優化的就是Task Execution
- 往下拉,可以看到TaskExcution的詳細引數
可以看到lint耗時最多,然後我們就可以根據自己專案中的具體情況來做優化
禁用Task達到提速
- 根據上面的耗時來對應禁用Task來達到提速的效果
- 在
Project:build.gradle
中的buildScript
中動態配置編譯時禁用即可, 程式碼:gradle.startParameter.excludedTaskNames.add('lint')
就可以實現禁用了,具體需要繼續禁用的可以根據專案輸出的編譯分析檔案來作出新增和調整
AAPT
aapt即Android Asset Packaging Tool,在SDK的build-tools目錄下。該工具可以檢視,建立, 更新ZIP格式的文件附件(zip, jar, apk)。也可將資原始檔編譯成二進位制檔案,儘管你可能沒有直接使用過aapt工具,但是build scripts和IDE外掛會使用這個工具打包apk檔案構成一個Android 應用程式(百度百科)
- 在Debug模式下,我們需要優化AAPT來大量提速我們的編譯(記得在release下改回來)
aaptOtions{
cruncherEnabled = false
}複製程式碼
Gradle編譯優化
- 提升Gradle本身編譯速度
gradle.properties
中配置
//開啟守護執行緒支援
org.gradle.daemon=true
//開啟並行編譯
org.gradle.parallel=true
//按需編譯
org.gradle.configureondemand=true
//手動配置Gradle編譯時記憶體分配
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# 開啟JNI編譯支援過時API
android.useDeprecatedNdk=true複製程式碼
build.gradle
中配置
//增加編譯記憶體到4g
dexOptions{
incremental true
javaMaxHeapSize "4g"
}複製程式碼
認識Task
- Task基本使用:
task testTask << {
println 'testTask << print'
// 表示在task最前面來執行的過程
doFirst {
println 'testTask do first'}
// << 和 doLast表示對當前task追加執行過程,效果是一樣的
doLast{
println 'testTask do last!'}
}
//task之間的依賴 dependsOn
//當執行存在依賴的task時,會先執行他的父類也就是依賴目標
task testDependsOn(dependsOn:testTask){
println 'testDependsOn default print '
}
//或者
testDependsOn.dependsOn testTask
//當執行testDependsOn是 列印順序: testDependsOn default print -> testTask do first -> testTask << print -> testTask do last!
//順序總結為:
//1.不加doLast和doFirst的最先執行
//2.依賴task優先順序高於自己的doFirst和doLast
//3.同一個task中的doLast按從上向下順序執行
//4.同一個task中的doFirst按從下到上倒序執行
//5.同一個task的doFirst優先順序高於doLast
//顯示宣告型別為Copy, 不宣告預設為defaultTask
task testCopy(type : Copy){
//將當前gradle檔案從src目錄拷貝到dst目錄
from "src"
into "dst"
}
//每一個特定的Task型別還可以含有特定的Property,比如Copy的from和to等。
//自定義property
ext.testProperty = ""
task testExtProperty << {
//直接使用自定義的property
println testProperty
}複製程式碼
- 自定義Task:
//區域性自定義Task
//直接在build.gradle中自定義Task
//但是也只能在當前module中引用
class TestCustomTask extends DefaultTask {
//@Optional,表示在配置該Task時,message是可選的。
@Optional
String message = 'I am jjx'
//@TaskAction表示該Task要執行的動作,即在呼叫該Task時,hello()方法將被執行
@TaskAction
def hello() {
println "hello world $message"
}
}
//hello使用了預設的message值
task hello(type: TestCustomTask)
//重新設定了message的值
task helloOne(type: TestCustomTask) {
message = "I am a android developer"
}
全域性自定義Task
如果需要自定義大量的Task,就要新建一個Gradle檔案來統一管理
通過apply來引入使用
//這是外掛
apply plugin: 'com.android.application'
//這裡gradle-quality.gradle就是另外單獨定義了task的gradle
apply from: '../build-config/gradle-quality.gradle'複製程式碼
自定義Plugin
自定義Plugin可以讓我們在工程編譯根據需求中自動去完成一些操作
下面就是一個編譯後自動列印當前時間的Plugin
build.gradle中直接定義
與自定義Task十分類似
可以在build.gradle中自定義plugin
apply plugin: DateAndTimePlugin
dateAndTime {
timeFormat = 'HH:mm:ss.SSS'
dateFormat = 'MM/dd/yyyy'
}
class DateAndTimePlugin implements Plugin<Project> {
//該介面定義了一個apply()方法,在該方法中,我們可以操作Project,
//比如向其中加入Task,定義額外的Property等。
void apply(Project project) {
//載入Extension
project.extensions.create("dateAndTime", DateAndTimePluginExtension)
//使用Extension配置資訊
project.task('showTime') << {
println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
}
project.tasks.create('showDate') << {
println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
}
}
}
//每個Gradle的Project都維護了一個ExtenionContainer,
//我們可以通過project.extentions進行訪問
//比如讀取額外的Property和定義額外的Property等。
//向Project中定義了一個名為dateAndTime的extension
//並向其中加入了2個Property,分別為timeFormat和dateFormat
class DateAndTimePluginExtension {
String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
String dateFormat = "yyyy-MM-dd"
}複製程式碼
工程中定義
其實本質上就是對上面的自定義Plugin結構化拆解
Plugin目錄建立
- 在根目錄下建立
buildSrc
- 子目錄結構如下:
buildSrc/src/main/groovy/com/ray
和buildSrc/src/main/resources/META-INF/gradle-plugins
- 在根目錄下建立
建立
buildSrc/build.gradle
, 配置如下
apply plugin:'groovy'
dependecies{
compile gradleApi()
compile localGroovy()
}複製程式碼
Plugin
邏輯實現
自定義Plugin的主要實現邏輯
在buildSrc/src/main/groovy/com/ray
下建立DateAndTimePlugin
class DateAndTimePlugin implements Plugin<Project> {
void apply(Project project) {
//載入Extension
project.extensions.create("dateAndTime", DateAndTimePluginExtension)
//使用Extension中的配置資訊
project.task('showTime') << {
println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
}
project.tasks.create('showDate') << {
println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
}
}
}複製程式碼
Extension
實現Extension
相當於Gradle配置資訊(相當於實體類),然後主專案的build.gradle
通過Extension
傳遞配置(相當於賦值)
同樣在buildSrc/src/main/groovy/com/ray
下建立DateAndTimePluginExtension
//每個Gradle的Project都維護了一個ExtenionContainer,
//我們可以通過project.extentions進行訪問
//比如讀取額外的Property和定義額外的Property等。
//向Project中定義了一個名為dateAndTime的extension
//並向其中加入了2個Property,分別為timeFormat和dateFormat
class DateAndTimePluginExtension {
String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
String dateFormat = "yyyy-MM-dd"
}複製程式碼
Plugin命名
自定義外部引用時Plugin的名字
在buildSrc/src/main/resources/META-INF/gradle-plugins
下建立timePlugin.properties
,內容只有一行程式碼 :implementation-class = com.ray.DateAndTimePlugin
Plugin的使用
主專案中apply plugin:'timePlugin'
可選:配置Extension
:
timePlugin{
//動態修改和配置Extension屬性
//這裡修改了日期格式
timeFormat = 'MM/dd/yyyy'
}複製程式碼
獨立Module自定義外掛
- 新建plugin目錄結構如圖:
buildSrc/build.gradle
的修改 如下:
apply plugin: 'groovy'
//增加Maven的支援
apply plugin: 'maven'
version = 1.0
group = 'com.ray.plugin'
archivesBaseName = 'timeplugin'
repositories.mavenCentral()
dependencies {
compile gradleApi()
groovy localGroovy()
}
//將外掛部署到repo目錄下
uploadArchives {
repositories.mavenDeployer {
repository(url: uri('../repo'))
}
}複製程式碼
- 釋出:和其他Module一樣釋出到中央庫中
- 使用外掛,主專案中配置如下:
apply plugin: 'timePlugin'
buildscript {
repositories {
maven {
url uri('../repo')
} }
dependencies {
classpath group: 'com.ray.plugin', name: 'timePlugin',
version: '1.0'
}
}複製程式碼
最後
以上就是總結的Gradle實用的進階指南,讓我們可以更加隨心所欲地去管理我們的工程。以後如果有一些新的認識或者想法,我也會在這裡實時更新的。也算是自己對Gradle認識和學習的總結整理吧。
正確使用Gradle的配置是為了讓我們開發更加便捷、效率更高,千萬不要本末倒置了。
文中哪裡有錯誤的話,歡迎大家指出糾正
如果文章對你有用的話,請點贊鼓勵一下哈O(∩_∩)O~~~~
參考和感謝以下博文和專案: