Gradle的卖家秀和买家秀

Gradle的卖家秀和买家秀。

这里写图片描述

大部分人都经历过高中,不难发现高考650分的人和450分的人书单基本上是一样的,这是为什么呢?

这往往并不是因为他们接触了更多的信息,而是因为他们处理信息的方式与众不同。他们往往善于整理信息,并且获得“系统化知识体系”。

写在前面的话

今天翻了翻自己之前的博客,发现上一篇还是在2017-01-12发的。不知不觉3个月就过去了,之间好像再也没有很系统的总结过东西了。仔细想想一年也没几个3个月,还是有点方的。

3个月说长不长,但也足够发生很多事情。比如我投身到了直播(YOLO)行业,开始接手前人(PiasypromeG等等)的项目,之间曲折自不需多说。好在已经适应了现在的节奏,博客也会慢慢更新。

其实直播并没有想象中的那样复杂,概括的讲无非就是采集端(推流端)和观众端(拉流端)。然后再添加一些礼物系统、弹幕系统、聊天系统、人脸识别、游戏互动、连麦互动、美颜等功能使之丰满。

关于直播,我们暂且不进行深入讨论。今天我们来说一说Gradle相关的东西。可能好多人会有这样的感慨,为什么我看了那么多“Gradle从入门到精通”,仍然管理不好自己项目的构建呢?今天我们就来一起整理下Gradle相关的那些知识点,也许会有收获哦。

Gradle思维导图

为了方便梳理Gradle的知识点,我画了下面的思维导图,有不足的地方还望大家指出。其实这些点基本上每个都可以写一篇单独的博客来扩展,而且有很多已经总结的很好了,这里就不赘述了。

而关于我们为什么要用Gradle,Gradle能做什么这样的问题,弄清楚也是很有必要的。比较官方的说法是:Gradle使用易懂的DSL语法 ,将开发过程中需要的编译、构建、测试、打包以及部署工作,变得非常简单、而且方便重复使用。即Gradle可以从开发过程的各个环节来简化我们的开发。

这里写图片描述

Gradle实战

俗话说的好:Talk is cheap,show me the code。接下来我就抛砖引玉,分享下我们的Gradle是怎样配置的。

