Android Gradle学习(二)- build.gradle结构

1,302 阅读4分钟

1. 基本概念

  • 项目级 build.gradle 主要用于配置整个项目的构建设置
  • 模块级 build.gradle 配置各个module的构建设置,指定了项目编译版本、打包配置及依赖库等

2. 项目级 build.gradle

2.1 文件内容

// Top-level build file where you can add configuration options common to all sub-projects/modules.  
buildscript {  
    repositories {  
        mavenLocal()  
        google()  
        jcenter()  
        maven {  
            allowInsecureProtocol = true  
            url "http://mvnrepo.alibaba-inc.com/mvn/repository"  
        }  
    }  
    dependencies {  
        classpath 'com.android.tools.build:gradle:3.5.1'  
    }  
}  
  
allprojects {  
    repositories {  
        mavenLocal()  
        google()  
        jcenter()  
        maven {  
            allowInsecureProtocol = true  
            url "http://mvnrepo.alibaba-inc.com/mvn/repository"  
        }
    }  
}

task clean(type: Delete) {  
    delete rootProject.buildDir  
}
  • buildscript - gradle脚本执行所需依赖。
    • repositories - 配置依赖库仓库
    • dependencies - 依赖插件
  • allprojects - 各个module gradle脚本执行所需依赖。
    • repositories - 配置依赖库仓库
  • task clean - 执行clean时,会删除build目录

3. 模块级 build.gradle

3.1 文件内容

apply plugin: 'com.android.application'  
  
android {  
    compileSdkVersion 30  

    defaultConfig {  
        minSdkVersion 21  
        targetSdkVersion 30  
        versionCode 1  
        versionName "1.0"  
        applicationId 'com.xxx.xxx'  
        buildConfigField "boolean", "NeedLogger", 'true'  
        buildConfigField "String", "AUTH_SECRET", '"请填入您的密钥"'  
    }  
  
    sourceSets {  
        main {  
            java.srcDirs = ['src/main/java']  
            jniLibs.srcDirs = ['libs']  
            res.srcDirs = ['src/main/res']  
        }
    }  
  
    signingConfigs {  

        debug {  
            keyAlias 'xx'  
            keyPassword 'xx'  
            storeFile file('xx/xx.keystore')  
            storePassword 'xx'  
            v1SigningEnabled true  
            v2SigningEnabled true  
        }  

        release {  
            keyAlias 'xx'  
            keyPassword 'xx'  
            storeFile file('xx/xx.keystore')  
            storePassword 'xx'  
            v1SigningEnabled true  
            v2SigningEnabled true  
        }  

    }  
  
    buildTypes {  
        release {  
            debuggable false  
            minifyEnabled true  
            proguardFile file('proguard-rules.pro')  
            zipAlignEnabled false  
            multiDexEnabled true  
            signingConfig signingConfigs.release  
        }  

        debug {  
            debuggable true 
            minifyEnabled false  
            zipAlignEnabled false  
            multiDexEnabled true  
            signingConfig signingConfigs.debug  
        }  
    }  
}  
  
dependencies {  
    implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])  
    implementation 'androidx.appcompat:appcompat:1.3.1'  
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'  
}
  • apply plugin - 应用插件
  • android - 配置项目构建的各种属性
  • dependencies - 依赖库

3.2 apply plugin插件

apply plugin: 'com.android.application' 是项目级build.gradle里面引入的插件classpath 'com.android.tools.build:gradle:3.5.1'

  • com.android.application 表示该module是一个APP项目,编译产物是apk
  • com.android.library 表示该module是一个库,编译产物是aar

引入其他插件

//项目级build.gradle
dependencies {  
    classpath 'com.android.tools.build:gradle:3.5.1'  
    // 引入kotlin插件依赖
    classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20'
}  

//模块级build.gradle
apply plugin: 'com.android.application' 
// 引入kotlin插件
apply plugin:'kotlin-android'

3.3 android项目配置

  • compileSdkVersion 30 - 编译版本,配置当前项目使用API 30的android.jar参与编译,可以在项目目录-External Libraries-<Android API 30> 找到 android.jar
  • buildToolsVersion '30.0.3'
  • defaultConfig - 项目信息默认配置,优先级最低
    • minSdkVersion 21 - 最低支持的版本设备
    • targetSdkVersion 30 - 目标适配的版本,比如设置成23以下,就不需要动态申请权限
    • versionCode 1 - 版本号,用于对比两个APP的版本大小,判断是否可以覆盖安装
    • versionName "1.0" - 显示版本号,作为展示使用
    • applicationId 'com.xxx.xxx' - 包名
    • buildConfigField "boolean", "NeedLogger", 'true' - 在BuildConfig.class内生成一个boolean NeedLogger = true的变量
    • buildConfigField "String", "AUTH_SECRET", '"请填入您的密钥"' - 在BuildConfig.java内生成一个String Auth_SECRET = "请填入您的密钥",注意要加引号,否则编译错误
  • sourceSets - 指定代码,配置等的目录,可以配置不同的渠道的配置目录
  • signingConfigs - 配置签名相关
  • dependencies - 依赖库配置

3.3.1 多渠道打包

