04、Android--Gradle基础 Gradle

Gradle是Android项目的主流编译工具,是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。

编译周期

在解析Gradle的编译过程中会涉及到Gradle非常重要的两个对象:ProjectTask

每个项目编译至少有一个Project,其中一个build.gradle就代表一个Project,而每个Project又包含多个Task,其中Task又包含多个Action,而Action是一个代码块,里面包含需要被执行的代码,它们的包含关系如下:

Project > Task > Action

在编译过程中,Gradle会根据Build相关文件来聚合所有的Project和Task,并执行Task中的Action。

所有的Task的执行都按照一定的逻辑顺序,这种逻辑称之为依赖逻辑

编译过程分为三个阶段:

初始化阶段:创建Project对象,如果有多个build.gradle则会创建多个Project。

配置阶段:会执行所有的编译脚本,同时还会创建Project中的所有Task,为下个阶段做准备。

执行阶段:Gradle会根据传入的参数决定执行这些Task,真正的Action执行代码就在这里。

Gradle结构

Android项目中的Gradle最基础的文件配置如下:

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

一个项目包含一个settings.gradle、包括一个顶层的build.gradle文件,每个Module都有各自的build.gradle文件。

setting.gradle

setting.gradle中的定义的是哪些Module应该被加入到编译过程,对于单Module项目可以不用该文件。对于多Module项目则需要该文件来指示需要加载哪些Module,它的代码在初始化阶段就会被执行。

include ':app'
include ':Module1'
include ':Module2'    
rootProject.name = "MyApp"

多个Module通过include标签进行标明,项目就会根据指定的Module进行加载操作。

Project - build.gradle

一般Android项目中包含两个build.gradle,Project最顶层的build.gradle文件的配置会被应用到所有项目中。

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

buildscript{}定义了Android编译工具的类路径,其中repositories里是gradle脚本执行所需的依赖,分别是对应Maven和插件。

allprojects{}中定义的属性会被应用到所有的Module中,但是要保证项目的独立性,不要在这里操作过多共有的配置。

App - build.gradle

每个Module都有单独的build.gradle,可以针对不同的Module进行配置,如果这里的配置和顶层的build.gradle相同的话,后者将会覆盖前者。

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.legend.demo"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
    ......
}

plugins{}指定了Android程序的Gradle插件,plugin中提供了Android编译、测试和打包的所有Task。在早期的Gradle版本中不是使用id的方式指定插件的,而是通过如下方式(两种都可以使用):

apply plugin: 'com.android.application'

android{}是编译文件中最大的代码块,关于Android所有的特殊配置都在这里。其中'defaultConfig{}'就是程序的默认配置,如果在AndroidMainfest.xml文件定义相同的属性的话,会以这里的为准。

buildTypes{}定义了编译类型,针对不同的类型可以有不同的配置和编译命令。默认的有debug和release选项。

