1. 程式人生 > >Android build system:構建系統的組成及其原理

Android build system:構建系統的組成及其原理

Android build system

組成部分

Android build system 的組成部分:Gradle + Android plugin for Gradle

android app打包流程(即構建流程):
android app構建流程

  • Gradle:用於構建專案,即設定app打包過程的各種配置,如使用哪些SourceCode、哪些依賴、是否打簽名包等。

  • Gradle 外掛:使系統能支援執行Gradle。與Gradle 是獨立執行的,因此需要獨自下載。

  • Build Tools:Android 構建的相關工具都在這裡面,位於./sdk/build-tools/目錄下,它提供了類似aapt、dx這樣的工具,gradle則是使用這樣的工具來完成相應的構建任務。

Gradle和Gradle外掛是分開的,那它們各自是如何下載的呢?

Gradle 、Gradle外掛和Build Tools的下載安裝

1、 Gradle
開啟一個專案時,AS會根據專案中gradle > wrapper > gradle-wrapper.properties 檔案中的distributionUrl=https://services.gradle.org/distributions/gradle-2.4-all.zip 設定的Gradle版本去查詢。

那麼AS是在什麼地方查詢的呢?
通過在Android Studio中依次點選File > Settings > Build, Execution, Deployment > Gradle,我們可以鎖定當前專案使用的Gradle的位置。
- 若選中Use default gradle wrapper(recommended)

,則設定的Gradle位置為Service directory path中的路徑;
- 若選中Use local gradle distribution,則設定的Gradle位置為Gradle home中的路徑。
注:Service directory path是全域性級的,Use default gradle wrapper(recommended)與Use local gradle distribution是專案級的,優先順序高於全域性級的設定。
Gradle存放位置
通常我們都是選擇Use default gradle wrapper(recommended)。
選擇Use default gradle wrapper(recommended) 之後,AS根據gradle-wrapper.properties 檔案中的配置去service directory path下查詢(後面介紹gradle-wrapper.properties會講到),若沒有則通過distributionUrl 去下載Gradle。

AS自動下載Gradle會很慢,兩種解決辦法
1. 修改gradle-wrapper.properties 中的Gradle版本:在開啟專案前,修改gradle-wrapper.properties 中distributionUrl 的值為service directory path目錄下已有的Gradle版本。開啟專案後,有可能還要更改Gradle外掛、build tools等的版本,詳情看 “Android Studio、Gradle、Gradle外掛、build tools它們的版本關係”。
2. 手動下載Gradle:參考[Android Studio系列(五)] Android Studio手動配置Gradle的方法(windows /Linux適用)

2、 Gradle 外掛
都是在專案的build.gradle 檔案(The Top-level Build File)中設定依賴,自動下載的,不需要fq:

buildscript {
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.0'//Gradle外掛版本
    }
}

只要是能通過設定依賴獲得的基本都不用fq,因為國內網能正常訪問到在repository中設定的jCenter()和maven()等。

3、 Build Tools
在SDK Manager 中下載各個版本到本地,然後在模組的build.gradle檔案(The Module-level Build File)中設定:

android {
    buildToolsVersion "27.0.3"
}

Android Studio、Gradle、Gradle外掛、Build Tools它們的版本關係

關係:AS版本決定Gradle外掛版本,Gradle外掛版本決定Gradle版本(因為Gradle的執行必須由Gradle外掛支援)和Build Tools 版本。

安裝AS時,會自動安裝相應版本的Gradle外掛和Gradle,但可以根據專案需求更改到更高的版本。Gradle外掛版本能否更改為低版本的還不知道,需要的話自己去試試。

Plugin version Required Gradle version
1.0.0 - 1.1.3 2.2.1 - 2.3
1.2.0 - 1.3.1 2.2.1 - 2.9
1.5.0 2.2.1 - 2.13
2.0.0 - 2.1.2 2.10 - 2.13
2.1.3 - 2.2.3 2.14.1+
2.3.0+ 3.3+
3.0.0+ 4.1+
3.1.0+ 4.4+

至於其他關係官網沒有列出來表格,但對每個版本有單獨介紹,網上有別人整理了一個,但不是最新的:
AS、Gradle、GradlePlugin、BuildTools版本關係
注:AS版本號和Gradle外掛版本號通常一樣或接近。

Gradle 的各種構建配置檔案

Gradle 的構建配置檔案有如下幾種:
Gradle各種構建配置檔案

真實環境下的Gradle構建配置檔案如下:
真實環境下的Gradle構建配置檔案

The Gradle Settings File

告訴Gradle在打包app時應該包含哪些模組。

include ‘:app’//app是模組名稱。

