1. 程式人生 > >Android開發必備的Gradle知識

Android開發必備的Gradle知識

前言

本篇文章來自本人以前的學習筆記,本來筆記語言比較生硬,之前有同事問我關於Gradle的知識,對我的筆記評價還不錯。於是趁最近有時間對語言稍加潤色,發出來供大家參考。這篇筆記來自《巧用Gradle構建Android應用》(《Gradle Recipes for Android》)這本書(以前從學校圖書館借的書,怕忘記一些知識點,所以做了筆記)。

目錄
1. 基礎

Android Studio使用Gradle做構建、打包和部署。採用指令碼語言Groovy。

1.1 認識Gradle構建檔案
- 示例1-1 setting.gradle 檔案裡面
include ':app'


include語句表示app子目錄是唯一的子專案。如果你新增一個Android庫專案,其也會被新增到這個檔案。
- 示例1-2 build.gradle檔案頂層。

// Top-level build file where you can add configuration options common to all sub-projects/modules.
//你可以在構建檔案頂層新增所有子專案或模組通用的配置選項

buildscript {
    repositories {
        google()
        jcenter()
        maven { url 'https://maven.google.com'
} } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files //注意:不要把你的應用程式依賴放在這裡,它們屬於各獨立模組的build.gradle檔案 } } allprojects { repositories { jcenter() google() maven { credentials { username 'username'
password 'password' } url 'http://repo.mycompany.com/maven2' } } } task clean(type: Delete) { delete rootProject.buildDir }

Gradle本身預設不包含Android的功能,Google為Gradle提供了一個Android的外掛,其簡化了Android專案的配置。構建檔案頂層的buildscript塊告訴Gradle去哪下載這個外掛。
allprojects段頂層專案和所有子專案都預設使用 jcenter() 倉庫來下載所有的Java第三方依賴庫。
gradle允許你自定義你的task,並插入到有向無環圖(DAG)(例如task clean方法)。
- 示例1-3 app的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    buildToolsVersion "26.0.2"

    defaultConfig {
        applicationId "com.kousenit.myandroidapp"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:27.0.2'
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    testImplementation 'junit:junit:4.12'
}

fileTreey依賴意味著libs目錄下所有以.jar結尾的檔案都被新增到了編譯類路徑。
implementation 'com.android.support:appcompat-v7:27.0.2'意味著Gradle從Android支援的庫中新增版本為27.0.2的appcompat-v7 jar檔案。注意-v7意味著支援Android應用程式Android 7及以後版本,並不是庫本身的版本7這個庫被定義為編譯時依賴,因此其所有的類在整個專案都可用。
頂層Android構建檔案通過buildscript塊將Android的Gradle外掛新增到了你的專案中。模組的構建檔案通過“apply”應用這個外掛,因此將Android塊新增到Gradle的DSL(領域特定語言)中。Android專案使用com.android.application外掛。
1.2 認識SDK版本以及其他的預設值

compileSdkVersion 指定目標編譯環境
buildToolsVersion 指定構建工具版本
applicationId 應用程式的“package”名字
minSdkVersion 應用程式支援的最小Android SDK版本
targetSdkVersion 應用程式目標的Android版本
versionCode 數值代表你的app相對於其他的版本(應用升級時候用)
versionName 代表app發行版本,面向使用者的
compileOptions 這個app期望使用JDK1.7

1.3 執行Gradle的方法(
Linux(./gradlew),Windows(gradlew))
檢視gradle可以使用的任務:

E:\work\gradletest>gradlew task
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :tasks 

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.

Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleArro - Assembles all Arro builds.
assembleDebug - Assembles all Debug builds.
assembleFriendly - Assembles all Friendly builds.
assembleRelease - Assembles all Release builds.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
clean - Deletes the build directory.
cleanBuildCache - Deletes the build cache directory.
compileArroDebugAndroidTestSources
compileArroDebugSources
compileArroDebugUnitTestSources
compileArroReleaseSources
compileArroReleaseUnitTestSources
compileFriendlyDebugAndroidTestSources
compileFriendlyDebugSources
compileFriendlyDebugUnitTestSources
compileFriendlyReleaseSources
compileFriendlyReleaseUnitTestSources
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'gradletest'.
components - Displays the components produced by root project 'gradletest'. [incubating]
dependencies - Displays all dependencies declared in root project 'gradletest'.
dependencyInsight - Displays the insight into a specific dependency in root project 'gradletest'.
dependentComponents - Displays the dependent components of components in root project 'gradletest'. [incubating]
help - Displays a help message.
model - Displays the configuration model of root project 'gradletest'. [incubating]
projects - Displays the sub-projects of root project 'gradletest'.
properties - Displays the properties of root project 'gradletest'.
tasks - Displays the tasks runnable from root project 'gradletest' (some of the displayed tasks may belong to subprojects).

