使用 Gradle 配置 Android 工程

3,103 阅读10分钟

0. 前言

自从将 Android 开发工具从 eclipse 迁移到 AndroidStudio 中之后,Android 工程构建越来越方便。见过很多项目中各式各样的 Gradle 配置语法,懵懵懂懂。遂花费近两周时间将官方文档通读一遍并整理出常用 Gradle 配置 Demo,以此博客作为说明。

1. 准备工作

一个最新版本的 AndroidStudio 毋庸置疑。下载地址:请戳。此处使用 3.5 版本。

Gradle 版本 5.4.1 ,无须单独下载,在工程中配置好即可。

2. 基本概念

2.1. Module

软件工程中,通常采用模块化开发的策略,将一个项目划分成若干部分,多人协作开发中,每人完成一部分,最终完成整个项目。因此 AndroidStudio 也采用模块化管理的方式编译 Android 工程 。各个模块之间的依赖关系类似于树形结构,主 Module 作为根节点,其余多个库 Module 都直接或简介的被主 Module 引用。

2.2. Project

基于前面的描述,Project 即 AndroidStudio 打开的一个目录,其中包含 N+1 个 Module ,通过 Gradle 构建多个 Module 并最终合并成一个 apk 文件。

其实,在一个 Project 目录中,可以有多个主 Module ,每个主 Module 可以依赖相同的库 Module 并负责生成一个 apk 文件。通常不建议这么使用,因为这样会破坏 Module 之间的树形依赖关系,不利于后期维护。推荐将复用频率较高的库 Module 封装成 aar 文件,通过直接依赖或远程依赖的方式用于不同的主 Module 之中。

2.3. buildType & productFlavor

通过直译的方式 ,可以理解成 构建类型产品特性 。构建类型中,默认包含 debug 和 release 两种类型。一些基本的差异如 :debug 包允许调试,release 包不允许调试。产品特性可以根据实际需要自行定义,如:收费和免费版本,汉语、英语及日语。不同的特性之间通过维度的概念区分。

最终如上图所示,生成 2x3x2 共 12 种不同的 apk 文件。

3. 工程结构

精简后的 AndroidStudio 工程结构如下:

├── app
│   ├── build.gradle
│   ├── proguard-project.txt
│   └── src
│       └── main
│           ├── AndroidManifest.xml
│           ├── java
│           │   └── com
│           │       └── flueky
│           │           └── demo
│           │               └── MainActivity.java
│           └── res
│               ├── layout
│               │   └── activity_main.xml
│               └── values
│                   ├── strings.xml
│                   └── styles.xml
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
  1. 文件夹 app 作为一个 Module ,必须有 build.gradle 文件,src 文件夹包含全部源文件:Java 文件、资源文件、清单文件等。
  2. 文件 build.gradle 不同于 app/build.gradle , 它用于整个工程的配置,而 app/build.gradle 只作用于 app Module 的配置。
  3. gradle 包含使用的 gradle 插件版本 。若未指定 ,AndroidStudio 会自动生成最新的 gradle 插件目录。
  4. gradlew、gradlew.bat 分别是 Mac/Linux 和 Windows 的脚本文件。关联于 gradle 插件版本,不可单独删除。
  5. settings.gradle 包含整个工程的所有 Module 的声明。

settings.gradle 示例:

// 主Module
include ':app'
// 库Module
include ':library'

// 添加外部库
include 'other-lib'
project(':other-lib').projectDir = new File(rootDir, "../other-sample/library")

4. 配置 Project

4.1. 构建脚本

buildscript {
    repositories {
        //  仓库地址
        google()
        jcenter()
        mavenCentral()
    }
//    工程的依赖配置
    dependencies {
        // 必须包含的 gradle 版本的构建工具。
        classpath 'com.android.tools.build:gradle:3.4.1'
    }
}

4.2. 全工程配置

// 全部工程配置,作用于每个 Module
allprojects {
    repositories {
        //  仓库地址
        google()
        jcenter()
        mavenCentral()

        // 依赖仓库,添加用于查找依赖项的目录,可以多个。
        flatDir {
            // jar、aar 存放的目录
            dirs 'libs'
        }
    }
}

