Android 中的 Gradle 配置介绍

目录

Android Studio 目录层级

├── app #Android App目录
│   ├── app.iml
│   ├── build #构建输出目录
│   ├── build.gradle #构建脚本
│   ├── libs #so相关库
│   ├── proguard-rules.pro #proguard混淆配置
│   └── src #源代码,资源等
├── build
│   └── intermediates
├── build.gradle #工程构建文件
├── gradle
│   └── wrapper
├── gradle.properties #gradle的配置 外部属性
├── gradlew #gradle wrapper linux shell脚本
├── gradlew.bat
├── LibSqlite.iml
├── local.properties #配置Androod SDK位置文件
└── settings.gradle #工程配置

settings.gradle

settings.gradle 用于配置 project。settings 文件声明了所需的配置来实例化项目的层次结构,标明其下有几个 module,比如这里包含一个:appmodule。

include ':app'

根目录的 build.gradle

settings.gradle 在同一目录下的 build.gradle 是一个顶级的 build 配置文件,在这里可以为所有的 project 以及 module 配置一些常用的配置。

根目录中的 build.gradle 默认包含 2 个代码块

  • buildscript{} 用于配置构建脚本所用到的代码库和依赖关系。这里定义了 Android 编译工具的类路径。repositories 中, jCenter 是一个著名的 Maven 仓库。
  • allprojects{} 用于定义所有模块需要用到的一些公共属性。这里定义的属性会被应用到所有的 module 中,但为了保证每个项目的独立性,一般不会在这里操作太多共有的东西
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()//使用 jcenter库
    }
    dependencies {
        // 依赖 android提供的 1.1.0 的 gradle build
        classpath 'com.android.tools.build:gradle:1.1.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
//为所有的工程的 repositories 配置为 jcenters
allprojects {
    repositories {
        jcenter()
    }
}

模块级 build.gradle

除了更目录下的 build 文件,gradle 支持对每个 module 进行配置,主要有三个重要的代码块:plugin、android 和 dependencies

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "org.flysnow.demo"
        minSdkVersion 9
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

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

apply plugin

第一行 apply plugin: 'com.android.application',这表示该 module 是一个 app module,应用了com.android.application 插件。
如果是一个 android library,那么这里的是 apply plugin: 'com.android.library'

  • apply plugin:声明引用插件的类型。如果是库的话就加
  • apply from:表示引用其他的配置文件,比如 apply from:"config.gradle"

android

compileSdkVersion & buildToolsVersion

  • compileSdkVersion: 基于哪个 SDK 编译,这里的 Version 是 API LEVEL,是21
  • buildToolsVersion: 基于哪个构建工具版本进行构建的

defaultConfig

defaultConfig 是默认配置,如果没有其他的配置覆盖,就会使用这里的。

常用属性:

  • applicationId 配置包名的

  • versionCode 版本号

  • versionName 版本名称

  • minSdkVersion app能够运行的最小版本

  • targetSdkVersion 目标设备sdk(向前兼容)

    一般 minSdkVersion < targetSdkVersion <= compileSdkVersion
    miniSdkVersion: app能运行的最小版本,比如 minSdkVersion=18(Android 4.3)那么在低于这个系统版本的设备上就会提示不能安装或运行。

    compileSdkVersion: 是我们告诉 Gradle,我们是用哪一版本的Android Sdk去编译程序的,可以使用这个版本的 API,比如我们使用的是 7.0 的版本
    compileSdkVersion=24,那么我们对于拍照裁剪图片等功能的操作,就可以使用 FileProvider了。

    targetSdkVersion: 系统通过 targetSdkVersion 来保证 Android 的向前兼容性,在 Android4.4 之后的设备上,系统会判断你的 targetSdkVersion 是否小于 19,如果小于的话,那就按照 19 之前的 api 方法,如果大于等于 19,那么就按照之后的 api 方法来走,保证了程序运行的一致性。也就是向前兼容性。

defaultConfig{} 还有很多其他的配置,后面介绍

buildTypes

buildTypes 是构建类型,常用的有 releasedebug 两种,可以在这里面启用混淆,启用 zipAlign 以及配置签名信息等。

buildTypes{} 中的其他配置,后面介绍

dependencies

dependencies 就不属于 Android 专有的配置了,它定义了该 module 需要依赖的 jar,aar,jcenter库信息。

apply plugin: 'com.android.application'

android { ... }

dependencies {
    // Dependency on a local library module
    implementation project(":mylibrary")

    // Dependency on local binaries
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // Dependency on a remote binary
    implementation 'com.example.android:app-magic:12.3'
}

本地项目依赖

implementation project(':mylibrary')

这声明了对一个名为“mylibrary”(此名称必须与在您的 settings.gradle 文件中使用 include: 定义的库名称相符)的 Android 库模块的依赖关系。在构建您的应用时,构建系统会编译该库模块,并将生成的编译内容打包到 APK 中。

本地二进制文件依赖项

implementation fileTree(dir: 'libs', include: ['*.jar'])

Gradle 声明了对项目的 module_name/libs/ 目录中 JAR 文件的依赖关系(因为 Gradle 会读取 build.gradle 文件的相对路径)。

或者,您也可以按如下方式指定各个文件:

implementation files('libs/foo.jar', 'libs/bar.jar')

远程二进制文件依赖项

implementation 'com.example.android:app-magic:12.3'

这实际上是以下代码的简写形式:

implementation group: 'com.example.android', name: 'app-magic', version: '12.3'

这声明了对“com.example.android”命名空间组内的 12.3 版“app-magic”库的依赖关系。

注意:此类远程依赖项要求您声明适当的远程代码库,Gradle 应在其中查找相应的库。如果本地不存在相应的库,那么当 build 需要它时(例如,当您点击 Sync Project with Gradle Files 图标 [站外图片上传中...(image-aa6d99-1607933369919)] 或运行 build 时),Gradle 会从远程站点提取它。

依赖项配置

  • implementation: Gradle 会将依赖项添加到编译类路径和构建输出,依赖不会暴露给其他模块
  • api:Gradle 会将依赖项添加到编译类路径和构建输出,依赖会传递给其他依赖该模块的模块
  • compileOnly:Gradle 只会将依赖项添加到编译类路径,仅在编译期依赖,运行时可有可无时可以使用该配置
  • runtimeOnly:Gradle 只会将依赖项添加到构建输出,以便在运行时使用。也就是说,不会将其添加到编译类路径。
    annotationProcessor:添加注解处理器的库依赖关系,需使用该配置将其添加到注解处理器类路径。注:Kotlin 使用 kapt 声明注解处理器依赖项

以上配置可以将依赖项应用于所有构建变体

如果想为特定的构建变体源代码集或测试源代码集声明依赖,必须将配置的名称首字母大写,并在前面加上构建变体或测试源代码集的名称作为前缀

dependencies {
    freeImplementation 'com.google.firebase:firebase-ads:9.8.0'
}

不过,如果您想为将产品变种和构建类型组合在一起的变体添加依赖项,就必须在 configurations 代码块中初始化配置名称。以下示例向“freeDebug”构建变体添加了 runtimeOnly 依赖项(使用本地二进制文件依赖项):

configurations {
    // Initializes a placeholder for the freeDebugRuntimeOnly dependency
    // configuration.
    freeDebugRuntimeOnly {}
}

dependencies {
    freeDebugRuntimeOnly fileTree(dir: 'libs', include: ['*.jar'])
}

ext{} 代码块 定制项目属性

在根目录 build.gradle 中, 可以定制适用于所有模块的属性,通过 ext 代码块来实现,如:

ext {
    compileSdkVersion = 28
    minSdkVersion = 18
}

然后在模块的 build.gradle 配置引用这些属性,语法为:
rootProject.ext.{属性名}, 比如:

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
} 

