Gradle 完整指南(Android)

本文轉載自 https://blog.csdn.net/qq_29825237/article/details/54017131html

前言

爲何須要學Gradle?

Gradle 是 Android 如今主流的編譯工具,雖然在Gradle 出現以前和以後都有對應更快的編譯工具出現,可是 Gradle 的優點就在於它是親兒子,Gradle 確實比較慢,這和它的編譯過程有關,可是如今的Gradle 編譯速度已經有了成倍提升。除此以外,相對其餘編譯工具,最重要的,他和 Android Studio 的關係很是緊密,能夠說對於一些簡單的程序咱們幾乎不須要任何代碼上的配置只使用 Android Studio 就能夠完成編譯和運行。java

可是對於一些比較複雜的,特別是多人團隊合做的項目咱們會須要一些個性化的配置來提升咱們的開發效率。好比咱們要自定義編譯出的apk包的名字、對於一些特殊產品咱們可能會要用同一個項目編譯出免費版付費版的apk。這些高級的功能都須要咱們對配置代碼進行自定義地修改。python

最近伴隨着 Android Studio2.0的發佈, Gradle 也進行了一次很是大的升級,叫Instant Run.它的編譯速度網上有人用逆天兩個字來形容。當咱們第一次點擊run、debug按鈕的時候,它運行時間和咱們往常同樣。可是接下去的時間裏,你每次修改代碼後點擊run、debug按鈕,對應的改變將迅速的部署到你正在運行的程序上,傳說速度快到你都來不及把注意力集中到手機屏幕上,它就已經作好相應的更改。可是剛出來的彷佛對一些項目的兼容性不太好,如今升級後不知道怎麼樣。android

爲何要了解命令行編譯?

在不少狀況下咱們都是使用的 Android Studio 來build、debug項目。Android Studio 能知足咱們開發的大多數需求,可是某些狀況下命令行可以讓咱們編譯的效率更高,過程更明朗,一些高級的配置也須要熟悉命令行纔可以使用,好比在服務器編譯,某些項目初始化的時候若是直接交給Android Studio ,它會一直Loading,你都不知道它在幹嗎,可是用命令行你就知道它卡在了哪一個環節,你只須要修改某些代碼,立刻就可以編譯過了。git

瞭解 Gradle 以後咱們能夠作什麼?

we can do everything what we want.編程

  • 自定義編譯輸出文件格式。
  • hook Android 編譯過程。
  • 配置和改善 Gradle 編譯速度

Gralde Overview

History

咱們知道,Android 的編譯過程很是複雜:windows

咱們須要一種工具幫咱們更快更方便更簡潔地完成 Android 程序的編譯。如今結合Android Studio 咱們通常使用的工具都是Gradle, 在 Gradle 出現之前Android 也有對應的編譯工具叫Ant,在Gradle 出現以後,也有新的編譯工具出現,就是FaceBook 的Buck工具。這些編譯工具在出現的時候幾乎都比 Gradle 要快,Gradle 之因此慢是跟它的編譯週期有很大關係。緩存

Gradle 的編譯週期

在解析 Gradle 的編譯過程以前咱們須要理解在 Gradle 中很是重要的兩個對象。ProjectTask安全

每一個項目的編譯至少有一個 Project,一個 build.gradle就表明一個project,每一個project裏面包含了多個task,task 裏面又包含不少actionaction是一個代碼塊,裏面包含了須要被執行的代碼。服務器

在編譯過程當中, Gradle 會根據 build 相關文件,聚合全部的projecttask,執行task 中的 action。由於build.gradle文件中的task很是多,先執行哪一個後執行那個須要一種邏輯來保證。這種邏輯就是依賴邏輯,幾乎全部的Task 都須要依賴其餘 task 來執行,沒有被依賴的task 會首先被執行。因此到最後全部的 Task 會構成一個有向無環圖(DAG Directed Acyclic Graph)數據結構