4.3. 扩展变量

// 扩展变量,用于统一管理每个 Module 的有关配置
ext {
    // 应用 id
    applicationId = "com.flueky.demo"
    // 统一控制各个 Module 使用的 SDK 版本
    compileSdkVersion = 29
    buildToolsVersion = "29.0.0"
    minSdkVersion = 19
    targetSdkVersion = 29

    // 定义 Module 的版本号
    app = [
            versionCode: 1,
            versionName: '1.0.0'
    ]
}

4.4. 加载配置文件

// 加载其他的 gradle 配置文件,可选
apply from: "config.gradle"

5. 配置 Module

5.1. 加载插件

// 声明 Module 是主 Module,生成 apk 文件
apply plugin: 'com.android.application'
// 声明 Module 是库 Module,生成 aar 文件
apply plugin: 'com.android.library'
// 声明 Module 是 Java 库,生成 jar 文件
apply plugin: 'java-library'

还有很多 Plugin 可以用,如: java web maven-publish 等。

另一种写法,一个 Module 中使用多个插件,如:

plugins {
    id 'java'
    id 'war'
    id 'maven-publish'
}

5.2. 使用SDK

android {
    // 定义编译的 sdk 版本,只有使用最新版本的sdk,才能在代码中只用最新的 api 方法
    compileSdkVersion rootProject.ext.compileSdkVersion
    // 定义构建工具的版本
    buildToolsVersion rootProject.ext.buildToolsVersion
}

rootProject.ext 是在 Project 的 build.gradle 文件中声明。

5.3. 默认配置

android{
    defaultConfig {
        // 应用 id
        applicationId rootProject.ext.applicationId
        // 最小 sdk 版本,低于此 Android 版本的手机不能安装
        minSdkVersion rootProject.ext.minSdkVersion
        // 目标 sdk 版本,低于此 Android 版本的手机完美兼容,高于此 Android 版本的手机,部分特性不能使用
        // 升级 target 需要针对高版本做兼容。
        targetSdkVersion rootProject.ext.targetSdkVersion
        // 应用版本号,覆盖安装时,升级版本依据
        versionCode rootProject.ext.app.versionCode
        // 版本名称,
        versionName rootProject.ext.app.versionName
        // 指定需要编译 abi 版本的 so
        ndk{
            abiFilters 'armeabi-v7a'
        }
        // 设置编译的资源
        resConfigs "zh-rCN"
        // 多 dex 支持 ,minSdkVersion >= 20 使用 
        multiDexEnabled true
    }
}

5.4. 签名配置

签名文件,至少配置一个。如未使用,debug 包,会使用 user home/.android/debug.keystore 的签名文件。 release 包默认不签名。

// 可以将签名文件信息配置在 keystore.properties 文件中
def ksPropFile = rootProject.file("keystore.properties")
def ksProp = new Properties()
// 加载签名配置文件
ksProp.load(new FileInputStream(ksPropFile))

android{
    signingConfigs {
        // 生产签名,读取配置文件
        release {
            keyAlias ksProp['keyAlias']
            keyPassword ksProp['keyPassword']
            storeFile file(ksProp['storeFile'])
            storePassword ksProp['storePassword']
        }
        // 测试签名,静态配置
        debug {
            keyAlias 'flueky'
            keyPassword 'android'
            storeFile file('../demo.keystore')
            storePassword 'android'
        }
    }
}

使用配置的好处是,将生产签名文件信息保存在配置文件中,不添加到版本控制工具中,可以有效防范签名文件信息泄露,被他人使用。
针对签名文件的校验,会有专门的防篡改技术,防止应用被反编译 apk 后二次打包。如签名文件泄露,会构成很大威胁。

5.5. 构建类型