构建类型 android.buildTypes{}

默认情况下,Android Plugin 会自动给项目构建 debugrelease 版本。两个版本的区别在于能否在安全设备(非 dev)上调试,以及 APK 如何签名。debug 使用通过通用的 name/password 对生成的密钥证书进行签名(为了防止在构建过程中出现认证请求)。release 在构建过程中不进行签名,需要自行签名。

这些配置是通过 BuildType 对象来完成的。默认情况下,debugrelease 实例都会被创建。Android plugin 允许像创建其他 Build Type 一样自定义这两种类型。在 buildTypes 的 DSL 容器中进行配置:

android {
    buildTypes {
        debug {
            applicationIdSuffix ".debug"
        }

        jnidebug {
            initWith(buildTypes.debug)// 复制
            packageNameSuffix ".jnidebug"
            jnidebugBuild true
        }
    }
}

另外,每个 Build Type 都会创建一个新的 assemble<BuildTypeName> 任务。
例如:assembleDebug。
当 debug 和 release 构建类型被预创建的时候,assembleDebug 和 assembleRelease 会被自动创建。

buildType 的属性和方法

https://developer.android.com/reference/tools/gradle-api

属性 描述
applicationIdSuffix 应用 id 后缀
name build type的名字
versionNameSuffix 版本名称后缀
minifyEnabled 是否混淆
proguardFiles 混淆文件
signingConfig 签名配置
multiDexEnabled 是否拆成多个Dex
方法 描述
buildConfigField(type, name, value) 添加一个变量生成 BuildConfig 类
consumerProguardFile(proguardFile) 添加一个混淆文件进arr包
consumeProguardFile(proguardFiles) 添加混淆文件进arr包
externalNativeBuild(action) 配置本地的build选项
initWith(that) 复制这个 build 类型的所有属性
proguardFile(proguardFile) 添加一个新的混淆配置文件
proguradFiles(files) 添加新的混淆文件
resValue(type, name, value) 添加一个新的生成资源
setProguardFiles(proguardFileIterable) 设置一个混淆配置文件

