Gradle for Android 淺析

概述

Gradle是一個基於Apache AntApache Maven概念的項目自動化建構工具。它使用一種基於Groovy的特定領域語言來聲明項目設置,而不是傳統的XML。當前其支持的語言限於Java、Groovy和Scala,計劃未來將支持更多的語言。

Gradle可以做哪些事呢

差異管理
多渠道打包,根據渠道的不同實現差異化(例如,不同的簽名文件,不同的icon,不同的服務器地址)等。
依賴管理
我們的應用可以依賴不同的jar, library. 你當然可以通過將.jar/library工程下載到本地再copy到你的工程中。但是隨着依賴增加還有各種更新,維護起來複雜性可想而知。有一個中央倉庫的東西,在這個倉庫裏你可以找到所有你能想要的依賴包。而你所做的只需要指定一下座標即可。例如:
compile 'com.squareup.picasso:picasso:2.3.3'
通過這種方式,我們可以很方便的實現依賴的裝載卸載。
項目部署
自動將你的輸出(.jar,.apk,.war…)上傳到指定倉庫,自動部署…

Gradle學習

學習Gradle,需要學習,groovy語言,Gradle DSL學習,Android Plugin DSL學習,Gradle task學習。

groovy語言

Gradle基於groovy語言,groovyjava都是基於jvm,理解相對容易些,而且日常開發不用學習那麼多 教程

Gradle DSL

常用的 build.gradle, Gradle DSL 傳送門

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
        classpath 'com.novoda:bintray-release:0.4.0'

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

allprojects {
    repositories {
        jcenter()
        maven {url 'https://dl.bintray.com/calvinning/maven'}
    }
}

那麼buildscript中的 repositories 和allprojects的 repositories 的作用和區別是什麼呢?
1、 buildscript裏是gradle腳本執行所需依賴,分別是對應的maven庫和插件
2、 allprojects裏是項目本身需要的依賴,比如我現在要依賴我自己maven庫的toastutils庫,那麼我應該將maven {url 'https://dl.bintray.com/calvinning/maven'}寫在這裏,而不是buildscript中,不然找不到。

ext屬性

ext 定義

ext {
    compileSdkVersion = 25
    buildToolsVersion = "26.0.0"
    minSdkVersion = 14
    targetSdkVersion = 22
    appcompatV7 = "com.android.support:appcompat-v7:$androidSupportVersion"
    //建了一個map,且名字叫做android。
    android = [
            compileSdkVersion: 23,
            buildToolsVersion: "23.0.2",
            minSdkVersion    : 14,
            targetSdkVersion : 22,
    ]
}

根據ext屬性的官方文檔,ext屬性是ExtensionAware類型的一個特殊的屬性,本質是一個Map類型的變量,
ExtentionAware接口的實現類爲Project, Settings, Task, SourceSet等,ExtentionAware可以在運行時擴充屬性,而這裏的ext,就是裏面的一個特殊的屬性而已。

ext使用
訪問變量 通過rootProject.ext.compileSdkVersion方式。對於數組類型rootProject.ext.android[compileSdkVersion]這種方式。

使用ext屬性的優勢。ext屬性可以伴隨對應的ExtensionAware對象在構建的過程中被其他對象訪問,例如你在rootProject中聲明的ext中添加的內容,就可以在任何能獲取到rootProject的地方訪問這些屬性,而如果只在rootProject/build.gradle中用def來聲明這些變量,那麼這些變量除了在這個文件裏面訪問之外,其他任何地方都沒辦法訪問。

注意,如果ext是自己定義的gradle文件,如(denpendies.gradle, config.gradle)需要自己手動導入,可以在根‘build.gradle’的buildscript中添加, apply from: 'dependencies.gradle'

gradle.properties文件

詳細介紹,傳送門
gradle.properties裏面定義的屬性是全局的,可以在各個模塊的build.gradle裏面直接引用. 且可以在java代碼中訪問。

注意:在gradle.properties中定義的屬性默認是String類型的,如果需要int類型,需要添加XXX as int後綴。
第一步,在gradle.properties 定義,如下:

# Java 代碼使用
DEFAULT_NICK_NAME=maolegemi
DEFAULT_NUMBER=10086

# xml文件調用
USER_NAME=wangdandan
TEXT_SIZE=20sp
TEXT_COLOR=#ef5350

第二步, 在build.gradle中配置

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

        debug{
            // Java代碼調用, 就會在BuildConfig.java 生成文件,注入這邊變量,見下面java使用
            buildConfigField "String", "defaultNickName", DEFAULT_NICK_NAME
            buildConfigField "Integer", "defaultNumber", DEFAULT_NUMBER

            // xml佈局文件調用,就會在gradleResValues.xml 注入這些變量。在代碼中,就可以訪問
            resValue "string", "user_name", "${USER_NAME}"
            resValue "dimen", "text_size", "${TEXT_SIZE}"
            resValue "color", "text_color", "${TEXT_COLOR}"
        }
    }
}