Install tasks
-------------
installArroDebug - Installs the DebugArro build.
installArroDebugAndroidTest - Installs the android (on device) tests for the ArroDebug build.
installArroRelease - Installs the ReleaseArro build.
installFriendlyDebug - Installs the DebugFriendly build.
installFriendlyDebugAndroidTest - Installs the android (on device) tests for the FriendlyDebug build.
installFriendlyRelease - Installs the ReleaseFriendly build.
uninstallAll - Uninstall all applications.
uninstallArroDebug - Uninstalls the DebugArro build.
uninstallArroDebugAndroidTest - Uninstalls the android (on device) tests for the ArroDebug build.
uninstallArroRelease - Uninstalls the ReleaseArro build.
uninstallFriendlyDebug - Uninstalls the DebugFriendly build.
uninstallFriendlyDebugAndroidTest - Uninstalls the android (on device) tests for the FriendlyDebug build.
uninstallFriendlyRelease - Uninstalls the ReleaseFriendly build.

Verification tasks
------------------
check - Runs all checks.
connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices.
connectedArroDebugAndroidTest - Installs and runs the tests for arroDebug on connected devices.
connectedCheck - Runs all device checks on currently connected devices.
connectedFriendlyDebugAndroidTest - Installs and runs the tests for friendlyDebug on connected devices.
deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers.
deviceCheck - Runs all device checks using Device Providers and Test Servers.
lint - Runs lint on all variants.
lintArroDebug - Runs lint on the ArroDebug build.
lintArroRelease - Runs lint on the ArroRelease build.
lintFriendlyDebug - Runs lint on the FriendlyDebug build.
lintFriendlyRelease - Runs lint on the FriendlyRelease build.
lintVitalArroRelease - Runs lint on just the fatal issues in the arroRelease build.
lintVitalFriendlyRelease - Runs lint on just the fatal issues in the friendlyRelease build.
test - Run unit tests for all variants.
testArroDebugUnitTest - Run unit tests for the arroDebug build.
testArroReleaseUnitTest - Run unit tests for the arroRelease build.
testFriendlyDebugUnitTest - Run unit tests for the friendlyDebug build.
testFriendlyReleaseUnitTest - Run unit tests for the friendlyRelease build.

To see all tasks and more detail, run gradlew tasks --all

To see more detail about a task, run gradlew help --task <task>


BUILD SUCCESSFUL in 26s
1 actionable task: 1 executed

1.4 新增Java庫的依賴
gradle依賴的完整語法testImplementation group:'junit',name:'junit',version:'4.12' 依賴的快捷語法testImplementation 'junit:junit:4.12'
版本號以變數形式(不推薦)testImplementation 'junit:junit:4.+'
gradle預設遵循傳遞依賴,如果你想針對一個特定的庫關掉這個功能,使用transitive標誌。

implementation ('com.squareup.retrofit2:retrofit:2.4.0'){
        transitive = false
    }

如果你想要一個模組的jar,不引入任何額外的依賴,你也可以像這樣指定

 implementation 'org.codehaus.groovy:groovy:3.0.0-alpha-3@jar'

你也可以從dependencies塊中排除一個傳遞依賴

implementation ('org.spockframework:spock-core:1.2-RC1-groovy-2.5'){
        exclude group: 'org.codehaus.groovy'
        exclude group: 'junit'
    }

1.5 配置倉庫
任何一個Maven倉庫都可以使用maven並且跟隨一個url塊來新增到預設的倉庫列表中去。

repositories {
        google()
        jcenter()
        maven{
            url 'http://repo.spring.io/milestone'
        }
    }

通過密碼保護的倉庫可以使用一個credentials 塊。

maven{
            credentials{
                username 'username'
                password 'password'
            }
            url 'http://repo.spring.io/milestone'
        }

如果你有本地檔案系統的檔案,你可以通過flatDir 語法將一個檔案目錄作為一個倉庫。

    repositories {
        google()
        jcenter()
        maven{
            credentials{
                username 'username'
                password 'password'
            }
            url 'http://repo.spring.io/milestone'
        }
        flatDir{
            dirs 'lib'
        }
    }

這是除了顯式的使用 filesfileTree 新增檔案到dependencies 塊的另一種方式。

2. 專案屬性

2.1 屬性設定
ext屬性(使用ext塊配置通用的值)—-“ext”代表 “extra”。例如:

ext {
     AAVersion = '1.1.2'
}
implementation "com.android.support.constraint:constraint-layout:$AAVersion"

如果你想要將實際的值挪出構建檔案(使用者名稱,密碼)
原始碼:

 credentials{
                username 'username'
                password 'password'
            }

可以把它新增到專案根目錄下的gradle.properties檔案:

login = 'user'
pass = '123456'

然後

 credentials{
                username login
                password pass
            }

