前言
最近遇到了问题,大概是 APPT2 ERROR
错误,这个错误很常见,说的是 .9图片
有问题,但是网上的回答都非常的零散和不够系统。编译的时候从 LOG终端
中也看不了太多信息。网上的建议是加编译参数 --stacktrace --debug
,所以就想着把 Gradle 构建系统详细了解下。
以下说的大多是学习总结,一些概念可能描述的不准确,更多信息请参考文中和文末的资料链接。
问题
- Android Studio, Gradle, Groovy 的关系
- Android Studio 生成的项目中
build.gradle
,settings.gradle
,gradle.properties
,gradle-wrapper.properties
这些文件的作用 - 如何构建不同渠道的多个包
- 如何构建服务器地址不同的包,甚至逻辑不同的版本,而不是新开分支
回答
Android Studio, Gradle, Groovy 的关系
-
Groovy
是基于JVM的一门语言,类似于其他基于JVM的语言一样,如JAVA
,KOTLIN
等,最终生成JAVA字节码运行在JVM虚拟机上。 我们从 Groovy官网 中下载安装,并了解语言特性
。println ("Groovy Demo") def num1 = 6 num2 = 3 ppp "你好","Groovy" println "$num1 的平方是 ${sqrt(num1)}" println "$num1 + $num2 = ${ add(num1,num2)}" println "平方求和 ${sqrtSum(num1, num2, {var1, var2->var1 + var2})}" def ppp(m1, m2) { println "$m1 $m2" } def sqrtSum(var1, var2, action) { return sqrt(action(var2, var2) ) } def add(x, y) { x + y } def sqrt(x) { x * x } def mul(var1, var2) { return var1 - var2 } def div = { int var1, int var2-> var1 / var2 }
Groovy
了解个大概,有个概念差不多了。具体的可以参考官网。 -
Android Studio 编译时就是把整个构造任务委托给
Gradle
来处理,可以构建Android项目还有Ant
,Buck
等。Gradle
可以说是脚本,或者是语言,又或者是一个平台。Groovy
基于JVM,Gradle
基于Groovy
。Gradle
可以实现自动化打包,自动化测试,项目依赖管理等功能。Gradle
基于约定优先配置
原则,能够非常智能的知道该如果加载库,编译源码,加载资源,以及生成目标产物的最终位置。而配置的各种的插件,就是定义了一系列的规则。比如,java插件,android插件。apply plugin: 'com.android.application'
Gradle
编译的时候是执行一系列的task,可以使用下列命令查看所有的taskgradle task --all
常用的有
assemble
,assembleDebug
,assembleRelease
,build
,clean
。
下面的代码直观感受下,如何编写build.gradle
, 以及如何执行的。我们先新建文件夹GradleDemo
,然后新建文件build.gradle
, 基本结构就好了。在build.gradle
写上下面的代码:println "Gradle demo" task sayHello { println "hello from task 'sayHello'" }
如果我们执行
gradle task --all
将会看到sayHello
这个task
, 我们执行下gradle sayHello
将会看到如下输出
Gradle demo
hello from task 'sayHello'
:sayHello UP-TO-DATE
BUILD SUCCESSFUL
Total time: 2.401 secs同理,我们执行
gradle -q assemble
的时候也就是执行plugin
事先定义的复杂的task
assemble
进行构建。 显然加载不同的插件,Gradle
可以构建java
,ANDROID
,C/C++
等不同的项目。
Android Studio 生成的项目中 build.gradle
, settings.gradle
, gradle.properties
, gradle-wrapper.properties
这些文件的作用
-
我们先看
gradle-wrapper.properties
文件, 位于project/app/gradle/wrapper
中,这个是用于gradle
版本管理的,我们的不同项目现在都可以方便的指定gradle版本
。distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-3.3-all.zip如果在指定的目录中存在
3.3
的版本,就直接使用,如果不存在就去指定的服务器中下载。如果是下载的gradle
,可以使用gradle wrapper
命令来生成wrapper
,它将会生成.gradle
,gradle
,gradlew.bat
,gradlew
等文件和文件夹。现在我们可以使用gradlew.bat --version
查看下运行环境。它将显示
gradle
,groovy
,jvm
等使用版本。 -
我们接着看
gradle.properties
文件,顾名思义这个是gradle
配置属性值的地方。比如jdk
(org.gradle.java.home
)位置,编译参数(org.gradle.jvmargs)
等。具体可以查看 官网说明 build_environment。另外一种使用情况是定义一下统一的配置变量,比如compileSdkVersion
,buildToolsVersion
,supportLibrary
等变量。举个例子:
在gradle.properties
中添加如下代码:greetingWord=Hi, groovy
在
build.gradle
中添加一个task
,task sayHi { println rootProject.buildDir println "sayHi word from file $greetingWord" }
它将会打印出 Hi, groovy ,这是我们定义的值。
我们接着看
settings.gradle
文件,这个文件是针对多项目的配置文件,比如这个app
项目引入来了其它库,对于gradle
来说,就是引入了project
, 这个文件使用include
函数来说明了构建项目的mul-project
。-
我们最后看
app
模块中的build.gradle
文件,这个文件是单独对该 模块/PROJECT 进行配置的。apply plugin: 'com.android.application' //加载android 应用插件 android { //dsl android 配置部分 compileSdkVersion 25 //这两行必须要有 buildToolsVersion "26.0.2" //这两行必须要有 defaultConfig { applicationId "com.bbg.gradledemo" minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { //依赖 compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' }
里面的代码块是什么意思,更具体的可以参考 官网说明。
如何构建不同渠道的多个包
这个问题和接下来的问题,将涉及到两个主要概念,build types
和 product flavors
,这两个概念弄明白了,我们的问题也就差不多解决了。 build types
是什么? build types
是站在程序员的角度来说的,比如 debug
, release
版本,它并不为终端用户所感知。而 product flavors
则可以理解为面向终端用户的,比如 免费版
, 付费版
,又比如各大应用市场的不同版本。
我们看个复杂的实例,构建不同渠道的免费和收费的DEBUG、RELEASE两种版本。这个例子分开来看的话,一共是3个阶段。第一阶段,构建 release
和 debug
版本;第二阶段,构建 channel1
和 channel2
两个不同渠道的版本;第三阶段,构建 free
和 pay
收费模式版本。组合起来的话,一共 2*2*2
最终会构建成 8
版本。
buildTypes
构建apk类型,即 debug
和 release
; productFlavors
构建用户感知的app变体,即不同的app; flavorDimensions
构建多维度的 flavor
,可以理解为多维度的用户使用情形。比如该例子说的是,既要区分不同渠道,又要对不同渠道做 free
和 pay
两种产品的区分。具体的写法参考下面的 build.gradle
中的代码。下面的代码也展示如何在 AndroidManifest.xml
中插入我们配置的值,以及如何在 build.gradle
中配置运行时变量。 在项目代码中还展示了如何读取 meta-data
的值和在 build.gradle
中配置的值。
当我们 sync
我们的项目时,IDE中的 Build Variants
选项卡也会出现对应的各种APP变体。更官方的 Build Variants
可以参考这里,而 build.gradle
中 block代码
块中的属性值或方法具体是什么意思或该怎么用,可以参考这里。
下面是我们编译出来的共 8
个APP变体。 8
种不同的 flavors
。
app-channe2-free-debug.apk
app-channe2-free-release.apk
app-channe2-pay-debug.apk
app-channe2-pay-release.apk
app-channel-free-debug.apk
app-channel-free-release.apk
app-channel-pay-debug.apk
app-channel-pay-release.apk
//默认配置
defaultConfig {
//应用包名,注意和 AndroidManifest.xml 中的 packageName 区别,
//后者影响资源的R类的生成; 前者是唯一包名
applicationId "com.bbg.gradledemo"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//manifest 占位符, map类型
//buildConfigField 生成 BuildConfig 类的静态值,可以代码访问
//
manifestPlaceholders = ["KEY_TYPE": "KEY-default"]
buildConfigField("String", "var1", "\"var-default\"")
}
//签名配置
signingConfigs {
config {
keyAlias 'key0'
keyPassword '123456'
storeFile file("$rootProject.rootDir/keystore.jks")
storePassword '123456'
}
}
//build types
buildTypes {
release {
minifyEnabled true
debuggable true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
applicationIdSuffix ".release"
}
debug {
minifyEnabled true
debuggable true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
applicationIdSuffix ".debug"
}
}
//多维度flavor解决方法,前后顺序影响生成的 variants 类型
//然后在product flavors 指定 deminsion
flavorDimensions "channel", "mode"
productFlavors {
channel {
dimension "channel"
manifestPlaceholders = ["KEY_TYPE": "KEY-channel"]
buildConfigField("String", "var1", "\"var-channel\"")
}
channe2 {
dimension "channel"
manifestPlaceholders = ["KEY_TYPE": "KEY-channe2"]
buildConfigField("String", "var1", "\"var-channe2\"")
}
free {
dimension "mode"
buildConfigField("String", "mode", "\"mode-free\"")
}
pay {
dimension "mode"
buildConfigField("String", "mode", "\"mode-pay\"")
}
}
为不同的Flavor变体加入不同的资源和逻辑
从上面中的回答,我们可以知道,这种方式已经可以满足我们提出的要求了。一些不同的 Build Variants
, 可以根据变量值,在代码中进行设置,进行不同的逻辑处理。 假如我们不同的 Variants
需要不一样的启动图标,甚至逻辑部分不同,有没有另外的解决方案。答案是有的,当我们在 build.gradle
中配置了 buildTyps
和 productFlavors
的时候,Android Studio
就逻辑关系将每个源代码和资源分组为 源集
Android Studio 按逻辑关系将每个模块的源代码和资源分组为源集。模块的 main/ 源集包括其所有构建变体共用的代码和资源。其他源集目录为可选项,在您配置新的构建变体时,Android Studio 不会自动为您创建这些目录。不过,创建类似于 main/ 的源集有助于让 Gradle 只应在构建特定应用版本时使用的文件和资源井然有序
如果不同的 源集
包含同一文件的不同版本,Gradle
将按以下优先顺序决定使用哪一个:
构建变体 > 构建类型 > 产品风味 > 主源集 > 库依赖项
我们实践下,我们把上面的 channe2
变体的应用图标修改,把第一次进入时候的是否 pay
的逻辑改成 free
。具体的代码请参考Github。我们创建文件时,可以借助 IDE
来选择不同的 源集
。这样我们的 app
目录结构大概是这样:
```
app
|
└─src
├─androidTest
│ └─java
│ └─com
│ └─bbg
│ └─gradledemo
├─channe2
│ └─res
│ ├─mipmap-xhdpi
│ └─values
├─free
│ ├─java
│ │ └─com
│ │ └─bbg
│ │ └─gradledemo
│ └─res
│ └─values
├─main
│ ├─java
│ │ └─com
│ │ └─bbg
│ │ └─gradledemo
│ └─res
│ ├─drawable
│ ├─layout
│ ├─mipmap-hdpi
│ ├─mipmap-mdpi
│ ├─mipmap-xhdpi
│ ├─mipmap-xxhdpi
│ ├─mipmap-xxxhdpi
│ └─values
├─pay
│ ├─java
│ │ └─gradledemo
│ └─res
│ └─values
└─test
└─java
└─com
└─bbg
└─gradledemo
```
我们在 chane2
变体中引入 ic_launcher
,在 pay
和 free
变体中处理是否收费逻辑。编译的时候,可以把我们想要的都一起编译出来,实现自动化,简直完美。显然, gradle
知道该如何合并。我们可以参考这里,了解是 gradle
是如何处理合并的。
结尾
到这里,我们顺利的解决了提出的问题。发现 GRADLE
构建系统太出色了。这么好的工具,当然要用起来。期间查阅学习了较多资料,写成此文,希望对读者有帮助。文中有较多参考链接可以扩展查阅,最后末尾附上一些相关的参考补充资料。
如果文章对你有帮助,请点小心心或赞赏支持。欢迎留言交流。
Gradle 完整指南(Android)
深入理解Android(一):Gradle详解
构建配置
Groovy File IO Document
gradle 编译不过,Build可以问题解决方法
gradle实战
Android SDK构建视频
Android Gradle
Build Type, Flavour and Build Variant