1. 程式人生 > >Android Gradle 完整指南—一站式服務

Android Gradle 完整指南—一站式服務

轉載這篇文章,本文來自http://www.jianshu.com/p/9df3c3b6067a

前言

為什麼需要學Gradle?

Gradle是Android現在主流的編譯工具,雖然在Gradle出現之前和之後都有對應更快的編譯工具出現,但是Gradle的優勢就在於它是親兒子,Gradle確實比較慢,這和它的編譯過程有關,但是現在的Gradle編譯速度已經有了成倍提高。除此之外,相對其他編譯工具,最重要的,他和Android Studio的關係非常緊密,可以說對於一些簡單的程式我們幾乎不需要任何程式碼上的配置只使用Android Studio就可以完成編譯和執行。

但是對於一些比較複雜的,特別是多人團隊合作的專案我們會需要一些個性化的配置來提高我們的開發效率。比如我們要自定義編譯出的apk包的名字、對於一些特殊產品我們可能會要用同一個專案編譯出免費版

付費版的apk。這些高階的功能都需要我們對配置程式碼進行自定義地修改。

最近伴隨著Android Studio2.0的釋出, Gradle也進行了一次非常大的升級,叫Instant Run .它的編譯速度網上有人用逆天兩個字來形容。當我們第一次點選run、debug按鈕的時候,它執行時間和我們往常一樣。但是接下去的時間裡,你每次修改程式碼後點擊run、debug按鈕,對應的改變將迅速的部署到你正在執行的程式上,傳說速度快到你都來不及把注意力集中到手機螢幕上,它就已經做好相應的更改。但是剛出來的似乎對一些專案的相容性不太好,現在升級後不知道怎麼樣。

為什麼要了解命令列編譯?

在很多情況下我們都是使用的Android Studio 來build、debug專案。Android Studio 能滿足我們開發的大多數需求,但是某些情況下命令列能夠讓我們編譯的效率更高,過程更明朗,一些高階的配置也需要熟悉命令列才能夠使用,比如在伺服器編譯,某些專案初始化的時候如果直接交給Android Studio ,它會一直Loading,你都不知道它在幹嘛,但是用命令列你就知道它卡在了哪個環節,你只需要修改某些程式碼,馬上就能夠編譯過了。

瞭解Gradle 之後我們可以做什麼?

we can do everything what we want.

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

Gralde Overview

History

我們知道,Android 的編譯過程非常複雜:
在這裡插入圖片描述
我們需要一種工具幫我們更快更方便更簡潔地完成Android程式的編譯。現在結合Android Studio我們一般使用的工具都是Gradle,在Gradle出現以前Android也有對應的編譯工具叫Ant ,在Gradle出現之後,也有新的編譯工具出現,就是FaceBook的Buck工具。這些編譯工具在出現的時候幾乎都比Gradle要快,Gradle之所以慢是跟它的編譯週期有很大關係。

Gradle 的編譯週期

在解析Gradle的編譯過程之前我們需要理解在Gradle中非常重要的兩個物件。ProjectTask

每個專案的編譯至少有一個Project,一個build.gradle就代表一個project,每個project裡面包含了多個task,task裡面又包含很多action,action是一個程式碼塊,裡面包含了需要被執行的程式碼。

在編譯過程中, Gradle會根據build相關檔案,聚合所有的project和task,執行task中的action。因為build.gradle檔案中的task非常多,先執行哪個後執行那個需要一種邏輯來保證。這種邏輯就是依賴邏輯,幾乎所有的Task都需要依賴其他task來執行,沒有被依賴的task會首先被執行。所以到最後所有的Task會構成一個有向無環圖(DAG Directed Acyclic Graph)的資料結構。

編譯過程分為三個階段:

  • 初始化階段:建立Project物件,如果有多個build.gradle,也會建立多個project.
  • 配置階段:在這個階段,會執行所有的編譯指令碼,同時還會建立project的所有的task,為後一個階段做準備。
  • 執行階段:在這個階段,gradle會根據傳入的引數決定如何執行這些task,真正action的執行程式碼就在這裡.

剛剛我們提到Gradle 編譯的時候的一些相關檔案,下面我們挨個解析一下這些檔案。

Gradle Files

對於一個gradle 專案,最基礎的檔案配置如下:
在這裡插入圖片描述

一個專案有一個setting.gradle、包括一個頂層的build.gradle檔案、每個Module都有自己的一個build.gradle檔案。

  • setting.gradle:這個setting檔案定義了哪些module應該被加入到編譯過程,對於單個module的專案可以不用需要這個檔案,但是對於multimodule的專案我們就需要這個檔案,否則gradle不知道要載入哪些專案。這個檔案的程式碼在初始化階段就會被執行。
  • 頂層的build.gradle:頂層的build.gradle檔案的配置最終會被應用到所有專案中。它典型的配置如下:
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
}

allprojects{
    repositories{
        jcenter()
    }
}
  • buildscript:定義了Android編譯工具的類路徑。repositories中, jCenter是一個著名的Maven倉庫。

  • allprojects:中定義的屬性會被應用到所有module中,但是為了保證每個專案的獨立性,我們一般不會在這裡面操作太多共有的東西。

  • 每個專案單獨的build.gradle:針對每個module的配置,如果這裡的定義的選項和頂層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.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:同時執行assemble和check命令

這些都是基本的命令,在實際專案中會根據不同的配置,會對這些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'

引用的時候在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 flavors和Build 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

在這裡插入圖片描述

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來優化我們的資原始檔,除去那些不必要的資源。
在這裡插入圖片描述