Build File

  • 型別:The Top-level Build File(專案的build.gradle檔案)、The Module-level Build File(模組的build.gradle檔案)

  • 與AndroidManifest.xml 的關係:
    可以在build.gradle 中設定manifest檔案的某些屬性,build.gradle 設定的屬性會覆蓋manifest檔案中的屬性。

專案的build.gradle檔案

//buildscript 主要用於為Gradle配置倉庫和依賴,不能為模組配置。
buildscript {

    //倉庫中存放各種可依賴專案。
    //倉庫可以是遠端倉庫如JCenter, Maven Central, and Ivy,也可以是自定義的遠端倉庫或本地倉庫。
    repositories {
        google()
        jcenter()
    }

    /**
     * The dependencies block configures the dependencies Gradle needs to use
     * to build your project. The following line adds Android plugin for Gradle
     * version 3.1.0 as a classpath dependency.
     */
    //指定匯入上面倉庫的某個專案。
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.0'//Gradle外掛版本
    }
}

//為所有模組提供倉庫和依賴
allprojects {
   repositories {
       google()
       jcenter()
   }
}

//為所有模組定義公共變數
ext {
    // The following are only a few examples of the types of properties you can define.
    compileSdkVersion = 26
    // You can also create properties to specify versions for dependencies.
    // Having consistent versions between modules can avoid conflicts with behavior.
    supportLibVersion = "27.1.1"
    ...
}
...

公共變數在模組的build.gradle檔案中的使用:

android {
  // Use the following syntax to access properties you defined at the project level:
  // rootProject.ext.property_name
  compileSdkVersion rootProject.ext.compileSdkVersion
  ...
}
...
dependencies {
    compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
    ...
}

模組的build.gradle檔案


//將Gradle 外掛應用到模組中。
apply plugin: 'com.android.application'//主模組的設定,library模組的設定是’com.android.library’

/**
 * The android block is where you configure all your Android-specific
 * build options.
 */
android {

  /**
   * compileSdkVersion specifies the Android API level Gradle should use to
   * compile your app. This means your app can use the API features included in
   * this API level and lower.
   */

  //決定編譯時使用哪個SDK版本的API。
  //當用戶手機的系統版本高於compileSdkVersion時,高版本擁有的新特性無法使用。
  //即在開發時能使用的API只能屬於compileSdkVersion或更低版本的,
  //更高版本的SDK中的API無法使用。
  compileSdkVersion 26

  /**
   * buildToolsVersion specifies the version of the SDK build tools, command-line
   * utilities, and compiler that Gradle should use to build your app. You need to
   * download the build tools using the SDK Manager.
   *
   * If you're using Android plugin 3.0.0 or higher, this property is optional—
   * the plugin uses a recommended version of the build tools by default.
   */

  buildToolsVersion "27.0.3"

  /**
   * The defaultConfig block encapsulates default settings and entries for all
   * build variants, and can override some attributes in main/AndroidManifest.xml
   * dynamically from the build system. You can configure product flavors to override
   * these values for different versions of your app.
   */
  //為所有build variants提供預設配置,會自動覆蓋AndroidManifest.xml中的屬性
  defaultConfig {
    //applicationId 必須是獨一無二的。
    //編譯後同級別目錄下的manifest檔案中的package name的值會替換為applicationId的值,用於應用商店識別是否是同個app。
    //一般applicationId 和package name設定成一樣的值。
    applicationId 'com.example.myapp'

    // Defines the minimum API level required to run the app.
    minSdkVersion 15

    // Specifies the API level used to test the app.
    targetSdkVersion 26//只是用來指定測試app的API版本或者說手機系統版本

    versionCode 1

    versionName "1.0"
  }

  /**
   * The buildTypes block is where you can configure multiple build types.
   * By default, the build system defines two build types: debug and release. The
   * debug build type is not explicitly shown in the default build configuration,
   * but it includes debugging tools and is signed with the debug key. The release
   * build type applies Proguard settings and is not signed by default.
   */
  //預設提供debug 和release,可自定義更多的。
  //為debug 或release 版本的app設定構建配置。
  //如:為debug版本的app設定允許輸出log,去除程式碼抖動、程式碼混淆、簽名等不必要構建配置,提高打包速度。
  buildTypes {

    release {
        minifyEnabled true // Enables code shrinking for the release build type.
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//設定程式碼混淆檔案。
    }
  }

  /**
   * If you're using Android plugin 3.0.0 or higher, you need to also declare
   * and assign each flavor to a flavor dimension. To learn more, read the
   * migration guide.
   */
  //系統不預設提供,需自己定義。用於據不同風格打不同版本的包,如多渠道打包。
  //多渠道打包:為每個應用商店提供獨有的flavor 並進行相應配置。
  //與build type 不同之處:可以配置的屬性不同,如可配置defaultConfig 中的屬性。
  //會覆蓋defaultConfig 中屬性。每個flavor有自己獨有的application ID
  productFlavors {
    free {
      applicationId 'com.example.myapp.free'
    }

    paid {
      applicationId 'com.example.myapp.paid'
    }
  }

  /**
   * The splits block is where you can configure different APK builds that
   * each contain only code and resources for a supported screen density or
   * ABI. You'll also need to configure your build so that each APK has a
   * different versionCode.
   */

  splits {
    // Settings to build multiple APKs based on screen density.
    density {

      // Enable or disable building multiple APKs.
      enable false

      // Exclude these densities when building multiple APKs.
      exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
    }
  }
}

