Gradle总结
一. 构建生命周期
A. 初始化阶段:
gradle先找到settings.grade文件。如果该文件不存在, 则gradle会假定你只有一个模块。如果有多个模块,settings.gradle文件定义了这些模块的位置。
如果这些模块有其自己的build.gradle文件,gradle将会运行它们,并且将他们合并到构建任务中。
project实例在这儿创建,如果有多个模块,即有多个build.gradle文件,多个project将会被创建
B. 列表内容:
build.gradle脚本将会执行,为每个project创建和配置所有的tasks
C. 列表内容:
gradle会决定哪个tasks会被执行,哪个tasks会被执行完全依赖于开始构建时传入的参数和当前所在的文件夹位置。
二. 构建版本–每个module都可以定义多个构建版本, 在各module的build.gradle中定义:
android {
buildTypes { //在该方法里定义 "该module" 有哪些构建版本
debug { //定义debug版本. Studio默认构建版本
}
release { //定义release版本. Studio默认构建版本
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
staging { //用户自定义版本, 可重写默认构建版本的属性
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
buildConfigField "String", "API_URL", "\"http://staging.example.com/api\""
}
staging2.initWith(buildTypes.debug) //继承已有的构建版本, 再根据需要重写属性
staging2 {
applicationIdSuffix ".staging2"
versionNameSuffix "-staging2"
debuggable = false
}
}
}
对于 applicationId 各构建版本如下:
Debug: com.package
Release: com.package
Staging: com.package.staging
三. SourceSets–每个构建版本都有自己的source sets, 默认情况下, 该文件夹不会自动为你创建, 所以需要手动创建:
在 该module/src目录中创建: debug, release, staging, staging2 目录
注: 实际上每个构建变体(构建版本 和 风味 的不同组合)都可以有自己不同的SourceSets, 如对于1和4:
则可以在 该module/src目录中创建:
redDebug, redRelease, redStaging, redStaging2
blueDebug, blueRelease, blueStaging, blueStaging2文件夹
四 .依赖包–每个构建版本都可以有自己不同的依赖包, 在 该module/build.gradle中定义:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.0'
debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3' //只用于debug版本的依赖
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3' //只用于release版本的依赖
staging 'com.google.code.gson:gson:2.6.2' //只用于staging版本的依赖
}
五 .ProductFlavors–和构建版本不同, product flavors 用来为一个app创建不同版本
如果不确定是需要构建版本还是product flavors, 你应该问你自己,你是需要内部使用还是外部使用的apk。
如果需要一个完全新的app去发布, 和之前的版本完全隔离开,那么你需要product flavors。否则你只是需要构建版本。
android {
productFlavors {
red {
applicationId 'com.gradleforandroid.red'
versionCode 3
}
blue {
applicationId 'com.gradleforandroid.blue'
minSdkVersion 14
versionCode 4
}
}
}
六. 构建变体(BuildVariants)–BuildTypes和ProductFlavors的所有可能组合
如对于1中的BuildTypes和4中的ProductFlavors有 4*2 = 8 种构建变体, 分别是:
redDebug, redRelease, redStaging, redStaging2
blueDebug, blueRelease, blueStaging, blueStaging2
当ProductFlavors这样定义时,则有 4*2*2 = 16 种构建变体
android {
flavorDimensions "color", "price"
productFlavors {
red {
flavorDimension "color"
}
blue {
flavorDimension "color"
}
free {
flavorDimension "price"
}
paid {
flavorDimension "price"
}
}
}
分别是:
redFreeDebug, redFreeRelease, redFreeStaging, redFreeStaging2
redPaidDebug, redPaidRelease, redPaidStaging, redPaidStaging2
blueFreeDebug, blueFreeRelease, blueFreeStaging, blueFreeStaging2
bluePaidDebug, bluePaidRelease, bluePaidStaging, bluePaidStaging2
如果没有定义ProductFlavors, 那么构建变体只是简单的包含构建版本, 就算没有定义任何构建版, Studio也会默认为你创建debug版本。
七. tasks–当创建新的BuildTypes或ProductFlavors时, 相应的新task也会被创建, 如对于1和4
assemble: 构建所有变体
assembleRed: 使用red风味配置构建redDebug, redRelease, redStaging, redStaging2变体
assembleRedDebug: 使用red风味配置构建redDebug变体
八. 变体过滤器–当使用assemble构建项目时, 可加速构建过程, 即不会执行那些你不需要的变体。
在 该module/build.gradle中定义:
android.variantFilter { variant ->
if(variant.buildType.name.equals('release')) {
variant.getFlavors().each() { flavor ->
if (flavor.name.equals('blue')) {
variant.setIgnore(true);
}
}
}
}
九. 签名配置–即用不同的key去签名不同的变体
定义签名配置, 在 module/build.gradle中:
android {
signingConfigs {
staging.initWith(signingConfigs.debug)
release {
storeFile file("keystore名.jks")
storePassword "keystore密码"
keyAlias "keystore别名"
keyPassword "keystore密码"
}
}
}
在该例,创建了2个签名配置。debug配置是Studio默认的,其使用了公共的keystore和password,所以没必要为debug版本创建签名配置了。
staging配置 使用了initWith()方法,其会继承定义(此处是debug)的签名配置。这意味着staging版本和debug版本的key和password是一样的。
release配置 定义了storeFile, key alias和密码。当然这不是一个好的选择,建议最好在 项目根目录/gradle.properties文件中配置。
使用签名配置, 在 module/build.gradle中:
android {
buildTypes {
release {
signingConfig signingConfigs.release //可在定义 构建版本 时, 利用signingConfig属性使用定义好的签名配置
}
}
productFlavors {
blue { //也可在定义 风味 时, 利用signingConfig属性使用定义好的签名配置,
signingConfig signingConfigs.staging //但当 风味 和 构建版本 组合成 构建变体 时, 这里使用的签名配置可能会被重写
} //故: 最好用下面这种使用签名配置的方式
}
buildTypes { //当创建了不同的风味时, 最好用这种方式使用签名配置
release {
productFlavors.red.signingConfig signingConfigs.red
productFlavors.blue.signingConfig signingConfigs.blue
}
}
}
十. 多模块构建
例如: 有 app 和 wear 两个模块.
gradlew assembleDebug 则两个模块的相关task都会被执行
gradlew :wear:assembleDebug 则只有wear模块的相关task会执行
注: 所有的"依赖模块"总是在"应用模块"构建之前构建
默认情况下Gradle构建是单线程的,即Gradle会在单个线程中依次构建所有的模块。若要开启多线程构建, 则在 根目录/grade.properties文件中打开:
org.gradle.parallel=true (默认被注释)
Gradle会创建尽可能多的线程去构建你的项目,每个线程都会构建一个模块。parallel执行的是独立的模块,即你的模块是独立的
十一. 版本管理–app/build.gradle 文件中, 根据不同的构建变体, 自动修改生成的apk名称
A.在最上面添加: import java.text.SimpleDateFormat
B.在android方法中添加:
android {
defaultConfig {
applicationId "com.exp.test"
minSdkVersion 14
targetSdkVersion 22
versionCode 15 //当打包新的正式版本时, +1, 如16
versionName "1.4.9" //当打包新的正式版本时, +1, 如1.4.10
}
/*配置生成apk的名称*/
applicationVariants.all { variant ->
variant.outputs.each { output ->
def file = output.outputFile
if (variant.buildType.name.equals('release')) { //如果是release版本,则apk名称为: exp-1.4.9-20170512172630.apk
def releaseApkName = "exp-" + defaultConfig.versionName + "-" + buildTime() + ".apk"
output.outputFile = new File(file.parent, releaseApkName)
}
if (variant.buildType.name.equals('debug')) { //如果是debug版本,则apk名称为: exp-1.4.9-15-20170512172630-debug.apk
def debugApkName = "exp-" + defaultConfig.versionName + "-" + defaultConfig.versionCode + "-" + buildTime() + "-debug.apk"
output.outputFile = new File(file.parent, debugApkName)
}
}
}
}
C.在android方法外添加:
def buildTime() {
def sdf = new SimpleDateFormat("yyyyMMddHHmmss")
sdf.setTimeZone(TimeZone.getDefault())
return sdf.format(new Date())
}
十二. 单元测试–注:JUnit只能测试逻辑代码,因Android类是不能直接跑在JVM上的,只能在手机的虚拟机上(ART).故若单元测试中涉及到Android类,则会报错
A.测试代码放在 app/src/test目录中
B.在 app/build.gradle中依赖测试库:
dependencies {
testCompile 'junit:junit:4.12' //testCompile表示该jar包只会在你测试的时候导入apk
//testRedReleaseCompile 'junit:junit:4.12' //表示该jar包只会在redRelease构建变体测试的时候导入
}
C.在测试类或某个测试方法上单击右键, 选择 Run'类名或方法名'
D.用命令行测试:
gradlew test //运行所有构建变体的单元测试
gradlew testDebug //运行debug构建版本所有风味的单元测试
gradlew testRedDebug //运行redDebug构建变体的单元测试
gradlew test --continue //若某个测试单元报错后,测试并不会中断
当测试完成后,Gradle会生成一份测试报告,存放位置: app/build/reports/tests/debug/index.html
十三. Robolectric测试–如果测试代码中用到Android类, 则单元测试不行, 但可以用第三方开源库, 其中最出名的是Robolectric, 它不需要设备或模拟器就可运行。
A.在 app/build.gradle中添加依赖:
apply plugin: 'org.robolectric'
dependencies {
testCompile 'junit:junit:4.12' //如果Studio没有默认添加该, 则需要添加junit包
testCompile'org.robolectric:robolectric:3.0'
testCompile'org.robolectric:shadows-support:3.0'
}
B.测试代码也是放在 app/src/test目录中
例如: 点击某个TextView后改变其文字内容
@RunWith(RobolectricTestRunner.class)
@Config(manifest = "app/src/main/AndroidManifest.xml", sdk = 18)
public class MainActivityTest {
@Test
public void clickingButtonShouldChangeText() {
AppCompatActivity activity = Robolectric.buildActivity
(MainActivity.class).create().get();
Button button = (Button)
activity.findViewById(R.id.button);
TextView textView = (TextView)
activity.findViewById(R.id.label);
button.performClick();
assertThat(textView.getText().toString(), equalTo
(activity.getString(R.string.hello_robolectric)));
}
}
十三. 功能测试–用来测试一个app的多个模块是否能够正常工作, 如点击某一按钮后是否会跳转到指定的activity. 推荐google的Espresso框架
A.在 app/build.gradle中添加:
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
B.在 app/build.gradle中添加依赖:
dependencies {
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test:rules:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2'
}
注:这些依赖包使用了androidTestCompile,其不同于testCompile。当你直接运行时,会报错:
Error: duplicate files during packaging of APK app-androidTest.apk Path in archive: LICENSE.txt
Origin 1: ...\hamcrest-library-1.1.jar
Origin 2: ...\junit-dep-4.10.jar
因为文件重复导致,可在 app/build.gradle中简单处理如下:
android {
packagingOptions {
exclude 'LICENSE.txt'
}
}
C.功能测试代码需放在app/src/androidTest目录中:
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestingEspressoMainActivityTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void testHelloWorldIsShown() {
onView(withText("Hello world!")).check(matches(isDisplayed()));
}
}
十四. 测试覆盖率–即哪些类,方法,甚至代码行被测试, 推荐用Jacoco
A.在 app/build.gradle文件中配置:
buildTypes {
debug {
testCoverageEnabled = true
}
}
B.当构建完成后, 可在app/build/outputs/reports/coverage/debug/index.html中找到每个构建变体的测试报告.
14.Groovy入门–Groovy起源于Java, 也是运行在JVM上.
Groovy Java
打印: println 'Hello, world! System.out.println("Hello, world!");
变量:def name = 'Andy'
def greeting = "Hello, $name!"
def name_size = "Your name is ${name.size()} characters long."
表示greeting = "Hello, Andy!"
name_size = "Your name is 4 characters long."
定义类: class MyGroovyClass {
String greeting
String getGreeting() {
return 'Hello!'
}
}
注:无论是类名还是成员变量都没有修饰符。类和方法默认修饰为public,成员变量默认修饰为private
使用类: def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()
get/set方法groovy会默认为你添加, 故可以覆写它们
定义方法: def myMethod(def num) {
return num * num //return关键字可以省略, 但不建议这样做
}
简写:
def myMethod = { num -> //这个箭头很关键。箭头前面是参数定义,箭头后面是代码
return num * num
}
使用方法: myMethod 4
闭包: Groovy用成对的大括号表示闭包, 当没有显式地为闭包添加参数时, Groovy会默认添加一个参数it, 且初值为null
集合: 在groovy中, 有二个重要的容器分别是List和Map
定义List: List list = [1, 'haha', 3, true, 5] //由[]定义,其元素可以是任何对象
遍历List: list.each() { element ->
println element
}
或
list.each() {
println it
}
定义Map: Map map = [key1:10, key2:true] //key必须是字符串,value可以是任何对象.key可以用''或""包起来,也可以不用.
使用Map: map.get('key1')
map['key2']
map.key1