一、Groovy脚本
Groovy是一种动态语言,Groovy脚本基于Java且拓展了Java,都在Java虚拟机中运行。当运行Groovy脚本时它会先被编译成Java类字节码,然后通过JVM虚拟机执行这个Java字节码类。
1、如何安装Groovy?
安装Groovy在各种Bash下(bash 是一个为GNU计划编写的Unix shell)都是通用的,具体如下命令就可搞定:
$ curl -s get.sdkman.io | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk install groovy
$ groovy -version
//至此就可以享用了!
我们在写Groovy代码时可以直接使用自己喜欢的文本编辑器编辑就可以(studio有内置的 Tools-->IDE Scripting Console-->Groovy)
然后以.groovy后缀保存,然后在终端执行如下命令即可运行:
$ groovy ./HaiJiaTest.groovy
二、Gradle
通俗的说:gradle是打包用的,Gradle核心是基于Groovy的领域特定语言(DSL)。Ant和Maven都是基于XML的构建工具,Gradle是用Groovy编写的构建工具。Gradle通过编写一个名为build.gradle的脚本文件对项目进行设置,再根据这个脚本对项目进行构建(复杂的项目也有其他文件)。当前其支持的语言限于Java、Groovy、Kotlin和Scala。
Gradle里有两个基本概念:项目(projects)和任务(tasks)。
项目由多个任务组成,一个项目可以理解为提供给不同设备的构建版本,如桌面版、网页版、安卓版、iOS版等等,也可以理解为一种行为,例如部署应用到生产环境。任务相当于Ant的target,可以理解成一个构建中原子性的工作,例如编译、打包、执行等。需要注意的是,Ant中他自己的命令例如javac、copy等也叫做task,但Ant的task远没有Gradle的task那么自由。
为什么要用Gradle
ant可以自动化打包逻辑。
maven也可以自动化打包,相比于ant,它多做的事是帮你下载jar包。
但是maven的打包逻辑太死板,定制起来太麻烦,不如ant好用。gradle就是又能自动下jar包,又能自己写脚本,并且脚本写起来还比ant好用的这么个东西。
Gradle DSL基础
Gradle的实质是配置脚本,执行一种类型的配置脚本时就会创建一个关联的对象,譬如执行Build script脚本就会创建一个Project对象,这个对象其实就是Gradle的代理对象。下面给出来各种类型Gradle对应的对象类型:
脚本类型 | 关联对象类型 |
---|---|
Build script | Project :每个build.gradle会转换成一个Project对象。 |
Init script | Gradle :构建初始化时创建,整个构建执行过程中只有这么一个对象,一般很少去修改这个默认配置脚本。 |
Settings script | Settings : 每个settings.gradle会转换成一个Settings对象。 |
可以看见,当我们编写指定类型Gradle脚本时我们可以直接使用关联对象的属性和方法;当然了,每个脚本也都实现了Script接口,也就是说我们也可以直接使用Script接口的属性与方法。
1、构建脚本Build script(Project)
在Gradle中每个待编译的工程都是一个Project(每个工程的build.gradle对应一个Project对象),每个Project在构建的时候都包含一系列Task,这些Task中很多又是Gradle的插件默认支持的。
PS:所谓的我们编写Gradle脚本,实质大多数时候都是在编写构建脚本Build script,所以说Project和Script对象的属性和方法等API非常重要。
每一个Project对象和build.gradle一一对应,一个项目在构建时都具备如下流程:
-
为当前项目创建一个Settings类型的实例。
-
如果当前项目存在settings.gradle文件,则通过该文件配置刚才创建的Settings实例。
-
通过Settings实例的配置创建项目层级结构的Project对象实例。
-
最后通过上面创建的项目层级结构Project对象实例去执行每个Project对应的build.gradle脚本。
2、初始化脚本Init script(Gradle)
初始化脚本Init script(Gradle)类似于Gradle的其他类型脚本,这种脚本在构建开始之前运行,主要的用途是为接下来的Build script做一些准备工作。
初始化脚本的Gradle对象代表了Gradle的调运,我们可以通过调用Project对象的getGradle()方法获得Gradle实例对象。
3、设置脚本Settings script(Settings)
在对工程进行配置(譬如多项目树构建)时Settings实例与settings.gradle文件一一对应,它用来进行一些项目设置的配置。这个文件一般放置在工程的根目录。譬如:
4、Build生命周期
Gradle的构建脚本生命周期具备三大步,如下:
5、Gradle多项目构建:
多项目构建总是需要指定一个树根,树中的每一个节点代表一个项目,每一个Project对象都指定有一个表示在树中位置的路径;在设置文件中我们还可以使用一套方法来自定义构建项目树。
//分层布局的多项目构建settings.gradle文件
include 'project1', 'project2:child', 'project3:child1'
上面例子中把project的路径作为了include方法的参数,譬如上面的’project3:child1’参数就指定了物理路径的project3/child1(project3/child1是相对于多项目根路径的相对路径),这也同时意味着会创建’project3’和’project3:child1’两个project。
//平面布局的多项目构建settings.gradle文件
includeFlat 'project3', 'project4'
上面例子中includeFlat方法接受目录名作为参数,但是特别注意,这些项目目录必须是根目录的兄弟目录。
当然了,设置文件中创建的多项目树其实是由项目描述符来描述的,我们可以在设置文件中随时修改这些描述符。如下:
//settings.gradle
rootProject.name = 'main'
project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
project(':projectA').buildFileName = 'projectA.gradle'
可以看见,如上例子通过描述符更改名称和项目目录,并且建立了一个项目的文件。
Gradle构建初始化Initialization:
在初始化阶段如果我们在根路径下直接指明settings.gradle文件和相关配置则构建初始化就会直接按照我们的设置去构建项目,如果我们没指明settings.gradle文件则Gradle会以一定的规则去寻找settings.gradle文件,然后依据寻找结果的不同去决定如何构建项目。
三、Gradle在android中的应用
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "com.project.haijia"
// 配置生成的 BuildConfig 文件中的常量
buildConfigField "String", "CHANNEL", '"PLAY_STORE"'
minSdkVersion 16
targetSdkVersion 25
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
buildConfigField "String", "API_URL", '"http://dev.goodev.org/"'
buildConfigField "boolean"�, "SHOW_LOG"�, "true"
}
release {
minifyEnabled false
// 混淆文件的位置
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 移除lint检查的error
lintOptions {
abortOnError false
}
}
//dependencies中配置的是这个项目中要用的类库
dependencies {
//表示包括之前在libs文件夹下的所有jar包
compile fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
//表示测试代码的时候依赖junit这个库
testCompile 'junit:junit:4.12'
// 编译extras目录下的JiaAndroid模块
compile project(':extras:JiaAndroid')
//表示编译期依赖gson这个库
compile 'com.google.code.gson:gson:2.8.0'
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', {
exclude group: 'com.android.support', module: 'support-annotations'
})
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.0.1'
}
productFlavors {
youmi {
applicationId = "org.goodev.material.youmi"
buildConfigField "String", "CHANNEL", '"YOUMI"'
resValue "string", "app_name", "Material free"
}
pro {
applicationId = "org.goodev.material.pro"
buildConfigField "String", "CHANNEL", '"OTHER"'
resValue "string", "app_name", "Material pro"
}
}
signingConfigs {
release {
storeFile "${System.env.PRIVATE_KEY}"
keyAlias "${System.env.ALIAS}"
storePassword "${System.env.STORE_PW}"
keyPassword "${System.env.APP_PW}"
}
}
两个配置项 android 和dependencies
- android中配置的是源码的编译中的一些设置,如 sdkversion
- dependencies中配置的是这个项目中要用的类库
通过 System.env 来访问系统环境变量中的值,这样你就可以把一些私有的内容排除在代码外,这样当你提交代码的时候,就不会泄露这些内容。还可以把私有数据放到local.properties 文件中:
signingConfigs {
release {
def Properties localProps = new Properties()
localProps.load(new FileInputStream(file('local.properties')))
def Properties keyProps = new Properties()
if (localProps['keystore.props.file'] != null) {
keyProps.load(new FileInputStream(file(localProps['keystore.props.file'])))
}
storeFile keyProps["store"] != null ? file(keyProps["store"]) : null
keyAlias keyProps["alias"] ?: ""
storePassword keyProps["storePass"] ?: ""
keyPassword keyProps["pass"] ?: ""
}
}
动态生成 Android Manifest 中的内容
为了让所有的 productFlavors 都可以安装到同一个手机上, 安卓系统要求不同的应用具有不同的 provider 等内容,如果冲突则其他应用无法安装,可以通过如下方式来解决该问题
<permission android:name="${applicationId}.permission.XX"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.XX" />
<provider android:name=".MyProvider"
android:authorities="${applicationId}.provider"
… />
不同的 productFlavors 具有不同的 permission
四、Gradle常用命令
-
./gradlew -v 版本号
-
./gradlew clean 清除9GAG/app目录下的build文件夹
-
./gradlew installRelease Release模式打包并安装
-
./gradlew uninstallRelease 卸载Release模式包
-
./gradlew build 检查依赖并编译打包
这里注意的是 ./gradlew build 命令把debug、release环境的包都打出来,如果正式发布只需要打Release的包,该怎么办呢,下面介绍一个很有用的命令 assemble, 如 -
./gradlew assembleDebug 编译并打Debug包
-
./gradlew assembleRelease 编译并打Release的包
除此之外 assemble 还能和 Product Flavor 结合创建新的任务,其实 assemble 是和 Build Variants 一起结合使用的,而 Build Variants = Build Type + Product Flavor -
./gradlew assembleWandoujiaRelease 打包wandoujia渠道的release版本
如果我们,则: -
./gradlew assembleWandoujia 只打wandoujia渠道版本,此命令会生成wandoujia渠道的Release和Debug版本
- ./gradlew assembleRelease 这条命令会把Product Flavor下的所有渠道的Release版本都打出来。
assemble 命令创建task有如下用法:
- assemble: 允许直接构建一个Variant版本,例如assembleFlavor1Debug。
- assemble: 允许构建指定Build Type的所有APK,例如assembleDebug将会构建Flavor1Debug和Flavor2Debug两个Variant版本。
- assemble: 允许构建指定flavor的所有APK,例如assembleFlavor1将会构建Flavor1Debug和Flavor1Release两个Variant版本。
五、多渠道打包
这里举例友盟为主
AndroidManifest.xml
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
在build.gradle设置productFlavors
android {
productFlavors {
xiaomi {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
}
_360 {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
}
baidu {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
}
wandoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
}
}
}
或者批量修改
android {
productFlavors {
xiaomi {}
_360 {}
baidu {}
wandoujia {}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
一个完整的多渠道例子
apply plugin: 'com.android.application'
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "com.haijia.*"
minSdkVersion 16
targetSdkVersion 26
versionCode 1
versionName "1.0"
// dex突破65535的限制
multiDexEnabled true
// 默认是umeng的渠道
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
}
lintOptions {
abortOnError false
}
signingConfigs {
debug {
// No debug config
}
release {
storeFile file("../yourapp.keystore")
storePassword "your password"
keyAlias "your alias"
keyPassword "your password"
}
}
buildTypes {
debug {
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
versionNameSuffix "-debug"
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
}
release {
// 不显示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
minifyEnabled true
zipAlignEnabled true
// 移除无用的resource文件
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// 输出apk名称为boohee_v1.0_2015-01-15_wandoujia.apk
def fileName = "boohee_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
}
}
// 友盟多渠道打包
productFlavors {
wandoujia {}
_360 {}
baidu {}
xiaomi {}
tencent {}
taobao {}
...
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
...
}
网友评论