android{
    buildTypes {
        // 测试版本
        debug {
            // 允许 Java 代码调试(默认允许)
            debuggable true
            // 允许 JNI 代码调试(默认允许)
            jniDebuggable true
            // 签名 信息
            signingConfig signingConfigs.debug
            // 版本名称后缀
            versionNameSuffix = '-beta'
            // 应用 Id 后缀
            applicationIdSuffix '.debug'
        }
        // 发行版本
        release {
            // 禁止 Java 代码调试(默认禁止)
            debuggable false
            // 禁止 JNI 代码调试 (默认禁止)
            jniDebuggable false
            signingConfig signingConfigs.release
            // build/intermediates/proguard-files 目录下存在三个混淆配置文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
            // 允许代码压缩、混淆、优化,默认情况下会使用 R8 压缩
            minifyEnabled true
            // 允许资源压缩,包括清理无用资源。在生成 apk 时,如需保留被删除的图片使用,keep.xml 声明 。
            shrinkResources true
            // 配置需要放在主 dex 中的类
//            multiDexKeepFile file('multidex-config.txt')
            // 或者使用下面的配置,语法同 proguard file
//            multiDexKeepProguard file('multidex-config.pro')
        }
        // 内部使用版本
        inner {
            // 使用 debug 的配置
            initWith debug
            applicationIdSuffix '.inner'
        }
    }
}

AndroidStudio 默认支持 debug 和 release 两种类型。并有一些默认的配置。常用配置见上。

5.6. 产品特性

android{
    // 定义两种纬度
    flavorDimensions 'stage', 'api'

    productFlavors {
        // 开发阶段
        dev {
            dimension 'stage'
        }
        // 生产阶段
        pro {
            dimension 'stage'
        }

        minApi21 {
            dimension 'api'
            minSdkVersion 21
        }
        minApi23 {
            dimension 'api'
            minSdkVersion 23
        }
        minApi26 {
            dimension 'api'
            minSdkVersion 26
        }
    }
}

产品特性和构建类型,不仅仅是在构建的时候修改些配置,还可以在 src 目录下,定义同名的文件夹,存放 java 、res 、assets 和 AndroidManifest.xml 等。用于实现不同的业务逻辑,资源图片等。

5.7. 过滤变体

产品特性和构建类型可以通过组合的方式生成多个 apk 文件。但在实际中可能不需要部分组合方式。忽略后,可加速编译过程。

如,内部使用不需要生产阶段的 apk 文件,配置如下:

android{
    //  设置不需要生成 apk 的类型和特性
    variantFilter { variant ->
        // 将多个 flavor 组合转成字符串数组
        def names = variant.flavors*.name
        // 获取到 buildType 名称
        def type = variant.buildType.name
        // 忽略部分不需要生成的 apk
        if (names.contains('pro') && type.equals("inner")) {
            setIgnore(true)
        }
    }
}

5.8. 细分APK

android{
    splits {
//       注意和 nkd.abiFilters 的冲突
        abi {
            enable true
            // 包含所有版本so 的apk 文件。此配置仅用于 abi 。
            // density 中默认会生成包含所有资源的 apk
            universalApk true
            // 去除  x86 和 x86_64
            exclude 'x86', 'x86_64'
        }
//       注意和 resConfigs 的冲突
        density {
            enable true
            reset()
            include "xhdpi"
//            compatibleScreens 'small', 'normal', 'large', 'xlarge'
        }
    }
}

universalApk 只用在 abi 的配置中。true 表示按照默认的方式将全部 so 打包 。density 中,此值必须为 true。 使用 abi 和 density 要注意同 ndk.abiFilters 和 resConfigs 使用的冲突。

5.9. 添加依赖

依赖第三方库,在新版本 Gradle 中,常用的有 implementationapi

它们区别是:

  1. A 中 implementation B , B 中 implementation C 。 B 可以使用 C 的类和方法,A 可以使用 B 的类和方法。
  2. A 中 implementation B , B 中 api C 。 B 可以使用 C 的类和方法,A 可以使用 B 的类和方法。A 还可以使用 C 的类和方法。

这样的好处是,如果只修改了 Module C , 情况 1 只会重新编译 B 和 C ,情况 2 会重新编译 A B C 。 因此在实际应用中,避免大量使用 api 的方式。

下面列举了对 jar 文件、 aar 文件和 maven 库文件的依赖方式。