/**
 * If you're using Android plugin 3.0.0 or higher, you should
 * use the new dependency configurations, which help you improve build speeds by
 * restricting which dependencies leak their APIs to other modules.
 */

//只屬於該模組的依賴
dependencies {
    compile project(":lib")//依賴專案中其它模組
    compile 'com.android.support:appcompat-v7:27.1.1'
    compile fileTree(dir: 'libs', include: ['*.jar'])//依賴模組的libs資料夾中的所有jar檔案
}

Source sets 及 app 構建原理

1、Source sets 概念
將模組中的java程式碼和資源分組。預設只有(主源集)main source set。
開發者可為每個build variants 提供各自的source set,每個source set下都有自己的manifest檔案,可在android{}中設定。

build variants:構建變體,是打包app的渠道,每個build variants能打包得到一個app。這裡的build variants指由一個build type 和一個flavor 組合而成的。單個的build type 或flavor 也可以稱為build variants。

增加名為debug 的source set後的目錄結構如下圖:
source set

2、app 構建原理
打某個build variants 型別的包時主模組會將 相應 build variants 的source setmain source setlibrary dependence 融合,包括java程式碼、資源、manifest檔案等的融合。(注:其他library 模組的融合也是如此。其實,主模組的融合過程就是整個app的構建過程。)

  • 融合規則
    若融合過程出現同名檔案或屬性時,優先順序高的覆蓋優先順序低的。 優先級別:build variants(最高) > build type > flavor > main source set(主源集) > library dependence

    1. Java程式碼:所有java/ 目錄下的程式碼被一起編譯。這些合併到一起的程式碼的java類不能“同路徑同名”(“同路徑同名”指兩個同名類在各自 source set 的 java/ 目錄之後的路徑相同)。
    2. 資源:不同source set 或library 中的資源名稱可以一樣。
    3. manifest檔案:不同manifest檔案中的配置可以一樣。
      manifest檔案的融合過程如下圖:
      manifest-merger_2x

    注意:一般優先順序低的 manifest 檔案設定的依賴、Build Tools等的版本不能高於優先順序高的manifest檔案,因為高版本具有向下相容的功能。
    參考官方文件:Merge multiple manifest files

  • 總結:
    main source set 和其它source set中的java類不能“同路徑同名”(“同路徑同名”指兩個同名類在java/ 目錄之後的路徑相同),但 resource資源 和 manifest檔案中的檔案或屬性 可以一樣(優先順序高的覆蓋優先順序低的)。
    同一個 source set 中的 resource 資源 不能重複(因為同一個 source set 中它們的優先順序一樣)且只能有一個manifest檔案。
    source set 的融合參考官方文件:Configure build variants

關於模組的build.gradle 更詳細介紹參考:

Gradle properties files

  • gradle.properties
    This is where you can configure project-wide Gradle settings, such as the Gradle daemon’s maximum heap size.

  • local.properties
    Configures local environment properties for the build system, such as the path to the SDK installation. Because the content of this file is automatically generated by Android Studio and is specific to the local developer environment, you should not modify this file manually or check it into your version control system.

gradle-wrapper.properties(gradle版本統一管理檔案)

檔案內容:

#Mon Sep 21 12:15:49 CST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip

gradle-wrapper的作用就是使用統一的方式來管理gradle,保證gradle使用的是統一的版本。說明幾點:

android studio首先從distributionBase/distributionPath查詢gradle。
然後,從zipStoreBase/zipStorePath查詢gradle。
如果上述都沒有找到合適的gradle,則從distributionUrl指定的url去下載gradle。
注意:Linux中,這裡需要在.bashrc中增加GRADLE_USER_HOME的變數定義。

匯入github上的專案時,可以先更改專案中gradle資料夾下的gradle-wrapper.properties 中distributionUrl 的值為已有的gradle版本,然後再用AS開啟專案,這樣就不用到網上下載。