你還可以通過命令列設定這些值,通過使用-P傳參給gradle。

gradlew -Plogin = me -Ppassword = password assembleDebug

使屬性動態變化:

ext{
    if(!project.hasProperty('login'){
        login = 'login_from_build_file'
    }
    if(!project.hasProperty('pass'){
        pass = 'pass_from_build_file'
    }
}

這些也可以通過命令列來設定,並且擁有最高優先順序。
2.2 在專案之間分享設定

 allprojects {
    repositories {
        jcenter()
    }
}

此段程式碼對於這個預設專案來說意味著頂層專案和app模組。
另一個做法是使用subproject塊,例如多個庫專案在自己的構建檔案中應用library外掛,可以通過在頂層應用這個外掛來消除重複。

subprojects{
    apply plugin: `com.android.library`
}

2.3 為釋出apk簽名
預設情況下,除錯用的祕鑰儲存庫在你的使用者目錄下的.Android子目錄下面。預設的名字叫做debug.keystore,並且其密碼是Android。

3. 處理構建型別

一個構建型別決定app如何被打包。Gradle的Android外掛預設支援兩種不同型別的構建:debug和release。

  buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

    }

其中,minifyEnabled代表在打包app時自動刪除無用的資源。如果設定成true,Gradle還會刪除無需用到的依賴庫。這隻會在shrinkResources屬性同時設定成true時生效。

  buildTypes {
        release {
        //開啟程式碼壓縮
            minifyEnabled true
            //開啟資源壓縮
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

    }

為了能夠在一臺機器上安裝多種構建型別,Android必須能夠分辨出它們的應用程式ID。applicationIDSuffix允許Gradle生成多個APK,每個都有自己的ID。

  buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config
        }
        debug{
            applicationIdSuffix '.debug'
            versionNameSuffix '-debug'
        }
    }

現在釋出版本和除錯版本的app都可以被部署到同一臺裝置上了。
1. 產品定製和變種
如果我們想要構建同一個應用程式,但是使用不同的資源或類。這時要進行產品定製。
使用Android閉包的productFlavors塊來宣告一個產品定製。
假設一個”Hello,World”風格的Android app,基於簡單的EditText輸入名字來歡迎使用者。你可以通過引入“friendly”,“arro”來給這個app一個態度。

    productFlavors{
        friendly{
            applicationId 'com.test.friendly'
        }
        arro{
            applicationId 'com.test.arro' +
                    ''
        }
    }

我們可以有多個定製。在這個例子中,每個定製擁有一個略微不同的applicationId,所以這些定製可以被安裝到同一個裝置上。
每個產品定製可以擁有其自己的值,如下面的屬性,還有一些其他的基於預設的defaultConfig的相同屬性。
- applicationId
- minSdkVersion
- targetSdkVersion
- versionCode
- versionName
- sigingConfig
每個定製定義了自己的原始碼集和資源,其與主程式碼集是兄弟關係。在我們的示例中,意味著除了app/src/main/java以外,你還可以新增原始檔在如下目錄中:
- app/src/arro/java
- app/src/friendly/java
你還可以新增額外的資原始檔到如下目錄:
- app/src/arro/res
- app/src/friendly/res/layout
- app/src/friendly/res/values
所有任何res的子目錄也是一樣的。同樣的資源目錄結構也可以應用到所有的定製。
為了部署一個特定的變種,Android Studio提供了一個Build Variant檢視。從下拉框中選擇合適的變種,並像平常一樣部署。
2. 合併資源
如果我們要在同一個Activity展示不同的文字,應該如何設定呢?其實所有的定製都有自己的資源目錄,在appres下。在這裡,一個叫做values的子目錄被新增進去了,一個叫做strings.xml的檔案拷貝從app/src/main/res/values拷貝進去了。arro的定製string.xml如下所示。

<resources>
    <string name="app_name">GradleTest</string>
    <string name="hello_world">I\'m
        Arro</string>
</resources>

通過合併專案定製和構建型別的res資料夾以及主要的目錄樹來合併資源。優先順序是:構建型別覆蓋產品定製,其覆蓋main程式碼集。
在我們的MainActivity展示一段文字

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

再看看我們Friendly定製的Strings.xml

<resources>
    <string name="app_name">GradleTest</string>
    <string name="hello_world">I\'m Friendly</string>
</resources>

執行之後就可以顯示不同定製的文字了。
3. 定製維度
一個產品定製還不夠,你需要另一個標準類區分你應用程式的不同版本。