buildTypes{
    // 发布类型
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    // 测试类型,给测试人员
    debug {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    // 本地类型,和后端联调使用
    local {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}

增加这些配置后,会在Android Studio的Build Variants面板中看到debuglocal两个构建类型,点击运行即可构建。

Gradle Wrapper

Gradle不断的更新版本,新版本会对以往项目存在向后兼容的问题,所以Gradle Wrapper就应运而生。
在Android Studio构建的项目中会自带gradle-wrapper.jar文件,它还拥有一个配置文件:Project/gradle/wrapper/gradle-wrapper.properties,它的内容如下所示:

#Sun Jan 17 11:15:10 CST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-6.5-bin.zip

distributionUrl是要下载的gradle地址以及版本,gradle-wrapper会去wrapper/list目录下查找,如果没有对应版本的gradle采取下载,当然我们也可以手动下载Gradle版本放入相应目录即可。

全路径为C:Users<user_name>.gradlewrapperdistsgradle-x.x-bin<url-hash>

Gradle有三种类型的版本:

1、gradle-xx-all.zip是完整版,包含了各种二进制文件,源代码文件,和离线的文档。

2、gradle-xx-bin.zip是二进制版,只包含了二进制文件(可执行文件),没有文档和源代码。

3、gradle-xx-src.zip是源码版,只包含了Gradle源代码,不能用来编译你的工程。

如果是直接从eclipse 中的项目转换过来的,程序并不会自动创建wrapper脚本,我们需要手动创建。在命令行输入以下命令即可

gradle wrapper --gradle-version 2.4

它会自动构建相应的目录结构。

Gradle 命令

Gradle 会根据build 文件的配置生成不同的task,我们可以直接单独执行每一个task。

gradlew tasks:列出项目中所有的task。

以上命令可以在Android Studio的Terminal面板中使用,如果出现以下提示:

'.gradlew' 不是内部或外部命令,也不是可运行的程序或批处理文件。

出现这种情况则需要在Android Studio的Gradle面板下的build setup中执行wrapper即可解决。

Gradle命令的基本语法如下所示:

gradle [taskName...] [--option-name...]

gradlew是包装器,其中./gradlew、gradle是通用的。

命令待补充......

Android tasks

Gradle中有四个基本的task,Android继承它们并进行了自己的实现:

assemble:对所有的 buildType 生成 apk 包。
clean:移除所有的编译输出文件,比如apk
check:执行lint检测编译。
build:同时执行assemble和check命令

在实际项目中会根据不同的配置,会对这些task 设置不同的依赖。比如 默认的assmeble会依赖 assembleDebug 和assembleRelease,如果直接执行assmeble,最后会编译debug和release 的所有版本出来。我们运行的许多命令除了会输出到命令行,还会在build文件夹下生产一份运行报告。

BuildConfig

我们可以通过BuildConfig.DEBUG来判断当前版本是否是debug版本,以此输出只有在debug环境下才会执行的操作。这个类是有gradle根据配置文件生成的。其中Gradle使用的是Groovy语言,它是一种JVM语言,所以即使语法不同,它们最终都会生成JVM字节码文件。

在app/gradle.build中的android{} 下有一个buildTypes{}可以配置key-value的键值对,它的功能非常强大。

比如我们可以为debug何release两种环境定义不同的服务器地址,比如:

android {
	......
    buildTypes{
        release {
            buildConfigField "String", "API_URL", ""http://10.0.0.1/api/""
            buildConfigField "Boolean", "LOG_HTTP_CALLS", "true"
            ......
        }
        debug {
            buildConfigField "String", "API_URL", ""http://legend.com/api/""
            buildConfigField "Boolean", "LOG_HTTP_CALLS", "false"
            ......
        }
    }
	......
}

除此之外,我们还可以为不同编译类型来设置不同的资源文件,比如:

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

然后在BuildConfig.java文件中会生成相应的静态变量,直接引用即可。

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.legend.demo";
  public static final String BUILD_TYPE = "debug";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
  // Field from build type: debug
  public static final String API_URL = "http://legend.com/api/";
  // Field from build type: debug
  public static final Boolean LOG_HTTP_CALLS = false;
}

共享变量

在Android开发过程中,一个项目会有多个Module,如果想保持所有的Module和主Module的配置保持相同的话,可以在这里配置,具体步骤如下:

1、在Project的build.gradle中定义版本号的常量,使用ext{}包裹:

......
// Define versions in a single place
ext {
    compileSdkVersion = 30
    buildToolsVersion = "30.0.3"
    applicationId = "com.legend.demo"
    minSdkVersion = 16
    targetSdkVersion = 30
    versionCode = 1
    versionName = "1.0"
}
......

2、在app下的build.gradle中使用 $rootProject.xxx的方式引用即可。

......
android {
    compileSdkVersion rootProject.compileSdkVersion
    buildToolsVersion rootProject.buildToolsVersion
    defaultConfig {
        applicationId rootProject.applicationId
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode rootProject.versionCode
        versionName rootProject.versionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
	......
}    
......    

Repositories

Repositories 就是代码仓库,我们平时的添加的一些 dependency 就是从这里下载的,Gradle 支持三种类型的仓库:

Maven、Ivy、以及一些静态文件或者文件夹

gradle 支持多种Maven仓库,除了默认的jCenter()外,还可以添加国内镜像或公司的私有仓库:

repositories {
    maven {
        url "http://repo.legend.com/maven"
    }
}

如果仓库存在密码,也可以同时传入用户名和密码

repositories {
    maven {
        url "http://repo.legend.com/maven"
        credentials {
            username 'legend'
            password '123456'
        }
    }
}

我们还可以使用相对路径配置本地仓库,以此来引用项目中存在的静态文件夹作为本地仓库:

repositories {
    flatDir {
        dirs "aars"
    }
}

如果Gradle下载依赖过慢的情况下我们可以在这里配置阿里云的镜像:

buildscript {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google/' }
        maven { url 'https://maven.aliyun.com/repository/jcenter/'}
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }        
}

allprojects {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google/' }
        maven { url 'https://maven.aliyun.com/repository/jcenter/'}
    }
}

Dependencies

我们在引用库的时候,每个库名称包含三个元素:组名:库名称:版本号,如下:

dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
}

如果我们要保证我们依赖的库始终处于最新状态,我们可以通过添加通配符的方式,比如:

dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0.+'
    implementation 'com.google.android.material:material:1.1.0.+'
}

使用这种做法每次编译都会去网络请求查看新版本,导致编译慢之外,新版本也可能存在很多问题。

依赖方式

dependencies{}中库的引入有三种方式:compile、implementation、api,它们的区别如下所示:

compile:gradle升级3.+版本后,原来的依赖方法compile替换成了implementation和api,其中api是用来替换compile的。

implementation:当前Module的依赖,使用implementation指令的依赖不会传递。(只编译当前模块,构建速度快)

api:等同于compile,是用来替代compile的方式,使用api指令的依赖会传递。(编译所有模块,构建速度慢)

所谓的依赖传递的意思是:假如ModuleA依赖于ModuleB,而ModuleB依赖于ModuleC,那么ModuleA可以引用ModuleC中的依赖。

本地依赖

未完待续