启用 proguard 混淆

可以为不同的 buildTypes 选择是否启用混淆,一般 release 发布版本是需要启用混淆的,这样别人反编译之后就很难分析你的代码,而我们自己开发调试的时候是不需要混淆的,所以 debug 不启用混淆。对 release 启用混淆的配置如下:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFile 'proguard.cfg'
        }
   }
}
  • minifyEnabled 为 true 表示启用混淆
  • proguardFile 是混淆使用的配置文件
    这里是 module 根目录下的 proguard.cfg 文件

启用 zipAlign

这个也是比较简单的,同样也是在 buildTypes 里配置,可以为不用的 buildTypes 选择时候开启zipAlign

android {
    buildTypes {
        release {
            zipAlignEnabled true
        }
   }
}

配置应用的签名信息 android.signingConfigs{}

android.signingConfigs{} 下定义一个或者多个签名信息,在 buildTypes{} 配置使用即可。比如:

android {
    signingConfigs {
        release {
            storeFile file("release.keystore")
            keyAlias "tina"
            keyPassword "123456"
            storePassword "123456"
        }
        debug {
            ...
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
        debug {
            signingConfig signingConfigs.debug
        }
    }
  }
  • storeFile:签名证书文件
  • keyAlias:别名
  • keyPassword:key的密码
  • storePassword:证书的密码

配好相关信息即可在 buildTypes 配置使用

多渠道打包 android.productFlavors{}

Android Gradle 给我们提供了productFlavors,让我们可以对生成的 APK 包进行定制,所以就有了多渠道。它支持与 defaultConfig 相同的属性,这是因为,defaultConfig 实际上属于ProductFlavor

android  {
    productFlavors {
        dev{

        }
        google{

        }
        baidu{

        }
    }
}

这样当我们运行 assembleRelease 的时候就会生成 3 个 release 包,分别是 dev、google 以及 baidu。目前看这三个包除了文件名没有什么不一样,此时还没有定制,使用的都是 defaultConfig 配置。这里的 flavor 和 defaultConfig 是一样的,可以自定义其 applicationId、versionCode 以及 versionName等信息,比如区分不同包名:

android  {
    defaultConfig{
        ……
        // Specifies a flavor dimension.
        flavorDimensions "channel"
    }
    productFlavors {
        dev{
            applicationId "cn.tina.demo.dev"
        }
        google{
            applicationId "cn.tina.demo.google"
        }
    }
}

flavorDimensions(风格维度),所有变种都必须属于一个指定的变种维度,即一个产品变种组。必须将所有变种分配给某个变种维度;否则,您将收到如下所示的构建错误。如果给定的模块仅指定一个变种维度,则 Android Gradle 插件会自动将该模块的所有变种分配给该维度。

 Error:All flavors must now belong to a named flavor dimension.
 The flavor 'flavor_name' is not assigned to a flavor dimension.

注意:flavor 的命名不能与已存在的 Build Type 或者与 androidTest、test sourceSet 有冲突。

每一个 Build Type 都会生成新的 APK。Product Flavors 同样也会做这些事情:项目的输出将会组合所有的 Build Types 和 Product Flavors(如果有定义 Flavor)。

每一种组合(包含 Build Type 和 Product Flavor)就是一个 Build Variant(构建变种版本)。

例如,在之前的 Flavor 声明例子中与默认的 debug 和 release 两个 Build Types 将会生成 4 个 Build Variants:{dev, google}{debug, release}

  • dev - debug
  • dev - release
  • google - debug
  • google - release

项目中如果没有定义 flavor 同样也会有 Build Variants,只是使用的是 default 的 flavor/config,因为是无名称的,所以生成的 build variant 列表看起来就跟 Build Type 列表一样。

多定制的变种版本

扩展:flavorDimesions 可以设置多个,如

android  {
   defaultConfig{
       ……
       // Specifies a flavor dimension.
       flavorDimensions "channel","production"
   }
   productFlavors {
       dev{
           dimension "channel"
           applicationId "cn.tina.demo.dev"
       }
       google{
           dimension "channel"
           applicationId "cn.tina.demo.google"
       }
       productA{
         dimension "production"  
       }
       productB{
         dimension "production"  
       }
   }
}

那么可以组合出4种(2*2)种产品,分别有 debug 和 release,一共是8个变种版本

构建和任务

我们前面提到每一个 Build Type 会创建自己的 assemble<name> task,但是 Build Variants 是 Build Type 和 Product Flavor 的组合。
当使用 Product Flavor 的时候,将会创建更多的 assemble-type task。分别是:

  • assemble<Variant Name> 允许直接构建一个 variant 版本,例如 assembleDevDebug

  • assemble<Build Type Name> 允许构建指定 Build Type 的所有 APK,例如 assembleDebug 将会构建 DevDebugGoogleDebug 两个 variant 版本。

  • assemble<Product Flavor Name> 允许构建指定 flavor 的所有 APK,例如 assembleDev 将会构建 DevDebugDevRelease 两个 variant 版本。
    另外 assemble task 会构建所有的 variant 版本。

android.defaultConfig

AndroidManifest 里的占位符

AndroidManifest.xml 这是一个很重要的文件,我们的很多配置都在这里定义。有时候我们的一些配置信息,比如一个第三方应用的 key,第三方统计分析的渠道号等也要在这里进行配置。

这里以友盟统计分析平台为例,演示这一功能的使用。在友盟统计分析中,我们需要根据渠道进行统计,比如 google,百度,应用宝等渠道的活跃新增等。

友盟的 SDK 是在 AndroidManifest 里配置一个 name 为 UMENG_CHANNEL 的 meta-data,这样这个 meta-data 的值就表示这个 apk 是哪个渠道,我们版本发布有几十个渠道,manifestPlaceholders 允许动态替换在 AndroidManifest 文件里定义的占位符。

<meta-data 
    android:value="${UMENG_CHANNEL_VALUE}"
    android:name="UMENG_CHANNEL"
/>

如上 ${UMENG_CHANNEL_VALUE} 就是一个占位符,然后我们在 gradle 的 defaultConfig 里这样定义脚本:

android {
    defaultConfig {
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: 'dev']
    }
}

