Gradle For Android(2)--基礎的定製構建

sas???發表於2018-10-17

理解Gradle檔案

當建立一個新的Project的時候,會預設生成3個Gradle檔案。在專案的根目錄(在Project的Top-Level)下會生成settings.gradlebuild.gradle。而在Android app模組中會建立一個build.gradle檔案。目錄結構如下:

 MyApp
   ├── build.gradle
   ├── settings.gradle
   └── app
       └── build.gradle

Settings檔案

對於一個新的Project,settings.gradle檔案只會有一行

include ':app'

這個setting.gradle在初始化階段被執行,並且定義了哪些Module應該在構建中被包含。在該例中,只有:app模組被包含。只有一個模組的Project可以不需要該檔案,而多個模組的Project的必須要該檔案,否則Gradle不知道哪些模組需要被包含(include)。

在這種場景下,Gradle建立了為每個Settings檔案都建立了一個Serttings物件,並且可以從該物件中呼叫所需要的Methods。我們不需要知道Settings類的細節,但是最好關注一下。

頂層的build.gradle

頂層的build.gradle檔案中,我們可以配置一些options,這些options可以應用於所有在這個Project中的Module。它預設包含以下兩個程式碼塊:

buildscript {
      repositories {
            jcenter() 
      }
      dependencies {
           classpath 'com.android.tools.build:gradle:1.2.3'
      } 
}
allprojects {
      repositories {
            jcenter() }
      }
}

buildscript程式碼塊中是真正構建的配置的地方。respositories程式碼塊配置了JCenter作為倉庫。在這種情況下,倉庫代表了這個Project所依賴的資源或者說我們所需要的一些可下載的Libraries都是儲存在這個倉庫中。JCenter是一個知名的Maven倉庫。

dependencies程式碼塊用來配置構建過程的依賴。也就是說,我們不應該在Top-Level的build.gradle中包含Application或者Libraries的依賴。它唯一的依賴關係應該為是預設定義的Android Plugin。它會去遍歷每一個Android Module,因為它是會執行Android-Related Tasks的外掛。

allprojects程式碼塊用來定義需要被應用到每一個Module中的屬性。我們甚至可以在這個程式碼塊中建立Task,而這些Task可以在各個Module中被應用。

Module中的build.gradle

Module層的build.gradle檔案包含了一些options,這些options只能應用在Android app module中。它也能夠覆蓋Project層的build.gradle檔案中的屬性。該模組的file如下:

apply plugin: 'com.android.application'
android {
       compileSdkVersion 22
       buildToolsVersion "22.0.1"
       defaultConfig {
           applicationId "com.gradleforandroid.gettingstarted"
           minSdkVersion 14
           targetSdkVersion 22
           versionCode 1
           versionName "1.0"
       }

       buildTypes {
           release {
               minifyEnabled false
               proguardFiles getDefaultProguardFile
                ('proguard-android.txt'), 'proguard-rules.pro'
           }
      } 
}
dependencies {
       compile fileTree(dir: 'libs', include: ['*.jar'])
       compile 'com.android.support:appcompat-v7:22.2.0'
}

其中三個主要的程式碼塊:

  • plugin:第一行應用了Android的application plugin,配置在頂層的build.gradle中。這個外掛主要由Android工具團隊寫並且維護的,提供了所有需要構建application以及libraries的build,test,package任務
  • android:這個程式碼塊主要包括了Android特殊的配置。只有兩個屬性需要配置compileSdkVersion以及buildToolsVersion。負責編譯的sdk api版本以及build tools的版本。其中build tools包括了很多命令列的工具,比如說aapt,zipalign,dx,renderscript等等,使用這些工具我們可以生產出各種各樣的中介軟體。我們可以通過SDK Manager下載build tools。

defaultConfig程式碼塊配置了App核心的屬性。在這個程式碼塊中的屬性會重寫AndroidManifest.xml中相對應的屬性。

applicationId屬性會重寫Manifest.xml中的packageName。在Gradle之前的構建系統中,PackageName有兩個作用,唯一表示一個App以及用於為R.java賦予包名。而通過Gradle使用build variants使得構建不同版本的App變得更加簡單了。比如,很容易構建一個付費/免費的版本。而這兩個版本需要兩個單獨的PackageName,這樣才能夠被一起裝到一個手機上。但是原始碼以及R檔案包名都還保持著相同的PackageName,以至於在構建多個版本的時候,需要把所有的原始檔都進行修改。

因此,這也就是為什麼Android Tool團隊減弱了packageName的這兩個用途。定義在Manifest中的PackageName仍然會用於SourceCode以及R檔案。而Google Play則會使用application id作為唯一識別符號來區分App。

包括minSdkVersion,targetSdkVersion,versionCode,versionName等等在內的所有值都會覆蓋掉Manifest.xml中的值,如果在build.gradle中定義了這些值,Manifest.xml中就可以不定義了,但是以防萬一最好還是在Manifest.xml中定義好,避免遺漏出錯。

buildType程式碼塊定義了構建不同型別的App的地方。後續會再詳細說明。

dependencies程式碼塊是標準Gradle配置的一部分,這也就是它為什麼會在android程式碼塊之外的原因。並且它定義了app或者library中所有的依賴關係。預設一個新的Android App會對libs目錄下的所有jar包有依賴。取決於新Project的啟動項配置。

瞭解Tasks

為了瞭解整個Project中哪些Task是可用的,我們可以通過gradlew tasks來列出所有可用的Tasks。如下圖所示:

1941624-7b42547235010501.png
可用的Tasks

