Gradle 构建Android Studio 项目详解

Gradle入门

每一个基于gradle构建的项目,都应该至少有一个build.gradle 文件
Android的构建文件中, 有一些元素是必须的 比如

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

这就是实际构建配置的地方 。在repositories 代码快中 jcenter库被配置为整个构建过程的依赖仓库
jcenter 是一个预配置的Maven仓库 不需要额外的配置

每一个Android项目都应该申请该插件

apply plugin: 'com.android.application'

如果你正在构建一个依赖库 那么需要申请library插件

apply plugin: 'com.android.library'

当你使用Android插件时 ,不仅可以配置针对Android的特殊约定 还可以生成只应用于Android的任务 。
下面的Android代码片段是由插件来定义的,可以配置在每个项目中:

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

}

这是配置Android特殊约定参数的一部分 Android插件为Android的开发需求提供了一整套DSL 。
唯一需要的参数属性是编译目标和构建工具

基本自定义构建

理解Gradle 文件

当用Androidstudio创建一个新项目时 , 会默认生成三个gradle文件
其中两个文件 settings.gradle 和 build.gradle 位于项目的根目录
另外一个build.gradle 文件则在 app模块内被创建

settings.gradle文件

对于一个只包含一个Android应用的新项目来说 settings.gradles文件应该是这样的
include ':app'
settings.gradle文件在初始化阶段被执行 并且定义了哪些模块应该包含在构建内

顶层构建文件

在项目中 所有模块的配置参数都应该在顶层build.gradle文件中配置 默认情况下其包含如下两个代码块

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

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

repositories 代码块配置仓库地址
dependencies 代码块用于配置构建过程中的依赖包
allprojects 代码块可用来声明那些需要被用于所有模块的属性

模块的构建文件

模块层的build.gradle 文件的属性只能应用在Android app模块, 它可以覆盖顶层build.gradle文件的任何属性
该模块的构建文件示例如下

apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"
    defaultConfig {
        applicationId "com.bhb.seniorcustomview"
        minSdkVersion 23
        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 'com.google.android.material:material:1.3.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

从下到下依次为三个模块 插件 Android 依赖包

1 插件 第一行用到了Android应用插件 该插件在顶层构建文件中被配置成了依赖
2 Android 在构建文件中 占比最大的是android代码块 。 该代码块包含了全部的Android特有配置,
这些配置之所以可被使用,是因为之前我们使用的Android插件
必须有的属性是
compileSdkVersion 用来编译应用的Android API版本
buildToolsVersion 构建工具和编译器使用的版本号
构建工具包含命令应用 如aapt、zipalign、dx和renderscript , 这些都被用来打包在打包应用时生成各种中间产物

def aultConfig代码块用于配置应用的核心属性
applicationId 应用唯一id
minSdkVersion 应用运行的最小API级别
targetSdkVersion 应用运行目标API级别
versionCode 1 应用版本号
versionName "1.0" 应用版本名

buildTypes 代码块用来定义如何打包和构建不同类型的应用 在后面会详细介绍

  1. 依赖包 依赖代码块是标准Gradle配置的一部分,其定义了一个应用或依赖项目的所有依赖包

任务入门

要想知道一个项目中有哪些任务可被使用, 则可以运行gradlew tasks 任务,该命令会打印出所有可用的任务

基础任务

Gradle的Android插件使用了Java基础插件 , 而Java基础插件又使用了基础插件
基础插件定义了 assemnle和clean 任务
java基础插件定义了 check和buildtasks任务
它们被用来定义插件之间的约定,约定如下:
assemble 集合项目的输出
clean 清理项目的输出
check 运行所有检查
build 同时运行assemble和check

Android任务

Android插件扩展了基本任务
assemble 为每个构建版本创建一个APK
clean 删除所有的构建内容 ,例如APK
check 运行Lint检查 如果发现问题即可终止构建
build 同时运行assemble和check

assemble任务默认依赖于 assembleDebug 和 assembleRelease 如果你添加了更多的构建类型,那么
就会有更多的任务 。 这意味着,运行assemble将会触发每一个构件类型,并进行一次构建操作

除了这些之外 Android插件还添加了一些新的任务
connectedCheck 在连接设备或模拟器上运行测试
deviceCheck 一个占位任务 , 专为其他插件在远端设备上运行测试
installDebug和installRelease 在连接的设备上安装特定版本
所有install任务都会有相关的 uninstall任务

BuildConfig 和资源

构建工具会生成一个叫做BuildConfig 的类,
你可以通过gradle来扩展该文件 这样在 debug和release时, 就可以拥有不同的常量
这些常量可用于切换服务器url或其他属性例如

buildTypes {

        debug {
            buildConfigField "String", "API_URL", "\"https://test.com.api\""
        }

        release {buildConfigField "String", "API_URL", "\"https://com.api\""
        }
    }

字符串必须用转义双引号括起来 这样才会生成实际意义上的字符串
在添加buildConfig之后 可以在代码中使用 BuildConfig.API_URL

项目范围的设置

如果在一个项目中 你有多个Android模块 并且每个模块都需要设置相同的编译版本
我们可以在顶层文件中定义build.gradle文件都能定故意的额外属性 然后将它们应用到模块中
添加额外属性需要通过ext代码块 例如

ext{
compileSdkVersion = 30
buildToolsVersion "30.0.3"
}

然后在app 的模块中使用
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}