第三步,在代碼中使用(引用 BuildConfig.java 和 gradleResValues.xml)

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        System.out.println("defaultNickName:" + BuildConfig.defaultNickName);
        System.out.println("defaultNickName.type:" + BuildConfig.defaultNickName.getClass().getSimpleName());
    }

Android Plugin DSL

常用的build.gradle, Android Plugin DSL傳送門

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.joker.cliptest"
        minSdkVersion 21
        // ...
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    // ...
}

在android閉包下,也可以指定代碼目錄

   sourceSets {
        main {
        	// 指定so庫的存放位置
            jniLibs.srcDirs = ["libs"]
            //指定資源文件目錄
            res.srcDirs = ["src/main/res", "src/main/res-play", "src/main/res-shop"]
        }
   }

多渠道打包
productFlavors 也是android plugin dsl的內容,關於多渠道打包,這裏不單獨介紹,看這篇內容就可以了。傳送門

    //多渠道打包,在此配置
    productFlavors {
        xiaomi {
            resValue("string", "app_name", "小米ktLearn")
        }
        huawei {
            resValue("string", "app_name", "華爲ktLearn")
        }

    }

Gradle 構建生命週期

Gradle的構建過程分爲三部分: 初始化階段、配置階段 和 執行階段。其構建流程如下圖所示:
在這裏插入圖片描述

初始化階段

在這個階段中,會讀取根工程中的 setting.gradle 中的 include 信息,確定有多少工程加入構建,然後,會爲每一個項目(build.gradle 腳本文件)創建一個個與之對應的 Project 實例,最終形成一個項目的層次結構。
此外,我們也可以在settings.gradle文件中,指定其他project的位置,這樣就可以將其他外部工程的module導入到當前的工程之中了。

if(useLocal) {
	File projectFile = new File('../picBook/app')
    include ':picBook'
    project(':picBook').projectDir = projectFile
}

配置階段

執行各項目下的 build.gradle 腳本,完成 Project 的配置,與此同時,會構造 Task 任務依賴關係圖以便在執行階段按照依賴關係執行 Task. 而在配置階段執行的代碼通常來說都會包括以下三個部分的內容,如下所示:

  1. build.gralde 中的各種語句。
  2. 閉包。
  3. Task 中的配置段語句。

注意,執行任何 Gradle 命令,在初始化階段和配置階段的代碼都會被執行。

執行階段

Gradle 根據各個任務 Task 的依賴關係來創建一個有向無環圖。Gradle 構建系統通過調用 gradle <任務名> 來執行相應的各個任務。

Project

Project API

對應org.gradle.api包下,Project.java的方法。

getAllprojects
獲取當前工程下所有的project實例(含當前工程), 在不同的build.gradle下調用返回不一樣。在根build.gradle下會返回所有的project.
getSubprojects
獲取當前工程下所有子工程的project實例,返回的set除了不包含當前工程,其他同getAllprojects.
getParent
獲取當前project的父類.如果我們在根工程中使用它,返回爲null.
getRootProject
返回根工程的project實例。
project
指定工程的實例。有幾種重載方法。
我們常用的

implementation project(':xbase')

還有一種加閉包的重載,可對工程進行配置。

	//擴展ext方法
	ext.getRealDep = { projectName ->
        def aarName = rootProject.ext.project[projectName]
        if (aarName) {
            //如果在ext.project 配置了,使用配置的倉庫路徑
            println "\n use aar [$projectName] "
            return aarName
        } else {
            //沒有在ext.project 配置,則使用當前路徑的工程
            Project project = project(":" + projectName)
            if (project != null && project.getProjectDir() != null 
            && project.getProjectDir().exists()) {
                println "\nready to handle project [$projectName] real dependency"
                return project
            }
        }
        throw IllegalArgumentException("no project found for :$projectName")
    }

allprojects
用於配置當前project及其旗下的每一個子project.

allprojects {
    repositories {
        maven { url "https://jitpack.io" }
        maven { url 'https://repo.rdc.aliyun.com/repository/xxxx/'
        	//一般私有倉庫,需要驗證用戶密碼這樣設置
            credentials {
                username 'namexxx'
                password 'pwdxxx'
            }
        }
        maven { url 'https://dl.bintray.com/umsdk/release' }
        google()
        jcenter()
    }
}

subprojects
統一配置當前 project 下的所有子 project

subprojects {
    //當前 project 旗下的子 project 是不是庫,如果是庫纔有必要引入 publishToMaven 腳本
    if (project.plugins.hasPlugin("com.android.library")) {
        apply from: '../publishToMaven.gradle'
    }
}

