Android Grardle
Android的Gradle插件是由Google的Android团队开发的,它是一个第三方插件。
需要注意,Android的Gradle插件可以分为三类:
- APP 插件:表示应用工程,可以生成一个apk应用,它的插件id是
com.android.application - Library插件:表示库工程,可以生成一个aar包,插件id是
com.android.library - Test 插件:表示测试工程,主要是对工程进行单元测试,插件id是
com.android.test
注意,aar包和jar包的区别:
jar只包含class文件和清单文件,不包含资源文件,比如图片等aar即Android Archive,是一个Android库项目的二进制归档文件。它包含所有资源文件,包括class及res资源文件
前面学习了Gradle的基本知识,接下来要详细的学习Android工程构建以及Android Gradle插件,可以查看Android的官方指南 配置构建
脚本配置
顶层构建文件
顶层 build.gradle 文件位于项目的根目录下,用于定义适用于项目中所有模块的构建配置。默认情况下,顶层构建文件使用 buildscript 代码块定义项目中所有模块共用的 Gradle 代码库和依赖项。以下代码示例说明了创建新项目后可在顶层 build.gradle 文件中找到的默认设置和 DSL 元素:
/**
* buildscript块是你为Gradle本身配置资源库和依赖关系的地方,也就是说,你不应该在这里包含你的模块的依赖关系。
*/
buildscript {
/**
* 仓库块配置了Gradle用来搜索或下载依赖关系的仓库。Gradle 预先配置了对远程资源库的支持,
* 如 JCenter、Maven Central 和 Ivy。
* 你也可以使用本地资源库或定义自己的远程资源库。
* 下面的代码定义了JCenter作为Gradle应该使用的资源库来寻找它的依赖关系。
* 使用Android Studio 3.0和更高版本创建的新项目也包含Google的Maven仓库。
*/
repositories {
google()
jcenter()
}
/**
* dependencies块配置了Gradle在构建项目时需要使用的依赖关系。
* 下面一行添加了Android plugin for Gradle 4.0.0版本作为classpath依赖
*/
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
}
}
/**
* allprojects块是你配置项目中所有模块(如第三方插件或库)所使用的资源库和依赖关系的地方。
* 然而,你应该在每个模块级的build.gradle文件中配置特定模块的依赖关系。
* 对于新项目,Android Studio默认包含JCenter和Google的Maven仓库,
* 但它并没有配置任何依赖关系(除非你选择一些需要的模板)。
*/
allprojects {
repositories {
google()
jcenter()
}
}
模块构建文件
模块级 build.gradle 文件位于每个 project/module/ 目录下,用于为其所在的特定模块配置构建设置。你可以通过配置这些构建设置提供自定义打包选项(如额外的构建类型和产品种类),以及替换 main/ 应用清单或顶层 build.gradle 文件中的设置。
/**
* 构建配置中的第一行将Gradle的Android插件应用到这个构建中,并使android块可用来指定Android特定的构建选项
*/
apply plugin: 'com.android.application'
/**
* android 块是你配置所有Android特定构建选项的地方
*/
android {
/**
* compileSdkVersion 指定了Gradle应该使用的Android API级别来编译你的应用,
* 这意味着你的应用可以使用这个API级别及以下的API功能。
*/
compileSdkVersion 28
/**
* buildToolsVersion 指定了Gradle应该使用的SDK构建工具、命令行实用程序和编译器的版本来构建你的应用程序。
* 你需要使用SDK管理器下载构建工具。 这个属性是可选的,因为插件默认使用推荐版本的构建工具
*/
buildToolsVersion "29.0.2"
/**
* defaultConfig 块封装了所有构建变体的默认设置和条目,
* 并且可以从构建系统中动态覆盖main/AndroidManifest.xml中的一些属性。
* 你可以配置产品口味,为您的应用程序的不同版本覆盖这些值。
*/
defaultConfig {
/**
* applicationId 指定项目的包名
* 但是,你的源代码仍然应该引用main/AndroidManifest.xml文件中包属性定义的包名。
*/
applicationId 'com.example.myapp'
// 定义运行应用程序所需的最低API级别。
minSdkVersion 15
/**
* 指定项目的目标版本,表示在该目标版本上已经做过充分测试,系统会为该应用启动一些对应目标系统的最新功能特性,
* Android系统平台的行为变更,只有targetSdkVersion的属性值被设置为大于或等于该系统平台的API版本时,才会生效。
* 例如,若指定targetSdkVersion值为22,则表示该程序最高只在Android5.1版本上做过充分测试,
* 在Android6.0系统上(对应targetSdkVersion为23)拥有的新特性如系统运行时权限等功能就不会被启用。
*/
targetSdkVersion 28
// 定义应用程序的版本号。一般每次打包上线时该值应该增加
versionCode 1
// 为应用程序定义一个用户友好的版本名称。应用市场上会展示出来
versionName "1.0"
}
/**
* buildTypes 块是你可以配置多种构建类型的地方。默认情况下,构建系统定义了两种构建类型:调试和发布。
* 调试构建类型在默认的构建配置中没有明确显示,但它包括调试工具,并使用调试密钥签名。
* release 构建类型应用Proguard设置,默认情况下没有签名。
*/
buildTypes {
/**
* 默认情况下,Android Studio配置发布构建类型以启用代码混淆,使用minifyEnabled,并指定默认的Proguard规则文件。
*/
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
/**
* productFlavors 块是可以配置多个产品口味的地方。这允许你创建不同版本的应用,
* 这些应用可以用自己的设置覆盖defaultConfig 块。产品口味是可选的,构建系统默认不创建它们。
* 这个例子创建了一个免费和付费的产品类型。然后,每个产品类型都指定了自己的应用ID,
* 这样它们就可以同时存在于Google Play商店或Android设备上。
* 如果你声明产品口味,你还必须声明dimension,并为每个风味分配一个dimension。
*/
flavorDimensions "tier"
productFlavors {
free {
dimension "tier"
applicationId 'com.example.myapp.free'
}
paid {
dimension "tier"
applicationId 'com.example.myapp.paid'
}
}
/**
* 在split块中,可以配置不同的APK构建,每个构建只包含支持的屏幕密度或ABI的代码和资源。
* 你还需要配置你的构建,使每个APK有一个不同的版本代码
* 详细请查看文档《构建多个 APK》
*/
splits {
// 设置 根据屏幕密度构建多个APK。
density {
// 启用或禁用构建多个APK
enable false
// 构建多个APK时,排除这些密度
exclude "ldpi", "tvdpi", "xxxhdpi", "400dpi", "560dpi"
}
}
}
/**
* 模块级构建配置文件中的依赖关系块指定了构建模块本身所需的依赖关系
* 要了解更多信息,查看文档 《添加构建依赖项》
*/
dependencies {
implementation project(":lib")
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
项目依赖
// 项目的依赖
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:27.1.1'
compile 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
}
Android 中的几种依赖方式
| 依赖方式 | 依赖特点 |
|---|---|
compile |
Gradle3.0弃用,完全等同于以下的api用法 |
api |
该种方式导入的依赖,依赖可以传递,即其他 module 都可以访问这个依赖 |
provided |
标识该库只是在编译期使用,但是不会打包到最终的APK中,但是需要保证一点,如果运行时需要用到该库,则需要保证项目中其他地方已导入该库,且是可访问的。 |
implementation |
该种方式导入的依赖不能在编译期被其他模块共享。其他模块只有在运行时才可以共享此模块 |
compileOnly |
用来取代旧版的provided,标识该库只在编译期间使用,运行时可有可无。如果运行时有用到,需要保证有对应的库取代它,这种方法有助于减小APK体积大小,但是要注意运行时依赖是否存在。 |
runtimeOnly |
只在生成apk的时候参与打包,编译时不会参与,很少用 |
注意,从Android Studio3.0后compile不在使用,而是使用api和implementation,api完全等同于以前的compile,用api引入的库整个项目都可以使用,用implementation引入的库只有对应的Module能使用,其他Module不能使用,由于之前的项目统一用compile依赖,导致的情况就是模块耦合性太高,不利于项目拆解,使用implementation之后虽然使用起来复杂了但是做到降低偶合兴提高安全性。
总的来说,Android的库依赖机制可以分为三种:
本地模块依赖
这是一种源码依赖,弊端是每次都需要构建module,当module比较多时构建非常耗时。
// module需要在settings.gradle中通过include引入 implementation project(':librarydict')本地二进制库依赖
本地的jar和aar包需要拷贝到module的
libs文件夹下,弊端是不知道jar和aar的版本号,如果使用这种方式依赖,可以将jar/aar的名字后面加上版本信息后缀,方便确认版本// 可以一次引入libs下所有的jar implementation fileTree(dir: 'libs', include: ['*.jar']) // 也可以指定依赖某一个或几个jar implementation files('libs/dict-v120.jar', 'libs/download-v151.jar') // 依赖libs下的所有aar包 implementation fileTree(dir: 'libs', include: ['*.aar']) // 指定依赖某一个aar implementation (name: 'library-download', ext: 'aar')远程二进制库依赖
使用公共的maven仓库,或者企业内部私有的maven仓库,这种方式能够统一的根据版本号管理依赖的库
// 依赖明确的版本,标明group、name和version implementation group: 'com.android.demo', name: 'library-dict', version: '1.2.0' // 简写如下 implementation 'com.android.demo:library-dict:1.2.0'
Gradle 脚本关联源码
根据自己的具体版本,增加如下配置即可
android{
dependencies {
implementation "com.android.tools.build:gradle:4.1.0"
}
}
实际构建中的问题
由于涉及到远程的库依赖,因此网络问题常常成为主要的构建失败的原因。解决这个问题,通常有两种方法,一个是使用科学上网方式,配置代理
项目的gradle.properties文件中配置代理
# http
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=1080
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=1080
# socks
org.gradle.jvmargs=-DsocksProxyHost=127.0.0.1 -DsocksProxyPort=1080
有时候,大家喜欢使用Android Studio的图形界面去配置代理,但要注意这种方式是存在BUG的,当我们关闭或开启代理的时候,可能不会生效,从而常常导致偏离我们预期的各种连接异常的构建错误。要解决这种问题,需要手动去处理代理配置。
- 首先找到系统用户目录下的
.gradle文件夹,查看是否存在gradle.properties文件,如果有打开它检查是否存在上述的代理配置代码,然后删除代理配置内容 - 然后打开项目工程目录下的
gradle.properties文件,根据需求,注释掉代理配置内容或打开它
另一种解决网络问题的方法,是配置国内的Maven仓库地址:
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0-alpha13'
}
}
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
}
除此外,Gradle工具本身下载也较为缓慢,可以考虑配置一个国内镜像,目前腾讯提供了一个Gradle版本下载的镜像地址:https://mirrors.cloud.tencent.com/gradle/ ,可以下载离线包,也可以修改配置的地址。
示例如下,修改gradle/wrapper/gradle-wrapper.properties文件,配置国内地址
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-6.1.1-all.zip
构建实践
Groovy语言的API用法,查看Groovy API文档
修改生成的apk文件的名称
android {
applicationVariants.all { variant ->
// 当构建类型是release时
if (variant.buildType.name == "release") {
variant.outputs.all {
// 打印apk的路径
println(outputFile.parent)
// 修改apk的文件名
outputFileName = "${new Date().format("yyyy-MM-dd")}_${buildType.name}_${defaultConfig.versionCode}.apk"
}
}
}
}
多渠道打包
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
applicationVariants.all { variant ->
if (variant.buildType.name == "release") {
variant.outputs.all {
println(outputFile.parent)
outputFileName = "${variant.productFlavors[0].name}_${buildType.name}_${defaultConfig.versionCode}.apk"
}
}
}
flavorDimensions "default"
productFlavors {
xiaomi {
applicationId "com.xiaomi"
manifestPlaceholders = [APP_NAME: "@string/app_name",
APP_CHANNEL: "xiaomi",
APP_ICON: "@mipmap/ic_launcher"]
}
google {
applicationId "com.google"
manifestPlaceholders = [APP_NAME: "@string/app_name_google",
APP_CHANNEL: "google",
APP_ICON: "@mipmap/ic_launcher_google"]
}
huawei {
applicationId "com.huawei"
manifestPlaceholders = [APP_NAME: "@string/app_name_huawei",
APP_CHANNEL: "huawei",
APP_ICON: "@mipmap/ic_launcher_huawei"]
}
}
}
AndroidManifest.xml文件中使用变量
<application
android:allowBackup="true"
android:icon="${APP_ICON}"
android:label="${APP_NAME}"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
在友盟统计这类SDK中,常常需要做一个渠道配置,如下
<meta-data
android:name="UMENG_CHANNEL"
android:value="${APP_CHANNEL}" />
这里可以在MainActivity中写一段代码验证配置是否有效
try {
ApplicationInfo info = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
String value = info.metaData.getString("UMENG_CHANNEL");
Log.d("MainActivity",value);
} catch (Exception e) {
e.printStackTrace();
}
版本号自增
// 调用版本号方法
def currentVerCode = getVersionCode();
android {
// ......
defaultConfig {
applicationId "xyz.bczl.androidgradle"
minSdkVersion 21
targetSdkVersion 30
versionCode currentVerCode
versionName "1.0"
}
}
import groovy.json.JsonSlurper
def getVersionCode(){
def rootPath = getRootDir().absolutePath
def jsonSlurper = new JsonSlurper()
def jsonObj = jsonSlurper.parseText(new File(rootPath,"my_config.json").text)
def verCode = jsonObj.versionCode.toInteger()
// 只有运行assemble构建任务时才自增
if (gradle.startParameter.taskNames[0].toString().startsWith("assemble")) {
verCode +=1
new File(rootPath,"my_config.json").withWriter('utf-8') {
writer -> writer.writeLine "{\"versionCode\":$verCode}"
}
}
return verCode
}
附录:命令行编译
gradlew -v
gradlew clean
gradlew build --info # 编译并打印日志
gradlew assemble
gradlew assembleDebug # 编译并打Debug包
gradlew assembleRelease # 编译打Release包
gradlew installDebug # 打Debug包并安装
gradlew installRelease # 打Release包并安装
gradlew dependencies --info # 查看详细的依赖信息
公众号“编程之路从0到1”