项目属性

三种常用定义属性的方法
1 参考上面的例子 ext代码块
2 gradle.proterties 文件 例如: debugmode=false

  1. -p 命令行参数
    自定义任务例子
task printProterties << {
    if(project.hasProperty('cmd')){
        println cmd
    }else {
        println "not contains propterties"
    }
}

然后在命令行运行该命令
./gradlew printProterties -Pcmd="hello from comman line"

依赖管理

依赖仓库

依赖仓库可以认为是文件的集合 可以通过repositories代码块添加仓库地址
除了上面提到过的jcenter 也可以添加自己创建的maven仓库

allprojects {
    repositories {
        google()
        jcenter()


        //个人maven仓库
        maven {
            url "https://mymaven.com"
        credentials{
            username 'user'
            password 'password'
        }
        }

        //本地路径仓库
        maven {
            url "../file"
        }

        //本地aar路径仓库
        flatDir {
            dirs 'aars'
        }

        maven { url "https://jitpack.io" }

    }
}

然后在 dependencies 中添加具体需要依赖的项目
有两种写法
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation group:'androidx.appcompat' , name: 'appcompat' , version: '1.0.2'

文件依赖

// 添加本地依赖文件
implementation fileTree(dir: 'libs')
// 添加本地依赖文件 添加过滤 只有.jar文件会被依赖
implementation fileTree(dir: 'libs', include: ['*.jar'])

添加原生依赖库 so文件

Android插件支持原生依赖库 需要做的就是在模块层创建一个jniLibs文件夹 ,
然后为每个架构创建子文件夹 , 将so文件放在适当的文件夹中

如果此约定不生效 , 那么你可以在构建文件中设置相关位置
android {

sourceSets.main{
    jniLibs.srcDir 'src/main/libs'
}

}

项目依赖

implementation project(':library')

依赖关键字

compile                        该配置不仅会将依赖添加至类路径还会生成对应的APK
implementation            替代compile 类似于private引用的依赖只有本模块可以使用
api                                替代compile 类似于public引用的依赖本模块和引用本模块的项目都可以使用
apk                               该配置不会将依赖添加至类路径 只会生成对应的APK
provided                       该配置不会生成对应的APK 只会将依赖添加至类路径
testImplementation       只在test时有效
androidTestImplementation 只在androidTest时有效

语义化版本

版本化是依赖管理的重要部分 将依赖添加到JCenter等依赖库时 约定遵循了一套版本化规则 我们称之为语义化版本
在语义化版本中,版本数字的格式一般为 major.minor.patch 数字按照规则依次增加
1.当做不兼容的API变化时 major版本增加
2.当以向后兼容的方式添加功能时, minor版本增加
3.当修复一些bug时, patch版本增加

动态化版本

在某些情况下 你可能希望在每次构建应用时 都能获取到最新的依赖
想要实现这一点 最好的实现方式是使用动态化版本 例如

