fred-ye / summary

my blog
43 stars 9 forks source link

[Android]Gradle & Android #59

Open fred-ye opened 7 years ago

fred-ye commented 7 years ago

Gradle和 Gradle Wrapper

在使用Gradle前我们是不需要安装Gradle的。此时我们需要借助Gradle Wraper, Gradle Wrapper的工作方式是先通过脚本来判断需要Gradle版本是否安装,如果没有则会先去下载对应版本的Gradle。该脚本对应的linux版本是gradlew, windows版本是gradlew.bat

Gradle的安装

如果想在终端中使用Gradle,我们需要手动去安装Gradle

unzip ~/Downloads/gradle-3.3-all.zip -d /usr/local/gradle/ &&\
echo '# Adding Gradle to system path
export GRADLE_HOME=/usr/local/gradle/gradle-3.3
PATH=$GRADLE_HOME/bin:$PATH
export PATH' >> ~/.bash_profile &&\
source ~/.bash_profile

Gradle工程

于构建脚本中每个项目,Gradle 都创建了一project类型的对象用来关联此项目. 当构建脚本执行时,它会去配置所关联的工程对象.

在默认情况下Gradle将当前目录下的build.gradle文件作为项目的构建文件 在默认情况下Gradle为我们提供了几个常用的Task, 如查看项目的属性(gradle properties),显示当前项目中定义的所有Task(gradle tasks)

Groovy基础

在跑一些Groovy脚本时,我们不需要安装Groovy环境,因为gradle中已经包含了groovy环境,只需要将groovy代码置于build.gradle文件中,在build.gradle文件中添加声明

task groovy << {}

动态输入

groovy的类型检测是发生在运行时的,类似于很多脚本语言,我们在定义变量的时候不需要声明类型

def foo = 6.5
println "foo has value: $foo"
println "Let's do some math. 5 + 6 = ${5 + 6}"
println "foo is of type: ${foo.class} and has value: $foo"
foo = "a string"
println "foo is now of type: ${foo.class} and has value: $foo"

函数

def doubleIt(n) {
    n + n
}

groovy中函数的定义是可以没有返回值的,默认将最后一个表达式的结果作为返回值,函数在调用的时候可以不用括号,如下:

doubleIt 5

闭包

闭包的声明

def foo = 'test'
//类似于这种直接用花括号声明一个函数,我们称之为闭包
def myClosure = {
println "Hello from a closure"
println "The value of foo is $foo"
}
//闭包的调用
myClosure()

//闭包也可以在不同的变量之间传递 def bar = myClosure def baz = barb baz();


> ##### 参数的传递