project屬性

project.java接口中,僅僅定義了七個屬性

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
    /**
    * 默認的工程構建文件名稱
    */
    String DEFAULT_BUILD_FILE = "build.gradle";
    /**
    * 區分開 project 名字與 task 名字的符號
    */
    String PATH_SEPARATOR = ":";
    /**
    * 默認的構建目錄名稱
    */
    String DEFAULT_BUILD_DIR_NAME = "build";
    String GRADLE_PROPERTIES = "gradle.properties";
    String SYSTEM_PROP_PREFIX = "systemProp";
    String DEFAULT_VERSION = "unspecified";
    String DEFAULT_STATUS = "release";
    ...
}

幸運的是,Gradle 提供了 ext 關鍵字讓我們有能力去定義自身所需要的擴展屬性。有了它便可以對我們工程中的依賴進行全局配置。
見上面ext使用
gradle.properties下定義擴展屬性。

文件相關的API

獲取路徑

//getRootDir返回file指向根目錄
getRootDir().absolutePath
//getBuildDir返回file指向 '根目錄/build'
getBuildDir().absolutePath
//getProjectDir返回file指向 當前工程目錄, 如'根目錄/xlist'
getProjectDir().absolutePath

文件相關

//返回一個File
def mFile = file(pathxxx)
// 文件的拷貝
copy {
	from file("build/outputs/apk")
	into getRootProject().getBuildDir().path + "/apk/"
	exclude {
		//排除不需要拷貝的文件
	}
	rename {
		//對拷貝過來的文件進行重命名
	}
}
//fileTree方法
// 用法1
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 用法2
fileTree("build/outputs/apk") { FileTree fileTree ->
	fileTree.visit { FileTreeElement fileTreeElement ->
		println "The file is $fileTreeElement.file.name"
		copy {
			from fileTreeElement.file
			into getRootProject().getBuildDir().path + "/apkTree/"
		}
	}
}

其他
關於app module下的依賴,除了可以添加依賴,也可以移除, 需要用到 exclude,另外還有一個transive方法,控制是否傳遞依賴。configurationsproject中方法,他們都是類Configuration中的方法。projectconfigurations方法。

if (local_reader.toBoolean())
    configurations {
        implementation {
            exclude group: 'com.tdhLearn.base', module: 'core'
            exclude group: 'com.tdhLearn.base', module: 'push'
            transitive false
        }
    }

exec 執行外部命令。

Task

Gradle task 與我們是最爲緊密的。日常開發中開發者難免會進行 build/clean projectbuild apk 等操作。實際上這些按鈕的底層實現都是通過 Gradle task 來完成的,只不過 IDE 使用 GUI 降低開發者們的使用門檻。

task的使用

我們可以在任意一個build.gradle文件中定義Task

task tian {
    //這段代碼執行在配置階段
    println "exec tian task"
    //doFirst, doLast執行在 gradle執行階段
    doFirst {
        println "~~~tian task start"
    }
    doLast {
        println "~~~tian task end"
    }
}

// 任務依賴 tian
task fang(dependsOn:"tian") {
    doLast {
        println "~~~fang task end"
    }
}

執行

# 根目錄下的task
./gradlew task :tian
# 在其他module下的task
./gradlew task :app:tianThird

Task的屬性

task 組的概念,會對task進行分組,在gradle標籤下,我們可以看到。descriptiontask添加描述,相當於task的註釋,在打印task時會展示出來(如下圖)。
task的描述

// group定義組,description爲task添加描述。
task tianSec(group: "TianTask", description: "this is my test task") {
    println "~~~tianSec configure"
}

屬性擴展
我們也可以 使用 ext 給 task 自定義需要的屬性

task tian(group: "TianTask") {
    //擴展task的屬性
    ext.good = "hello tian"
}

//定義 組&描述
task tianSec(group: "TianTask") {
    doLast {
        //訪問tian的擴展屬性
        println "~~~I can get goodValue $tian.good"
    }
}

task類型
使用type屬性來直接使用一個已有的task類型。

// 刪除根目錄下的build文件
task clean(type: Delete) {
	delete rootProject.buildDir
}

// 將doc複製到 build/target目錄下
task copyDocs(type: Copy) {
	form  'src/main/doc'
	into 'build/target/doc'
}

// 執行時複製源文件到目標目錄,然後從目標目錄刪除所有非複製文件
task syncFile(type: Sync) {
	from 'src/mian/doc'
	into 'build/target/doc'
}

每個 task 都會經歷 初始化、配置、執行 這一套完整的生命週期流程。

參考資料

Android 開發者的 Gradle 系列
Android多渠道打包
深度探索Gradle自動化構建技術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章