// 表示我们想要 1.0.x 最新的版本
implementation 'androidx.appcompat:appcompat:1.0.+'

// 表示我们想要获取最新的 1.x版本 x至少是0
implementation 'androidx.appcompat:appcompat:1.0+'

//表示我们想要获取最新的版本
implementation 'androidx.appcompat:appcompat:+'

创建构建Variant

前面提到过 每个由Androidstudio 创建的新项目都会生成debug和release构建类型
另外一个概念是product flavor 构建类型和product flavor的结合结果被称之为构建variant

构建类型

下面是AS创建的标准buildTypes代码块

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

创建构建类型

当默认的设置不够用时 我们可以很容易的创建自定义构建类型
新的构建类型只需要在buildtypes代码块中新增一个对象即可 例如 下面staging自定义构建类型

 debug {

            minifyEnabled false     // 打开代码压缩
            shrinkResources false   // 打开资源压缩
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            applicationIdSuffix ".debug"    //修改debug applicationid后缀
            versionNameSuffix '-debug'    // 修改debug 版本号后缀
        }

因为我们设置了applicationIdSuffix debug 和release 的applicationId不一致 所以可以同时在一台机器上安装两个版本

第二种写法

 staging.initWith(buildTypes.release)
        staging{
            applicationIdSuffix ".staging"
        }

赋值一个已有的类型 然后覆盖其中需要修改的属性

资源集合

配置buildtypes 之后 可以在对应的资源目录下配置 属于不同类型的 资源文件
例如 配置的buildtypes 有 debug release staging
那么目录结构如下
app---src--||
---debug ---||
---res --- strings.xml
---main
---java ---- package.com --- MainAvtivity.java
---release
---res --- strings.xml
--- staging
---res --- strings.xml

java 类型只能有一份 但资源文件 xml drawable等文件可以有多份 打不同的构建类型下读取不同的文件

创建 product flavor

创建product flavor 和构建类型类似 可以通过在productFlavor 代码块中添加新的Product flavor来创建