dependencies {
    // 依赖同级的 libs 目录,只包含jar
    implementation fileTree(include: '*.jar', dir: 'libs')
    // 依赖同级的 libs 目录,包含 jar 和 aar,还可以选择不包含指定文件
    implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
    // debug  类型的依赖
    debugImplementation fileTree(include: '*.jar', dir: 'src/debug/libs')
    // release  类型的依赖
    releaseImplementation fileTree(include: '*.jar', dir: 'src/release/libs')
    // 同理 devImplementation minApi21Implementation innerImplementation
    // 如果 需要组合使用,如 devMinApi21DebugImplementation,见 configurations

    // 依赖本地 module
    implementation project(':library')
    implementation project(':other-lib')
    // 等同于带 path 参数
//    implementation project(path: ':library')
    // 依赖远程库,不建议在版本号中使用 + 通配符,括号可以省略
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation('com.android.support:support-v4:28.0.0') {
        // 一个远程库可能包含多个第三方库,可以排除指定库
        exclude group: 'com.android.support', module: 'collections'
    }
    // 等同于复杂形势
//    implementation group: 'com.android.support', name: 'support-v4', version: '28.0.0'
    implementation 'com.google.dagger:dagger:2.24'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
    // 5.0  之前多 dex 支持,minSdkVersion < 20 必须使用。
    implementation 'androidx.multidex:multidex:2.0.1'
}

注意到上面的配置中存在 debugImplementation 和 releaseImplementation 。这是对不同构建类型单独指定的依赖方式。

产品特性也可以使用上面的配置,但是需要先声明,方式如下:

configurations {
    // 添加多个依赖项配置
    devMinApi21DebugImplementation {}
    devMinApi21Api {}
    devDebugCompileOnly {}
    minApi21DebugRuntimeOnly {}
}

篇幅有限 ,以上介绍均是常用配置。更多配置请见源码

  1. 修改生成的 apk 文件目录。
  2. 修改生成的 apk 文件名称。
  3. 动态修改版本号。
  4. 替换 build 缓存目录。
  5. AndroidManifest.xml 注入变量。

关于 AndroidManifest.xml 合并冲突,有机会在后面的文章中讲。

6. 常见问题

6.1. 问题1

主 Module 有 inner 的构建类型,库 Module 中、没有 ,构建 app 时会报错。

可在库 Module 中添加 inner 的构建类型,或者在主 Module 的 inner 类型中,添加下面的配置。

    // inner 匹配失败 , debug 匹配成功, release 忽略
    matchingFallbacks = ['inner', 'debug', 'release']

如需匹配 release 时,将 release 放在 debug 前,或者直接删除 debug 。

6.2. 问题2

主 Module 有的产品特性,库 Module 中没有,构建 app 时会报错。

错误原因同 问题 1 。如,库 Module 中存在其他类似的产品特性,可使用匹配的方式 ,如下:

android{
    productFlavor{
        minApi21 {
            minSdkVersion 21
            // 库 module 没有 minApi21 ,匹配较高版本
            // 同时需要在 AndroidManifest.xml 中使用 overrideLibrary
            matchingFallbacks = ['minApi23']
        }
        minApi26 {
            minSdkVersion 26
            // 库 module 没有 minApi26 ,匹配较低版本,可兼容
            matchingFallbacks = ['minApi23']
        }
    }
}

如,库 Module 中没有定义任何产品特性,可以直接在 defauleConfig 中,忽略对维度的依赖 。

android{
    defauleConfig{
        // 库 module 没有开发进度的维度,因此忽略
        missingDimensionStrategy 'dev', 'pro'
    }
}

6.3. 问题3

主 Module 的 minSdk 小于 库 Module 的 minSdk。

使用 overrideLibrary ,指定复写库 Module 的 packageName 。

<manifest xmlns:tools="http://schemas.android.com/tools">
    <!-- minSdkVersion 存在冲突的解决方案-->
    <uses-sdk tools:overrideLibrary="com.flueky.library" />
</manifest>

源码地址

觉得有用?那打赏一个呗。去打赏