android {  
    // ...  
  
    // 定义构建变体的维度,例如这里定义了"market" 和 "tier" 两个维度  
    flavorDimensions "market", "tier"  

    // 根据 flavorDimensions 定义不同的产品风味,如 free 和 pro  
    productFlavors {  
        xiaomi {  
            dimension "market"  
        }  

        huawei {  
            dimension "market"  
        }  

        free {  
            dimension "tier"  
            applicationIdSuffix ".free"  
            versionNameSuffix "-free"  
        }  

        pro {  
            dimension "tier"  
            applicationIdSuffix ".pro"  
            versionNameSuffix "-pro"  
        }  
    }
}

此时会构建market(2) * tier(2) * buildTypes(2) = 8个渠道的包

image.png

看下编译生成的BuildConfig.java,可以通过匹配渠道做不同的操作,比如初始化不一样的推送(小米或华为),显示收费或者免费等等

public final class BuildConfig {  
public static final boolean DEBUG = Boolean.parseBoolean("true");  
public static final String APPLICATION_ID = "com.xx";  
public static final String BUILD_TYPE = "debug";  
public static final String FLAVOR = "huaweiFree";  
public static final int VERSION_CODE = 1;  
public static final String VERSION_NAME = "1.0-free";  
public static final String FLAVOR_market = "huawei";  
public static final String FLAVOR_tier = "free";  
// Fields from default config.  
public static final String AUTH_SECRET = "请填入您的密钥";  
public static final boolean NeedLogger = true;  
}

3.3.2 动态配置

  • 动态配置BuildConfig代码
buildTypes {  
    release {  
        // ...  
        buildConfigField "boolean", "NeedLogger", 'false'  
    }  

    debug {  
        // ...  
        buildConfigField "boolean", "NeedLogger", 'true'  
    }  
}

// code,测试包自动打开日志,正式包关闭日志输出
if (BuildConfig.NeedLogger) {  
    Log.i(TAG, "xxxx");  
}
  • 动态配置AndroidManifest.xml
// AndroidManifest.xml
<data android:scheme:"${xx_scheme}"/>

// build.gradle
buildTypes {  
    release {  
        // ...  
        manifestPlaceholders = [xx_scheme: "release_scheme"]
    }  

    debug {  
        // ...  
        manifestPlaceholders = [xx_scheme: "debug_scheme"]
    }  
}
  • 动态配置资源

渠道和buildType可以分别配置不同的资源,例如huaweiFreeDebug按照默认约定,可以创建如下目录

image.png

// debug/strings.xml
<string name="test_value">debug</string>

// free/strings.xml
<string name="test_value">free</string>

// huawei/strings.xml
<string name="test_value">huawei</string>

// main/strings.xml
<string name="test_value">main</string>

huaweiFreeDebug内部资源优先级debug>huawei>free>main

一般正式url可以配置在main/strings.xml,测试url可以配置在debug/strings.xml,这样在打包的时候就可以自动选择不同环境url,不需要手动去修改代码或配置切换。

另外,也可以手动配置资源路径,例如在项目根目录创建common/main,sourceSets可以配置此目录

image.png

sourceSets {  
    main {  
        // ... 
        res.srcDirs += ["${rootProject.projectDir}/common/main/java"]  
        res.srcDirs += ["${rootProject.projectDir}/common/main/res"] 
    }  

    huawei {  
    }  

    xiaomi {  

    }  
  
}

3.4 dependencies项目依赖

指定当前项目依赖的第三方库,常用的方式有四种

  • implementation - 该依赖方式的三方库,项目可以直接调用三方库里面的代码或其他资源。并且可以依赖传递,会将该三方库依赖的其他的库一起引入,默认使用版本最高的那个。
  • api - 当前module如果是library,该依赖方式引入的三方库,会将三方库的代码、配置等一起打入当前module中。如果当前module是application,和implementation无区别
  • compileOnly - 只在编译期使用,最终产物不会包含该依赖库的代码,一般搭配编译插件使用
  • runtimeOnly - 只在运行时使用,编码期间无法使用该依赖库内的代码或者其他资源,最终产物会包含该依赖库的所有代码和资源。
// 常用依赖
implementation 'androidx.appcompat:appcompat:1.2.0'

// 统一定义版本号
appcompatVersion = '1.2.0'
implementation "androidx.appcompat:appcompat:$appcompatVersion"

// 依赖module
implementation project(path: ':moduleA')


结合多渠道打包的变体

可以根据Debug或Release包引入不同依赖。比如工具库LeakCanary,用于检测内存泄漏,仅希望在测试包中使用,在正式包中剔除该功能。可以使用如下方式依赖

debugImplementation "com.xxx.xxx:LeakCanary:1.0.0"
releaseImplementation "com.xxx.xxx:LeakCanary-no-op:1.0.0"

还可以根据不同渠道引入不同依赖sdk。比如小米渠道引入小米推送sdk,华为推送引入华为sdk

xiaomiImplementation 'xiaomipushsdk'  
huaweiImplementation 'huaweipushsdk'

AndroidStudio提示了很多种,可以自行选择

image.png

剔除冲突的依赖

有一种场景,maven依赖和aar依赖同时包含了同一个库,编译时就会冲突,处理方式有两种

  • 通过exclude方式排除掉,优点是简单,缺点是每个冲突的依赖库都得加
implementation('com.xxx.xxx:xxx:1.0.0') {
    exclude group:'com.xxx', module:'xx'
}
  • 让aar提供方去掉冲突的库