上面代码块的意思就是我们的默认配置里 AndroidManifest 的 ${UMENG_CHANNEL_VALUE} 占位符会被 dev 这个字符串所替换,也就说默认运行的版本是一个开发版。以此类推,我们其他渠道的版本就可以这样定义:

android  {
    productFlavors {
        google{
            applicationId "org.demo.google"
            manifestPlaceholders.put("UMENG_CHANNEL_VALUE",'google')
        }
        baidu{
            applicationId "org.demo.baidu"
            manifestPlaceholders.put("UMENG_CHANNEL_VALUE",'baidu')
        }
    }
}

这样有多少个渠道就做多少次这样的定义,即可完成分渠道统计。但是如果上百个渠道,这样一个个写的确太累,很麻烦,我们继续研究,同学们有没有发现,我们的渠道名字和我们的 flavorName 一样,我们用这个 flavorName 作为 UMENG_CHANNEL_VALUE 不就好了吗,可以批量的替换吗?当然可以,这又体现了我们 Gradle 的强大和灵活之处。

productFlavors.all { flavor ->
        manifestPlaceholders.put("UMENG_CHANNEL_VALUE",name)
}

循环每个 flavor,并把他们的 UMENG_CHANNEL_VALUE 设置为他们自己的 name 名字。

BuildConfig 自定义常量

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String BUILD_TYPE = "debug";
}