//新增定製維度 attitude和client
 defaultConfig {
        applicationId "com.example.tangx.gradletest"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        flavorDimensions "attitude", "client"
//        flavorDimensions "versionCode"
    }
    productFlavors {
        arrogant {
            dimension 'attitude'
            applicationId 'com.kousenit.helloworld.arrg'
        }
        friendly {
            dimension 'attitude'
            applicationId 'com.kousenit.helloworld.frnd'
        }
        obsequious {
            dimension 'attitude'
            applicationId 'com.kousenit.helloworld.obsq'
        }
        stark {
            dimension 'client'
        }
        wayne {
            dimension 'client'
        }
    }

此處有兩個維度定製:attitude和client。arrogant、friendly和obsequious定製在attitude維度上的,stark和wayne是client型別的定製。
這個組合會生成很多變種。如(obsequious)下:
- obsequiousStarkDebug
- obsequiousStarkRelease
- obsequiousWayneDebug
- obsequiouswayneRelease
兩種構建型別,三種態度的定製和兩種客戶的定製,一共產生2 * 3 * 2=12不同的變種。
4. 合併不同定製間的java原始碼
如果想要為單獨的產品定製新增Android activities或者其他java類,可以用此方法。
雖然定製和構建型別中的字串和佈局資源與main原始碼集中的值會相應的覆蓋,java類卻又不同。如果main原始碼集中的程式碼引用了一個特定的類每個定製和構建型別可以有自己的實現,只要在main中沒有就行。

4. 使用Android庫

從Gradle的視角,Android庫就是根專案的子專案。意味著它們就像Android應用程式一樣,但是在一個子目錄中。被新增的模組的名字因此被新增到setting.gradle檔案中。
例如一個添加了模組的settings.gradle檔案
include ':app',':icndb'
在這個例子中,Android庫叫做icndb。
每個庫都有自己的Gradle構建檔案,其支援與根專案相同的設定。你可以指定最小和目標SDK版本、自定義構建型別、新增構建定製,並且修改依賴。重要的區別是Gradle構建使用了不同的外掛。

//使用library外掛
apply plugin: 'com.android.library'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    packagingOptions{
    //從多個依賴中移除衝突檔案
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/license.txt'
        exclude 'LICENSE.txt'
    }

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
}

在app編譯時使用icndb庫

dependencies {
    compile project(':icndb')
}

構建過程本身生成除錯和釋出版本的庫,在icndb/build/outputs/aar目錄下。

5. 提升Gradle構建效能
  1. Gradle守護程序
    Gradle守護程序實在構建之間也持續執行的後臺程序,同時快取資料和程式碼。為確保守護程序啟動了,在gradle properties中新增設定:
org.gradle.deamon = true

Gradle團隊強烈推薦你不要在持續整合伺服器上使用守護程序,在那裡穩定和重複構建比效能更有價值。
2. 並行編譯
Gradle有一個“incubating”選項來並行編譯獨立的專案。在gradle.properties檔案中新增如下配置:

org.gradle.parallel = true

注意這裡可能並沒有用,大多數Android專案的模組都是相互關聯的,使得並行編譯並不能帶來很多的好處。
3. 按需配置

org.gradle.configureondemand = true

大多數Android程式都只要很少數量的子專案,所以這個功能可能也不會很實用。
4. 修改JVM設定
最終Gradle構建會在一個Java程序中進行,所以影響JVM效能的因素也會影響Gradle的效能。如下演示Java虛擬機器的一個方便的設定。

rg.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

-Xmx標誌指定這個Java程序使用的最大記憶體數量。一個-Xmx標誌指示了最初為這個程序分配的記憶體數量。這個例子還修改了“永久代”的空間大小,並且當java.lang.OutOfMemoryError發生時,將堆中的情況匯出到一個檔案中。
5. 使用dex選項
Android塊允許你設定一個選項來控制轉換java二進位制程式碼(如.class檔案)為Dalvik可執行檔案(.dex檔案)的“dex”程序。Android塊內部的dexOptions塊包含了如下選項:

 dexOptions{
        javaMaxHeapSize '2g'
        jumboMode = true
        preDexLibraries = true
    }

使用javaMaxHeapSize作為dx執行時最大堆大小指定Xmx值得一種替代方式,增量為1024m。所以這裡設定為2g。
應用“jumbo模式”運載在dex檔案中出現大量的字串。如果這是個問題,你可能想要花更多的時間配置ProdGuard。
preDexLibraries會提前在庫上執行dex程序,正如其所聽起來的那樣。如文件所說,“這可以提高增量構建效能,但是單純的構建可能會更慢”
5. 剖析你的構建
你可以在命令列執行Gradle時加上–profile的選項來生成關於構建的有用資訊。結果會在build/reports/profile目錄下以HTML的形式被寫入。命令如下:

gradlew --profile assembleDebug

多個標籤將總結報告分成了幾個獨立的配置步驟,配置和執行。在這樣大小的專案中這裡看不到太多的東西,但是在大型專案中,這是一個很好的發現構建過程瓶頸的方式。
——“Gradle Recipes for Android by Ken Kousen”