项目根目录下的build.gradle文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: 'buildsystem/dependencies.gradle'
buildscript {
repositories {
jcenter()
}
dependencies {
apply from: 'buildsystem/dependencies.gradle'
classpath "com.android.tools.build:gradle:$gradleAndroidVersion"
classpath 'com.github.promeg:android-multi-channel-plugin:0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

app下的build.gradle文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import java.text.SimpleDateFormat
import static org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS
import static org.apache.tools.ant.taskdefs.condition.Os.isFamily
if (!testDevelopURC) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
apply plugin: 'android-multi-channel'
def keyConfigPath
if (isFamily(FAMILY_WINDOWS)) {
keyConfigPath = System.getenv('USERPROFILE') + File.separator + ".ssh" + File.separator +
"androidKeystore.properties"
} else {
keyConfigPath = System.getenv('HOME') + "/.ssh/androidKeystore.properties"
}
Properties props = new Properties()
if (new File(keyConfigPath).exists()) {
props.load(new FileInputStream(file(keyConfigPath)))
}
android {
def rootDep = rootProject.ext
compileSdkVersion rootDep.androidCompileSdkVersion
buildToolsVersion rootDep.androidBuildToolsVersion
defaultConfig {
minSdkVersion rootDep.androidMinSdkVersionn
targetSdkVersion rootDep.androidTargetSdkVersion
versionCode rootDep.releaseVersionCode
versionName rootDep.releaseVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
if (!testDevelopURC) {
applicationId "tv.yoloyolo.renlei.gradlepractice"
} else {
consumerProguardFiles 'proguard-rules.pro', 'proguard-fresco.pro'
}
buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""
manifestPlaceholders = [EASEMOB_APPKEY: "publish#publish"]
}
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'LICENSE.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/services/javax.annotation.processing.Processor'
exclude 'META-INF/rxjava.properties'
}
signingConfigs {
release {
storeFile file(props['keystore'])
storePassword props['keystore.password']
keyAlias "promegu"
keyPassword props['key.password']
}
}
dexOptions {
maxProcessCount 8
javaMaxHeapSize "6g"
}
testOptions.unitTests.all {
testLogging {
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
outputs.upToDateWhen { false }
showStandardStreams = true
}
// configure the test JVM arguments
jvmArgs '-noverify'
}
aaptOptions {
cruncherEnabled false
}
publishNonDefault true
productFlavors {
dev {
ndk {
abiFilter "armeabi"
}
buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""
manifestPlaceholders = [EASEMOB_APPKEY: "test#test"]
}
production {
ndk {
abiFilter "armeabi"
}
// inherit from default config
}
}
buildTypes {
debug {
minifyEnabled false
debuggable true
ext.enableCrashlytics = false
signingConfig signingConfigs.release
}
release {
minifyEnabled false
debuggable false
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
multiFlavors {
prefix = "TEST_";
def released = new SimpleDateFormat('yyyy-MM-dd').format(new Date())
subfix = "_$released";
defaultSigningConfig = android.signingConfigs.release
channelConfig {
production {
childFlavors =
["Tencent", "360", "Baidu", "Alibaba", "GFan", "Sogou",
"Lenovo", "XiaoMi", "Meizu", "OPPO", "Huawei", "GooglePlay"]
}
}
}
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'
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:shadows-support-v4:' + rootProject.ext.robolectricVersion
compile('com.github.promeg:android-multi-channel-plugin-lib:0.1') {
exclude module: 'appcompat-v7'
}
}

外部的dependencies.gradle文件

我在项目根目录创建了一个叫buildsystem的文件夹,dependencies.gradle文件放到了这个文件夹下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
allprojects {
repositories {
jcenter()
}
}
ext {
androidBuildToolsVersion = '25.0.2'
androidCompileSdkVersion = 25
androidMinSdkVersionn = 15
androidTargetSdkVersion = 25
androidSupportSdkVersion = '25.1.0'
gradleAndroidVersion = '2.2.3'
releaseVersionCode = 1
releaseVersionName = "1.0"
robolectricVersion = '3.0'
}

项目根目录下的gradle.properties文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
#org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.jvmargs=-Xmx8192m -XX\:MaxPermSize\=3072m
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.parallel=true
testDevelopURC=false

应用到的知识点

基本上就是这样了,这几个文件的配置是从YOLO项目简化而来的,不过不影响研究。接下来我们看下里边用到的点。由于具体每个点的实现网上好多已经介绍的很全面了,这里我只说下大概的点,具体大家可以自行google。后边我也会附上一些链接供大家参考。

依赖外部配置

如果我们的配置文件过大或者有需要复用的配置,可以考虑抽离成一个单独的文件,引入方式如下:

1
apply from: 'buildsystem/dependencies.gradle'

全局配置

Stack Overflow:How to define common android properties for all modules using gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ext {
androidBuildToolsVersion = '25.0.2'
androidCompileSdkVersion = 25
androidMinSdkVersionn = 15
androidTargetSdkVersion = 25
androidSupportSdkVersion = '25.1.0'
gradleAndroidVersion = '2.2.3'
releaseVersionCode = 1
releaseVersionName = "1.0"
robolectricVersion = '3.0'
}

占位符

如果我们在manifest中定义了占位符的话,可以在module的build.gradle中可以将其进行赋值。

1
2
3
4
5
<meta-data
android:name="EASEMOB_APPKEY"
android:value="${EASEMOB_APPKEY}" />
manifestPlaceholders = [EASEMOB_APPKEY: "publish#publish"]

ProductFlavors

对于ProductFlavors,我们可以在Build->Select Build Variant…选择我们debug状态下使用的默认配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
productFlavors {
dev {
ndk {
abiFilter "armeabi"
}
buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""
manifestPlaceholders = [EASEMOB_APPKEY: "test#test"]
}
production {
ndk {
abiFilter "armeabi"
}
// inherit from default config
}
}

BuildTypes

Gradle官方文档:BuildType,这篇讲的是有BuildType有哪些可以配置东西。

1
2
3
4
5
6
debug {
minifyEnabled false
debuggable true
ext.enableCrashlytics = false
signingConfig signingConfigs.release
}

BuildConfig

自定义BUILDCONFIG

1
buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""

dependencies

包括依赖的几种类型,以及如果解决依赖冲突。

Gradle官方文档:依赖项管理基础知识

Android官方文档:Add Build Dependencies

排除传递依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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'
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:shadows-support-v4:' + rootProject.ext.robolectricVersion
compile('com.github.promeg:android-multi-channel-plugin-lib:0.1') {
exclude module: 'appcompat-v7'
}
}

签名

这里用到了一些Groovy中的语法,主要用来找到自己的androidKeystore.properties文件并从中读取配置。

Android官方文档:签署您的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def keyConfigPath
if (isFamily(FAMILY_WINDOWS)) {
keyConfigPath = System.getenv('USERPROFILE') + File.separator + ".ssh" + File.separator +
"androidKeystore.properties"
} else {
keyConfigPath = System.getenv('HOME') + "/.ssh/androidKeystore.properties"
}
Properties props = new Properties()
if (new File(keyConfigPath).exists()) {
props.load(new FileInputStream(file(keyConfigPath)))
}
signingConfigs {
release {
storeFile file(props['keystore'])
storePassword props['keystore.password']
keyAlias "promegu"
keyPassword props['key.password']
}
}

各种options配置

Gradle官方文档:Android Plugin DSL Reference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'LICENSE.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/services/javax.annotation.processing.Processor'
exclude 'META-INF/rxjava.properties'
}
dexOptions {
maxProcessCount 8
javaMaxHeapSize "6g"
}
testOptions.unitTests.all {
testLogging {
events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
outputs.upToDateWhen { false }
showStandardStreams = true
}
// configure the test JVM arguments
jvmArgs '-noverify'
}
aaptOptions {
cruncherEnabled false
}

多渠道打包

这个多渠道打包其实是之前同事自己写了一个Gradle插件。传送门

当然如果你熟悉Groovy语法,也可以自己编写。编写Gradle插件

1
2
3
4
5
6
7
8
9
10
11
12
13
multiFlavors {
prefix = "TEST_";
def released = new SimpleDateFormat('yyyy-MM-dd').format(new Date())
subfix = "_$released";
defaultSigningConfig = android.signingConfigs.release
channelConfig {
production {
childFlavors =
["Tencent", "360", "Baidu", "Alibaba", "GFan", "Sogou",
"Lenovo", "XiaoMi", "Meizu", "OPPO", "Huawei", "GooglePlay"]
}
}
}

加速编译

Android官方文档:优化你的编译速度

1
2
3
4
5
6
7
8
9
dexOptions {
maxProcessCount 8
javaMaxHeapSize "6g"
}
org.gradle.jvmargs=-Xmx8192m -XX\:MaxPermSize\=3072m
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.parallel=true

Other

除了上边提到的那些点之外,还有一些总结的比较好的链接。其实国内好多博客都大同小异,都参考自官方文档,所以推荐直接看官方文档。

结语

可能上边说的内容比较多,不过大家可以按需查看。而且工欲善其事,必先利其器。对于我们来说,学好Gradle无疑可以对开发提供很大的帮助,节省更多的时间。

感谢官方文档详细的教程,也感谢喜欢分享的那些朋友,如果上边链接有侵权的话,请告诉我,我会删掉。

任磊_Coder wechat
关注博主是一种态度,评论博主是一种欣赏。
坚持原创技术分享,您的支持将鼓励我继续创作!