BuildConfig.java 是 Android Gradle 自动生成的一个 java 类文件,无法手动编译,但是可以通过 Gradle 控制,也就是说这里是动态可配置的,这里以生产环境和测试环境为例来说明该功能的使用。

我们在开发 App 的时候免不了要和服务器进行通信,我们的服务器一般都有生产和测试环境,当我们处理开发和测试的时候使用测试环境进行调试,正式发布的时候使用生成环境。以前的时候我们通过把不同的配置文件打包进 APK 中来控制,现在不一样了,我们有更简便的方法,这就是 buildConfigField

android {
    defaultConfig {
        buildConfigField'String','API_SERVER_URL','"http://test.cn/"'
    }
    productFlavors {
        google{
            buildConfigField 'String','API_SERVER_URL','"http://release.cn/"'
        }
        baidu{
            buildConfigField 'String','API_SERVER_URL','"http://release.cn/"'
        }
    }
}

buildConfigField 一共有3个参数,

  • 第一个是数据类型,是定义的常量值是一个什么类型,和 Java 的类型是对等的,这里是String。
  • 第二个参数是常量名,这里是 API_SERVER_URL
  • 第三个参数是常量值。如此定义之后,就会在 BuildConfig.java 中生成一个常量名为 API_SERVER_URL 的常量定义。默认配置的生成是:
public final static String API_SERVER_URL = "http://test.cn/"

当是 baidu 和 google 渠道的时候生成的就是 http://release.cn/ 了。这个常量可以在我们编码中引用。在我们进行打包的时候会根据 Gradle 配置动态替换。

我们发现一般渠道版本都是用来发布的,肯定用的是生产服务器,所以我们可以使用批处理来搞定这个事情,而不用在一个个渠道里写这些配置。

productFlavors.all { flavor ->
        buildConfigField 'String','API_SERVER_URL','"http://release.org/"'
    }

此外,比如 Gradle 的 resValue,也和 buildConfigField 一样,只不过控制生成的是资源,比如我们在 android 的 values.xml 定义生成的字符串。可以用它来动态生成我们想要的字符串,比如应用的名字,可能一些渠道会不一样,这样就可以很灵活的控制自动生成,关于 resValue 详细介绍请参考相关文档,这里不再举例说明。

参考:https://developer.android.com/studio/build

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容