```groovy
def doubleIt = { x -> x + x}

闭包为函数的参数设置了不同的语法, 类似于函数中的参数列表全部都在->的左侧, 同时groovy中函数的参数可以是一个函数类型,如下:

def doubleIt = { x -> x + x}
def applyTwice(func, arg) {
    func(func(arg))
}
def foo = 5;
def fooDoubleTwice = applyTwice(doubleIt, foo)
println fooDoubleTwice  // 20

列表的遍历

def myList = ["Gradle", "Groovy", "Android"]
def printItem = {item -> println "List item: $item"}
myList.each(printItem)

更简洁的方式

myList.each{println "Compactly println each list item: $it"}

如果闭包(函数)只使用了一个参数,那么该参数可以直接使用闭包

Task

Task的定义与执行

方式1:

task helloworld << {
    println "Hello World!"
}

这里的"<<"表示向任务helloworld中加入执行代码, 我们可以用doLast达到同样的效果

task helloworld {
    doLast {
        println "Hello World!"
    }
}

方式2:

每个gradle构建的项目中维护了一个TaskContainer类型的属性tasks。而TaskContainer中有create方法,于是可以用create来创建一个task:

tasks.create(name: 'hello') << {
   println "Hello World!"
}

Task间的依赖

task task1 << {
println "task1!"
}
task task2(dependsOn:task1) << {
println "task2!"
}

也可以在定义完成后再声明依赖:

task task1 << {
    println "task1!"
}
task task2 << {
    println "task2!"
}
task2.dependsOn task1

Task的配置

一个Task除了执行操作外,还可以有多个property, 例如copy的from和to,我们可以采用以下两种方式对task进行配置

  1. 在定义Task的时候对property进行配置

    task hello << {
    description = "hello task"
    println description
    }
  2. 通过闭包的方式配置已有的Task:

    task hello << {
    println description
    }
    hello {
    description = "hello task"
    }
  3. 利用Task的configure()方法完成property的设置

    task hello << {
    println description
    }
    hello.configure {
    description = "this is hello"
    }
  4. 直接添加属性

task hello << {
    println description
}
hello.description = "hello task"

对于每一个Task,gradle会在项目中创建一个同名的property, 所以可以直接这样添加属性。此外,对于每一个task, gradle还会创建一个同名的方法,该方法接受闭包,我们的第二种配置方式便是这个原理。

Task的增量构建机制

我们为每个Task定义输入(inputs)和输入(outputs),如果在执行一个Task时,如果它的输入和输出与前一次执行时没有发生变化,那么Gradle便会认为该Task是最新的(UP-TO-DATE),因此Gradle将不予执行。

Gradle中的property

Gradle在默认情况下已经为项目定义了很多的property, 比较常用的有:

看下面一个Demo:

version ='1.0'
description = 'project description'

task showProjectProperties << {
    println version
    println project.description
}

在打印description时,我们使用了project.description,而不是直接使用description。原因在于,Project和Task都拥有description属性,而定义Task的闭包将delegate设置成了当前的Task,故如果直接使用description,此时打印的是showProjectProperties的description,而不是Project的,所以我们需要显式地指明project

可以采用多种方式来定义项目的property, 如下:

在build.gradle中定义property

在build.gradle文件中向Project添加额外的Property时,我们并不能直接定义,而是应该通过ext来定义。

ext.property1 = "property1 value"

也可以通过闭包的方式:

ext {
    property2 = "property2 value"
}

在定义了property后,合用这些property时则不需要ext,可以直接访问

task showProperties << {
    println property1
    println property2
}

通过命令行参数定义Property

Gradle提供了-P命令行参数来设置Property. 如:

task showProperty << {
    println property3
}

执行时:

gradle -Pproperty3="this is property3" showProperty

通过JVM系统参数定义Property

gradle -Dorg.gradle.project.property3="this is another property3" showCommandLieProperties 每个property都要以org.gradle.project.为前缀

通过环境变量设置Property

export ORG_GRADLE_PROJECT_property3="this is yet another property3" 每个property都要以ORG_GRADLE_PROJECT_为前缀

java plugin

每一个plugin都会向project中引入多个Task和Property, java plugin向项目中引入了构建生命周期的概念。

需要注意的是Gradle的项目构建生命周期并不是Gradle的内建机制,而是由Plugin自己引入的。例如java plugin引入的一个名为build的task, 但该task又依赖于其他的Task, 其它的Task又依赖于另外的Task,所以就有了一个Task执行列表,这个执行列表就描建生命周期的概念。

java plugin还向Project中加了一些额外的属性,如sourceCompatibility用于指定在编译java源文件时所使用的java版本。

依赖的管理

在声明对第三方类库的依赖时,我们需要告诉Gradle从什么地方去获取这些依赖,即配置Gradle的Repository, 在配置好了之后,Gradle会自动从Repository中下载需要的包到本地。

配置Repository:

repositories {
    jcenter()
}

对于具体的包添加依赖有以下几种方式:

dependencies {
    compile 'com.google.guava:guava:18.0'
}

dependencies {
    compile group: 'com.google.guava', name: 'guava', version: '18.0'
}

dependencies {
    compile files('libs/foo.jar', 'libs/bar.jar')
}

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
}

添加aar依赖

假设已将需要的aar包放入libs目录下,接下来进行如下配置:

repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    compile(name:'component-debug', ext:'aar')
}

添加依赖的module(project)

dependencies {
    ...
    compile project(':TrainManager')
    ...
}

注意该module已将加在settings.gradle中的。

compile, testCompile和androidTestCompile

dependencies {
    compile 'com.android.support:appcompat-v7:23.1.0'
    compile 'com.android.support:design:23.1.0'
    compile 'com.squareup.okhttp:okhttp:2.5.0'
    compile 'com.youth.banner:banner:1.4.4'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testCompile 'junit:junit:4.12'
    compile project(':volley-release')
}

testCompile, androidTestCompile 中的第三方库仅在做测试的时候用到。testCompile关联的是工程下的test目录,androidTestCompile关联的是工程下的androidTest目录

查看依赖关系

多工程

此处指的是一个Gradle构建的项目中有多个工程(module)。如我们当前的项目结构:

FlightManager_Android_studio
    --FlightManager_Main
    --TrainManager
    --component
    --Hotel
    --volley-release
    --settings.gradle
    --build.gradle

构建这种项目,首先需要将各个子项目在setting.gradle中配置,即如下:

include ':FlightManager_Main', ':component',':Hotel', 'volley-release'
include ':TrainManager'

外层的build.gradle是对项目全局的配置,里面的配置会应用到各个子项目中,每个子项目中也会有build.gradle是对子项目的配置。

  1. 我们在外层的build.gradle中添加如下示例代码:
    allprojects {
    repositories {
        jcenter()
    }
    task allTask << {
        println project.name
    }
    }

执行gradle allTask结果如下:

:allTask
:TrainManager:allTask
:FlightManager_Main:allTask
:Hotel:allTask
FlightManager_Main
Hotel
FlightManager_Android_Studio
TrainManager
:volley-release:allTask
volley-release
:component:allTask
component

BUILD SUCCESSFUL

allprojects中的配置将会用到所有的项目中,Gradle中还提供了subprojects用来配置所有的子project(不包含根project), 同样,我们在build.gradle中加入如下代码:

subprojects {
   task subTask << {
      println project.name
   }
}

执行gradle allTask结果如下:

:FlightManager_Main:subTask
:Hotel:subTask
FlightManager_Main
Hotel
:component:subTask
component
:TrainManager:subTask
TrainManager
:volley-release:subTask
volley-release

会发现父project FlightManager_Android_Studio就不会出现了。

使用Gradle构建Android

在Android Studio中运行gradle脚本有两种方式:1 是打开底部的Terminal, 2 是打开右侧面板中的Gradle projects, 然后再选择对应项目中的Task, 双击执行。

对于一个Android Studio工程,采用的是多项目构建的方式。Android应用本身就是一个子项目。

Android plugin

在外层的build.gradle中声明的代码:

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

里面的com.android.tools.build:gradle:2.2.1便是指定gradle Android插件版本

在内层(Android应用)的build.gradle

grandroid {
    compileSdkVersion //required
    buildtoolsVersion //required

    defaultConfig { //针对manifest.xml的配置
        applicationId
        minSdkVersion
        targetSdkVersion
    }
}

buildTypes

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

productFlavors

如一个产品分为免费版和付费版

android {
    productFlavors {
        free {
            applicationId ""
        }
        paid {
            applicationId ""
        }
    }
}

针对某个productFlavor添加依赖 如针对免费版添加依赖

dependencies {
    freeCompile ""
}

Task的配置

applicationVariants.all {
if (buildTypes.name == 'debug') {
javaCompile.options.compilerArgs = ['-verbose']
}
}

签名

android { signingConfigs { config { keyAlias 'udacity' keyPassword 'password' storeFile file("$rootDir/keystore.jks") storePassword 'password' } } compileSdkVersion 24 buildToolsVersion "24.0.1" defaultConfig { applicationId "com.udacity.gradle.signing" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.config } } }

当我们设置了签名之后,在执行`:app:packageRelease`之前会先执行`:app:validateReleaseSigning`

签名的配置通常建议单独写一个文件进行保存,如下:

**新建一个配置文件**`keystore.properties`

storePassword=myStorePassword keyPassword=mykeyPassword keyAlias=myKeyAlias storeFile=myStoreFileLocation


**在module的build.gradle文件中加载配置文件**

```groovy
..