編譯過程分爲三個階段:

  • 初始化階段:建立 Project 對象,若是有多個build.gradle,也會建立多個project.
  • 配置階段:在這個階段,會執行全部的編譯腳本,同時還會建立project的全部的task,爲後一個階段作準備。
  • 執行階段:在這個階段,gradle 會根據傳入的參數決定如何執行這些task,真正action的執行代碼就在這裏.

謝絕轉載,非要轉載,請註明出處http://www.jianshu.com/p/9df3c3b6067a

剛剛咱們提到Gradle 編譯的時候的一些相關文件,下面咱們挨個解析一下這些文件。

Gradle Files

對於一個gradle 項目,最基礎的文件配置以下:


一個項目有一個setting.gradle、包括一個頂層的 build.gradle文件、每一個Module 都有本身的一個build.gradle文件。

  • setting.gradle:這個 setting 文件定義了哪些module 應該被加入到編譯過程,對於單個module 的項目能夠不用須要這個文件,可是對於 multimodule 的項目咱們就須要這個文件,不然gradle 不知道要加載哪些項目。這個文件的代碼在初始化階段就會被執行。
  • 頂層的build.gradle:頂層的build.gradle文件的配置最終會被應用到全部項目中。它典型的配置以下:
buildscript {
    ext.kotlin_version = '1.2.20'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

 

  • buildscript:定義了 Android 編譯工具的類路徑。repositories中,jCenter是一個著名的 Maven 倉庫。
  • allprojects:中定義的屬性會被應用到全部 moudle 中,可是爲了保證每一個項目的獨立性,咱們通常不會在這裏面操做太多共有的東西。

  • 每一個項目單獨的 build.gradle:針對每一個moudle 的配置,若是這裏的定義的選項和頂層build.gradle定義的相同,後者會被覆蓋。典型的 配置內容以下:

  • apply plugin:第一行代碼應用了Android 程序的gradle插件,做爲Android 的應用程序,這一步是必須的,由於plugin中提供了Android 編譯、測試、打包等等的全部task。
  • android:這是編譯文件中最大的代碼塊,關於android 的全部特殊配置都在這裏,這就是又咱們前面的聲明的 plugin 提供的。
    • defaultConfig就是程序的默認配置,注意,若是在AndroidMainfest.xml裏面定義了與這裏相同的屬性,會以這裏的爲主。
    • 這裏最有必要要說明的是applicationId的選項:在咱們曾經定義的AndroidManifest.xml中,那裏定義的包名有兩個用途:一個是做爲程序的惟一識別ID,防止在同一手機裝兩個同樣的程序;另外一個就是做爲咱們R資源類的包名。在之前咱們修改這個ID會致使全部用引用R資源類的地方都要修改。可是如今咱們若是修改applicationId只會修改當前程序的ID,而不會去修改源碼中資源文件的引用。
  • buildTypes:定義了編譯類型,針對每一個類型咱們能夠有不一樣的編譯配置,不一樣的編譯配置對應的有不一樣的編譯命令。默認的有debug、release 的類型。
  • dependencies:是屬於gradle 的依賴配置。它定義了當前項目須要依賴的其餘庫。

Gradle Wrapper

Gradle 不斷的在發展,新的版本不免會對以往的項目有一些向後兼容性的問題,這個時候,gradle wrapper就應運而生了。

gradlw wrapper 包含一些腳本文件和針對不一樣系統下面的運行文件。wrapper 有版本區分,可是並不須要你手動去下載,當你運行腳本的時候,若是本地沒有會自動下載對應版本文件。

在不一樣操做系統下面執行的腳本不一樣,在 Mac 系統下執行./gradlew ...,在windows 下執行gradle.bat進行編譯。

若是你是直接從eclipse 中的項目轉換過來的,程序並不會自動建立wrapper腳本,咱們須要手動建立。在命令行輸入如下命令便可

gradle wrapper --gradle-version 2.4gradle wrapper --gradle-version 2.4

它會建立以下目錄結構:

wrapper 就是咱們使用命令行編譯的開始。下面咱們看看 wrapper 有什麼樣的做用。

Gradle basics

Gradle 會根據build 文件的配置生成不一樣的task,咱們能夠直接單獨執行每個task。經過./gradlew tasks列出全部task。若是經過同時還想列出每一個task 對應依賴的其餘task,可使用./gradlew tasks -all

其實每當咱們在Android Studio點擊 build,rebuild,clean菜單的時候,執行的就是一些gradle task.

Android tasks

有四個基本的 task, Android 繼承他們分別進行了本身的實現:

  • assemble:對全部的 buildType 生成 apk 包。
  • clean:移除全部的編譯輸出文件,好比apk
  • check:執行lint檢測編譯。
  • build:同時執行assemblecheck命令

這些都是基本的命令,在實際項目中會根據不一樣的配置,會對這些task 設置不一樣的依賴。好比 默認的 assmeble 會依賴 assembleDebug 和assembleRelease,若是直接執行assmeble,最後會編譯debug,和release 的全部版本出來。若是咱們只須要編譯debug 版本,咱們能夠運行assembleDebug

除此以外還有一些經常使用的新增的其餘命令,好比 install命令,會將編譯後的apk 安裝到鏈接的設備。

咱們運行的許多命令除了會輸出到命令行,還會在build文件夾下生產一份運行報告。好比check命令會生成lint-results.html.build/outputs中。

Configuration

BuildConfig

這個類相信你們都不會陌生,咱們最經常使用的用法就是經過BuildConfig.DEBUG來判斷當前的版本是不是debug版本,若是是就會輸出一些只有在 debug 環境下才會執行的操做。 這個類就是由gradle 根據 配置文件生成的。爲何gradle 能夠直接生成一個Java 字節碼類,這就得益於咱們的 gradle 的編寫語言是Groovy, Groovy 是一種 JVM 語言,JVM 語言的特徵就是,雖然編寫的語法不同,可是他們最終都會編程 JVM 字節碼文件。同是JVM 語言的還有 Scala,Kotlin 等等。

這個功能很是強大,咱們能夠經過在這裏設置一些key-value對,這些key-value 對在不一樣編譯類型的 apk 下的值不一樣,好比咱們能夠爲debug 和release 兩種環境定義不一樣的服務器。好比:

除此以外,咱們還能夠爲不一樣的編譯類型的設置不一樣的資源文件,好比:

Repositories

Repositories 就是代碼倉庫,這個相信你們都知道,咱們平時的添加的一些 dependency 就是從這裏下載的,Gradle 支持三種類型的倉庫:Maven,Ivy和一些靜態文件或者文件夾。在編譯的執行階段,gradle 將會從倉庫中取出對應須要的依賴文件,固然,gradle 本地也會有本身的緩存,不會每次都去取這些依賴。

gradle 支持多種 Maven 倉庫,通常咱們就是用共有的jCenter就能夠了。
有一些項目,多是一些公司私有的倉庫中的,這時候咱們須要手動加入倉庫鏈接:

若是倉庫有密碼,也能夠同時傳入用戶名和密碼

咱們也可使用相對路徑配置本地倉庫,咱們能夠經過配置項目中存在的靜態文件夾做爲本地倉庫:

Dependencies

咱們在引用庫的時候,每一個庫名稱包含三個元素:組名:庫名稱:版本號,以下:

若是咱們要保證咱們依賴的庫始終處於最新狀態,咱們能夠經過添加通配符的方式,好比:

可是咱們通常不要這麼作,這樣作除了每次編譯都要去作網絡請求查看是否有新版本致使編譯過慢外,最大的弊病在於咱們使用過的版本很很困難是測試版,性能得不到保證,因此,在咱們引用庫的時候必定要指名依賴版本。

Local dependencies

File dependencies

經過files()方法能夠添加文件依賴,若是有不少jar文件,咱們也能夠經過fileTree()方法添加一個文件夾,除此以外,咱們還能夠經過通配符的方式添加,以下:

Native libraries

配置本地 .so庫。在配置文件中作以下配置,而後在對應位置創建文件夾,加入對應平臺的.so文件。

文件結構以下:

Library projects

若是咱們要寫一個library項目讓其餘的項目引用,咱們的bubild.gradle的plugin 就不能是andrid plugin了,須要引用以下plugin

apply plugin: 'com.android.library'apply plugin: 'com.android.library'

引用的時候在setting文件中include便可。

若是咱們不方便直接引用項目,須要經過文件的形式引用,咱們也能夠將項目打包成aar文件,注意,這種狀況下,咱們在項目下面新建arrs文件夾,並在build.gradle 文件中配置 倉庫:

當須要引用裏面的某個項目時,經過以下方式引用:

Build Variants

在開發中咱們可能會有這樣的需求:

  • 咱們須要在debug 和 release 兩種狀況下配置不一樣的服務器地址;
  • 當打市場渠道包的時候,咱們可能須要打免費版、收費版,或者內部版、外部版的程序。
  • 渠道首發包一般須要要求在歡迎頁添加渠道的logo。等等
  • 爲了讓市場版和debug版同時存在與一個手機,咱們須要編譯的時候自動給debug版本不同的包名。

這些需求都須要在編譯的時候動態根據當前的編譯類型輸出不一樣樣式的apk文件。這時候就是咱們的buildType大展身手的時候了。

Build Type

android 默認的帶有Debug和Release兩種編譯類型。好比咱們如今有一個新的statging的編譯類型

Source sets

每當建立一個新的build type 的時候,gradle 默認都會建立一個新的source set。咱們能夠創建與main文件夾同級的文件夾,根據編譯類型的不一樣咱們能夠選擇對某些源碼直接進行替換。

除了代碼能夠替換,咱們的資源文件也能夠替換

除此以外,不一樣編譯類型的項目,咱們的依賴均可以不一樣,好比,若是我須要在staging和debug兩個版本中使用不一樣的log框架,咱們這樣配置:

Product flavors

前面咱們都是針對同一份源碼編譯同一個程序的不一樣類型,若是咱們須要針對同一份源碼編譯不一樣的程序(包名也不一樣),好比 免費版和收費版。咱們就須要Product flavors

注意,Product flavorsBuild Type是不同的,並且他們的屬性也不同。全部的 product flavor 版本和defaultConfig 共享全部屬性!

像Build type 同樣,product flavor 也能夠有本身的source set文件夾。除此以外,product flavor 和 build type 能夠結合,他們的文件夾裏面的文件優先級甚至高於 單獨的built type 和product flavor 文件夾的優先級。若是你想對於 blue類型的release 版本有不一樣的圖標,咱們能夠創建一個文件夾叫blueRelease,注意,這個順序不能錯,必定是 flavor+buildType 的形式。

更復雜的狀況下,咱們可能須要多個product 的維度進行組合,好比我想要 color 和 price 兩個維度去構建程序。這時候咱們就須要使用flavorDimensions

根據咱們的配置,再次查看咱們的task,發現多了這些task:

Resource merge priority

在Build Type中定義的資源優先級最大,在Library 中定義的資源優先級最低。

Signing configurations

若是咱們打包市場版的時候,咱們須要輸入咱們的keystore數據。若是是debug 版本,系統默認會幫咱們配置這些信息。這些信息在gradle 中都配置在signingConfigs中。


配置以後咱們須要在build type中直接使用

Optimize

Speeding up multimodule builds

能夠經過如下方式加快gradle 的編譯:

  • 開啓並行編譯:在項目根目錄下面的 gradle.properties中設置
org.gradle.parallel=true
  • 開啓編譯守護進程:該進程在第一次啓動後回一直存在,當你進行二次編譯的時候,能夠重用該進程。一樣是在gradle.properties中設置。
org.gradle.daemon=true
  • 加大可用編譯內存:
org.gradle.jvmargs=-Xms256m -Xmx1024m

Reducing apk file

在編譯的時候,咱們可能會有不少資源並無用到,此時就能夠經過shrinkResources來優化咱們的資源文件,除去那些沒必要要的資源。

若是咱們須要查看該命令幫咱們減小了多少無用的資源,咱們也能夠經過運行shrinkReleaseResources命令來查看log.

某些狀況下,一些資源是須要經過動態加載的方式載入的,這時候我也須要像 Progard 同樣對咱們的資源進行keep操做。方法就是在res/raw/下創建一個keep.xml文件,經過以下方式 keep 資源:

Manual shrinking

對一些特殊的文件或者文件夾,好比 國際化的資源文件、屏幕適配資源,若是咱們已經肯定了某種型號,而不須要從新適配,咱們能夠直接去掉不可能會被適配的資源。這在爲廠商適配機型定製app的時候是很用的。作法以下:
好比咱們可能有很是多的國際化的資源,若是咱們應用場景只用到了English,Danish,Dutch的資源,咱們能夠直接指定咱們的resConfig:

對於尺寸文件咱們也能夠這樣作

Profiling

當咱們執行全部task的時候咱們均可以經過添加--profile參數生成一份執行報告在reports/profile中。示例以下:

咱們能夠經過這份報告看出哪一個項目耗費的時間最多,哪一個環節耗費的時間最多。

Practice

在開發的過程當中,咱們可能會遇到不少狀況須要咱們可以本身定義task,在自定義task 以前,咱們先簡單看看groovy 的語法。

Groovy

咱們前面看到的那些build.gradle 配置文件,和xml 等的配置文件不一樣,這些文件能夠說就是能夠執行的代碼,只是他們的結構看起來通俗易懂,和配置文件沒什麼兩樣,這也是Google 之因此選擇Groovy 的緣由。除此以外,Groovy 是一門JVM 語言,也就是,Groovy 的代碼最終也會被編譯成JVM 字節碼,交給虛擬機去執行,咱們也能夠直接反編譯這些字節碼文件。

咱們這裏簡單地說一下 groovy 一些語法。

變量

在groovy 中,沒有固定的類型,變量能夠經過def關鍵字引用,好比:

def name = 'Andy'

咱們經過單引號引用一串字符串的時候這個字符串只是單純的字符串,可是若是使用雙引號引用,在字符串裏面還支持插值操做,

 
  1. def name = 'Andy'

  2. def greeting = "Hello, $name!"

方法

相似 Python 同樣,經過def關鍵字定義一個方法。方法若是不指定返回值,默認返回最後一行代碼的值。

 
  1. def square(def num) {

  2. num * num

  3. }

  4. square 4

Groovy 也是經過Groovy 定義一個類:

 
  1. class MyGroovyClass {

  2. String greeting

  3. String getGreeting() {

  4. return 'Hello!'

  5. }

  6. }

  • 在Groovy 中,默認全部的類和方法都是pulic的,全部類的字段都是private的;
  • 和java同樣,咱們經過new關鍵字獲得類的實例,使用def接受對象的引用:def instance = new MyGroovyClass()
  • 並且在類中聲明的字段都默認會生成對應的setter,getter方法。因此上面的代碼咱們能夠直接調用instance.setGreeting 'Hello, Groovy!'注意,groovy 的方法調用是能夠沒有括號的,並且也不須要分號結尾。除此以外,咱們甚至也能夠直接調用;
  • 咱們能夠直接經過instance.greeting這樣的方式拿到字段值,但其實這也會經過其get方法,並且不是直接拿到這個值。

map、collections

在 Groovy 中,定義一個列表是這樣的:

List list = [1, 2, 3, 4, 5]List list = [1, 2, 3, 4, 5]

遍歷一個列表是這樣的:

 
  1. list.each() { element ->

  2.  
  3. println element

  4.  
  5. }

定義一個 map 是這樣的:

Map pizzaPrices = [margherita:10, pepperoni:12]Map pizzaPrices = [margherita:10, pepperoni:12]

獲取一個map 值是這樣的:

 
  1. pizzaPrices.get('pepperoni')

  2. pizzaPrices['pepperoni']

閉包

在Groovy 中有一個閉包的概念。閉包能夠理解爲就是 Java 中的匿名內部類。閉包支持相似lamda形式的語法調用。以下:

 
  1. def square = { num ->

  2. num * num

  3. }

  4. square 8

若是隻有一個參數,咱們甚至能夠省略這個參數,默認使用it做爲參數,最後代碼是這樣的:

 
  1. Closure square = {

  2. it * it

  3. }

  4. square 16

理解閉包的語法後,咱們會發現,其實在咱們以前的配置文件裏,android,dependencies這些後面緊跟的代碼塊,都是一個閉包而已。

Groovy in Gradle

瞭解完 groovy 的基本語法後,咱們來看看 gradle 裏面的代碼就好理解多了。

  • apply
    apply plugin: 'com.android.application'apply plugin: 'com.android.application'
    這段代碼其實就是調用了project對象的apply方法,傳入了一個以plugin爲key的map。完整寫出來就是這樣的:
project.apply([plugin: 'com.android.application'])project.apply([plugin: 'com.android.application'])
  • dependencies
    咱們看到的是這樣:

實際調用的時候會傳入一個DependencyHandler的閉包,代碼以下:

Task

  • 建立一個task

運行該 task

./gradlew hello

注意:咱們前面說過,gradle的生命週期分三步,初始化,配置和執行。上面的代碼在配置過程就已經執行了,因此,打印出的字符串發生在該任務執行以前,若是要在執行階段才執行任務中的代碼應該以下設置:

  • 添加Action:前面咱們說過task 包含系列的action,當task 被執行的時候,全部的action 都會被依次執行。若是咱們要加入本身的action,咱們能夠經過複寫doFirst()doLast()方法。

打印出來是這樣的:

  • Task 依賴:前面咱們也說過,task 之間的關係就是依賴關係,關於Task 的依賴有兩種,must RunAfterdependsOn。好比:
 
  1. task task1 <<{

  2. printfln 'task1'

  3. }

  4.  
  5. task task2 <<{

  6. printfln 'task2'

  7. }

  8. task2.mustRunAfter task1

 
  1. task task1 <<{

  2. printfln 'task1'

  3. }

  4.  
  5. task task2 <<{

  6. printfln 'task2'

  7. }

  8. task2.dependsOn task1

他們的區別是,運行的的時候前者必需要都按順序加入gradlew task2 task1執行才能夠順利執行,不然單獨執行每一個任務,後者只須要執行gradlew task2便可同時執行兩個任務。

Practice

咱們能夠經過兩個例子來實踐task。

keystore 保護

這裏直接將 store 的密碼明文寫在這裏對於產品的安全性來講不太好,特別是若是該源碼開源,別人就能夠用你的 id 去發佈app。對於這種狀況,咱們須要構建一個動態加載任務,在編譯release 源碼的時候從本地文件(未加入Git)獲取keystore 信息,以下:

你還能夠設置一個保險措施,萬一咱們的沒有找到對應的文件須要用戶從控制檯輸入密碼

最後設置最終值

而後設置release 任務依賴於咱們剛剛設置的任務

經過 hook Android 編譯插件 重命名 apk

最後編譯出來的apk 名字相似 app-debug-1.0.apk

參考文獻

  • Gadle For Android
相關文章
相關標籤/搜索