在一個新建立的Android Project中,它包括了:

  • Android tasks
  • build tasks
  • build setup tasks
  • help tasks
  • install Tasks
  • verification Tasks
  • ...

如果不止想看到Tasks,而是各個Task之間的依賴關係,可以使用gradlew tasks --all。當你希望列印出執行一個特殊的Task的所有步驟時,可以加上引數-m或者--dry-run

Android Tasks

Android Plugin繼承自基礎的Task,並且實現了自己一些功能。這些Tasks在Android中會有如下表現:

  • assemble:為每個Build Type構建APK
  • clean:移除所有Build中介軟體以及Apk檔案等等
  • check:執行Lint的檢查,並且如果Lint出現問題的時候,會打斷Build過程
  • build:執行assemble以及check任務

Assemble任務預設由assembleDebug以及assembleRelease構成,如果有更多的Build Type的話,則會有更多的任務。也就是說,執行Aseemble將會為每個Build Type觸發一個構建。

除了繼承了這些Tasks之外,Android Plugin也新增了一些新的Task。以下為最重要的新的Tasks:

  • connectedCheck:在已經連線的裝置或者模擬器上執行tests任務
  • deviceCheck:為其他外掛在遠端裝置上除錯提供的佔位任務
  • installDebug/installRelease:在已經連線的裝置或者模擬器上安裝一個特定的版本
  • 所有的install任務都會有相對應的uninstall任務

build任務依賴於check任務,而不是connectedCheck或者deviceCheck。這保證了常規的檢查不需要連線裝置 。執行完check任務後,會生成一個Lint Report檔案,其中儲存著warnings以及errors,可以在app/build/outputs/lint-results.html中找到。

1941624-78d952c2d208e371.png
Lint Report

當Assemble一個Release版本時,Lint將檢查可能會導致App Crash的問題。如果找到的話,就會中斷Build,並且在Command-Line中列印出錯誤。並且也會在app/build/outputs中生成lint-results-release-fatal.html檔案。如果有多個錯誤,則通過HTML的Report報告然後滑動到報錯的位置就可以看到了。

在Android Studio中,右側的Gradle視窗雙擊對應的Task即可開始執行。也就不用在命令列工具中輸入命令了。

1941624-e8eb251be15e6901.png
Gradle視窗

BuildConfig以及Resources

從SDK Tool版本17之後,Build Tool會生成一個名為BuildConfig的類,其中包含了根據build type生成的DEBUG常量。這對控制日誌列印是非常有利的。而且,這也為Debug或者Release的常量區分帶來了很多的方案,比如我們需要根據Build Type來開啟/關閉一些Features,或者設定Server的URLs等等,例如:

android {
       buildTypes {
           debug {
               buildConfigField "String", "API_URL","\"http://test.example.com/api\""
               buildConfigField "boolean", "LOG_HTTP_CALLS", "true"
           }
           release {
               buildConfigField "String", "API_URL","\"http://example.com/api\""
               buildConfigField "boolean", "LOG_HTTP_CALLS", "false"
          } 
      }
}

通過雙引號中新增的String值會被生成一個真實的String。通過新增了buildConfigField這一行,我們可以使用BuildConfig.API_URLBuildConfig.LOG_HTTP來引用不同的值。

而最近,Android Tool團隊也會通過以下方式來配置資源:

android {
       buildTypes {
           debug {
               resValue "string", "app_name", "Example DEBUG"
          }
           release {
               resValue "string", "app_name", "Example"
          }
        }
}

Project級別的Settings

如果擁有多個Android Modules的話,它可以非常簡便的修改每個Module中的build.gradle中的值,而不用手動的去修改了。我們已經看到了allprojects程式碼塊在頂層的build.gradle中定義了reositories,並且你可以使用相同的方式來應用Android指定的Settings:

allprojects {
       apply plugin: 'com.android.application'
       android {
           compileSdkVersion 22
           buildToolsVersion "22.0.1"
      } 
}

這段程式碼塊只會應用於所有的Android App Project,因為需要應用Android Plugin去使用Android特殊的Settings。一種更好的方案是在頂層的build.gradle中定義這些值,然後在各個Module中應用。也就意味著,我們可以在build.gradle檔案中繫結ext程式碼塊,其中定義一些自定義的屬性:

ext {
       compileSdkVersion = 22
       buildToolsVersion = "22.0.1"
}

通過這種方式來在Module級別的build.gradle中使用rootProject來獲取使用的值。

android {
       compileSdkVersion rootProject.ext.compileSdkVersion
       buildToolsVersion rootProject.ext.buildToolsVersion
}

Project properties

定義Project的Properties有幾種方式,最常用的三種:

  • 使用ext程式碼塊
  • 使用gradle.properties檔案
  • 通過-P的命令列引數

以下為這三種方式的示例程式碼:

ext {
     local = 'Hello from build.gradle'
}
task printProperties << {
     println local        // Local extra property
     println propertiesFile        // Property from file
     if (project.hasProperty('cmd')) {
         println cmd        // Command line property
     }
}

gradle.properties檔案中定義如下:

propertiesFile = Hello from gradle.properties

如果通過命令列引數執行printProperties任務的話,輸出如下:

$ gradlew printProperties -P cmd='Hello from the command line'
:printProperties
Hello from build.gradle
Hello from gradle.properties
Hello from the command line

預設的任務

如果使用gradle沒有指定具體的任務的話,則會執行help任務。如果需要指定預設的任務的話,則需要在頂層的build.gradle中加入預設任務:

defaultTasks 'clean', 'assembleDebug'

這樣的話,執行gradlew就會預設執行這兩個任務。而通過gradlew tasks | grep "Default tasks"也可以檢視預設的任務。

相關文章