// Create a variable called keystorePropertiesFile, and initialize it to your
// keystore.properties file, in the rootProject folder.
def keystorePropertiesFile = rootProject.file("keystore.properties")

// Initialize a new Properties() object called keystoreProperties.
def keystoreProperties = new Properties()

// Load your keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
    ...
}

指定文件配置

android {
    signingConfigs {
        config {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }
    ...
  }

multi dex

android {
...
defaultConfig {
...
multiDexEnabled true
}
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}

multi dex还需要在代码中进行相应的配置, 这里不展表

创建java库和Android库

创建java库

如我们有一个项目,其结构如下:

Demo
--app
  --build.gradle
--javalibrary
  --build.gradle
--build.gradle
--setting.gradle

javalibrary是我们创建的一个用来作为库的子项目,首先我们需要在其目录下的build.gradle中添加apply plugin: "java"代码,如下:

apply plugin: "java"
repositories {
    jcenter()
}

在项目的setting.gradle项目中添加该项目,对于需要用到该java库的module, 在其build.gradle中加入

dependencies {
    compile project(':javalibrary')
}

创建Android库

和上面的创建java库类似,只是在库对应项目下的build.gradle中加入

apply plugin: 'com.android.library'

同样在需要用到该库的项目中加入

dependencies {
    compile project(':android库的名称')
}

最终会在库项目的build/outputs/aar/目录下生成一个**.aar文件供其它项目引用.

参考

  1. http://www.cnblogs.com/davenkin/p/gradle-learning-1.html
  2. http://google.github.io/android-gradle-dsl/current/index.html
  3. Gradle Android Plugin手册
  4. Gradle Android Plugin手册(中文版)
  5. udacity上的课程和对应的代码