android {

    productFlavors{
        red{
            applicationIdSuffix 'com.gradleandroid.red'
            versionCode 3
        }
        blue{
            applicationIdSuffix 'com.gradleandroid.blue'
            versionCode 4
            minSdkVersion  14
        }
    }

product flavor 也可以和 buildtypes一样定义资源集合 方式和buildtypes一样
当然也可以和buildtypes组合起来定义资源集合 该文件夹的名称是 flavor名称+构建类型名称
例如 blue + release 的资源集合文件夹名称是 blueRelease

多种定制版本

在某些情况下 你可能想要更进一步 创建product flavor的结合体
例如 客户A 和 客户B 中都想要免费版和付费版 并且是基于相同的代码 不同的品牌
创建四种不同的flavor 意味着需要像这样设置多个拷贝 所以这不是最佳的做法
使用flavor 维度是结合flavor的有效方式 如下所示

android {

    flavorDimensions "color","price"

    productFlavors{
        red{
            flavorDimensions "color"
            applicationIdSuffix 'com.gradleandroid.red'
            versionCode 3
        }
        blue{
            flavorDimensions "color"
            applicationIdSuffix 'com.gradleandroid.blue'
            versionCode 4
            minSdkVersion  14
        }
        free{
            flavorDimensions "price"
        }
        paid{
            flavorDimensions "price"
        }
    }
}

当结合了两个flavor时 他们可能定义了相同的属性
这种情况下 flavor维度数组的顺序 靠前的会覆盖掉靠后的
上一个例子中 color维度覆盖了price维度 该顺序也决定了构建variant的名称
假设构建类型有 debug 和release 那么上面例子定义的flavor将构成下面这些 variant

blueFreeDebug 和 blueFreeRelease
bluePaidDebug 和 bluePaidRelease
redFreeDebug 和 redFreeRelease
redPaidDebug 和 redPaidRelease

任务

Gradle 的Android插件会为配置的每一个variant创建任务
一个新的Android应用默认有 debug 和release两种构建类型
所以可以用 assembleDebug 和 assembleRelease 来分别构建两个apk 即用单命令assemble构建两个apk
当添加一个新的构建类型时 新的任务也将被创建 一旦你开始添加flavor 那么整个全新的任务系统将会被创建
因为每个构建类型的任务都会和每个product flavor相结合
这意味着 仅用一个构建类型和一个flavor 做一个简单设置 你就会有三个任务用于构建全部的variant
assembleBlue 使用blue flavor配置和组装BlueRelease 及 BlueDebug
assembleDebug 使用debug类型 给每一个flavor组装一个debug版本
assembleBlueDebubg 用构建类型结合flavor配置 并且flavor将会覆盖构建类型的设置

新的tasks视为每个构建类型 每个product flavor 结合而创建的

variant 过滤器

android.variantFilter {
    variant ->
        if(variant.buildType.name.equals('release')){
            variant.getflavors().each() {
                flavor -> {
                    if(flavor.name.equals('blue')){
                        variant.setIgnore(true)
                    }
                }
            }
        }
}

签名配置

android {

    // 签名模块
    signingConfigs {
        release {
                keyAlias 'release_android'
                keyPassword 123456
                storeFile file('./release.jks')
                storePassword 123123
          
        }

 debug {
                keyAlias 'debug_android'
                keyPassword 123456
                storeFile file('./debug.jks')
                storePassword 123123
        }
    }
}

在android 代码块下 设置debug 和 release 版本的签名信息
在buildtypes模块中使用签名信息

  buildTypes {
        release {
            signingConfig signingConfigs.realse
    }
}

在flavor 中使用签名信息

android {

    flavorDimensions "color","price"

    productFlavors{
        red{
            singingConfig singingConfigs.release
            flavorDimensions "color"
            applicationIdSuffix 'com.gradleandroid.red'
            versionCode 3
        }
    }
}

创建任务和插件

groovy 语法不熟悉的同学 可以先去翻看别的文章

定义任务

任务属于project对象 并且每个任务都可以执行task接口 。
定义一个新任务的最简单的方式是 执行将任务名称作为其参数的任务方法
task hello
然后定义任务方法体

task hello {

    println 'hello '    //1
    doFirst {            //2
        println 'hello world first'
    }
    doLast {          //3
        println 'hello world last'
    }
}

运行任务 命令行 ./gradlew hello

task任务.png

可以看见 步骤1在 任务执行前就已经被打印 而 步骤2 3 是在任务执行后才被打印
在Gradle 构建中 都有三个阶段 初始化阶段-配置阶段-执行阶段
步骤1 为配置阶段 23为执行阶段
配置阶段 即便执行的不是当前任务 也会被执行 在这里我们添加一个hello2任务 然后执行

task hello {

    println 'hello '
    doFirst {
        println 'hello world first'
    }
    doLast {
        println 'hello world last'
    }
}

task hello2 {

    println 'hello2 '
    doFirst {
        println 'hello2 world first'
    }
    doLast {
        println 'hello2 world last'
    }
}
task任务2.png

dolast方法 和 dofirst方法可以定义多个

task first{
    doFirst {
        println 'not really first'
    }

    doFirst {
        println 'first'
    }

    doLast {
        println 'not really last'
    }

    doLast {
        println 'last'
    }
}

运行之后如图


task任务3.png

需要注意的是 dofirst 总是添加一个动作到task 的最前面 而 dolast 总是添加一个动作到最后面
要注意定义的顺序

当涉及到给task任务排序时 可以使用 mustRunAfter() 方法
hello.mustRunAfter hello2
命令行运行
./gradlew hello hello2
结果如图

mustRunAfter.png

如果你需要一个任务依赖于另一个 , 那么可以使用dependsOn 方法
hello.dependsOn hello2
结果如图


dependsOn.png

总结
mustRunAfter 任务可以独立执行
dependsOn 任务不会独立执行 并且依赖项目执行在前

自动重命名apk

  android.applicationVariants.all {
        variant ->
            variant.outputs.all {
                               outputFileName = "${variant.versionName}-${variant.versionCode}.apk"
            }
    }

这里可以根据variant 和属性 自行判断是什么分支 获取该分支的versionname 和versioncode 对outputFileName进行封装

你可能感兴趣的:(Gradle 构建Android Studio 项目详解)