使用Gradle构建Android应用

使用Gradle构建Android应用

最近重读 Gradle for Android 一书,发现以前囫囵吞枣式的阅读只了解到点皮毛。重读一次,收获颇丰,想着结合实践,做做笔记。本文从统一配置依赖管理,高级配置,多模块构建,创建构建变体,运行测试,构建过程,创建任务和插件,Jenkins持续集成设置,这几个方面总结gradle是如何配置,构建,测试 Android项目的。

一、统一配置依赖管理

使用Android Studio开发Android 应用程序,首先接触到的是project和module这两个概念。简单理解,project是一个Android项目,一个项目里面有一个或多个module。划分模块的好处:
1. 便于分工合作;
2. 解耦,易于维护管理;
3. 复用,每个module都是相对独立的,可以在各个模块甚至别的项目中共用。

一般情况下,一个Android项目会有多个module,各个module可能会依赖相同的支持库,或者第三方库,如support library,gson等。而这些依赖库有不同的版本,如果引用的依赖库版本不一样就会有冲突。此外,每个module都可以设置minSdkVersion、targetSdkVersion、compileSdkVersion、buildToolsVersion,一般来说,同一个项目各个module的这几个设置需要一致。为了解决冲突,统一配置,方便依赖管理,有必要统一配置依赖管理。

1.1 创建config.gradle

可以在根目录的build.gradle中统一配置,也可以创建一个config.gradle文件,然后再根目录的build.gradle中引入。个人感觉创建一个config.gradle文件来管理会更加方便,所以本文采用这种方式。config.gradle和根目录的build.gradle在同一个目录。在根目录的文件头引入:

apply from: "config.gradle"

1.2 config.gradle

  1. ext.android 包含四个基本属性:compileSdkVersion,buildToolsVersion,minSdkVersion,targetSdkVersion。
  2. ext.supportDependencies 是需要引入的support library
  3. ext.thirdParty 是第三方库依赖

(PS:supportDependencies 和 thirdParty 明显是一回事嘛,这里只是为了突出Google提供的support library,完全可以把thirdParty 的内容定义在supportDependencies 内)

/**
 * 在主项目的根目录下创建config.gradle文件
 * 注意需要在根目录的build.gradle中进行引入:apply from: "config.gradle"
 */
ext {
    android = [
            compileSdkVersion: 26,
            buildToolsVersion: "26.0.0",
            minSdkVersion    : 21,
            targetSdkVersion : 26
    ]

    supportLibrary = "26.+"
    supportDependencies = [
            supportAppcompatV7: "com.android.support:appcompat-v7:${supportLibrary}",
            supportV4       : "com.android.support:support-v4:${supportLibrary}",
            design          : "com.android.support:design:${supportLibrary}",
            constraintLayout: 'com.android.support.constraint:constraint-layout:1.0.0-alpha8'
    ]

    thirdParty = [
            fastJson         : 'com.alibaba:fastjson:1.1.63.android',
            fileDownloader   : 'com.liulishuo.filedownloader:library:1.6.5',
            mpAndroidChart   : 'com.github.PhilJay:MPAndroidChart:v3.0.2',
            retrofit         : 'com.squareup.retrofit2:retrofit:2.3.0',
            gsonConverter    : 'com.squareup.retrofit2:converter-gson:2.0.0-beta4',
            scalarConverter  : 'com.squareup.retrofit2:converter-scalars:2.0.0-beta4',
            rxJavaAdapter    : 'com.squareup.retrofit2:adapter-rxjava2:2.2.0',
            rxJava           : 'io.reactivex.rxjava2:rxjava:2.1.3',
            rxAndroid        : 'io.reactivex.rxjava2:rxandroid:2.0.1',
            eventBus         : 'org.greenrobot:eventbus:3.0.0',
            greenDao         : 'org.greenrobot:greendao:3.2.2'
    ]
}

1.3 在module中统一配置

有了统一的配置内容config.gradle后,需要在各个module中使用其中的配置。可以通过rootProject.ext.android.xxx,rootProject.ext.supportDependencies.yyy,rootProject.ext.thirdParty.zzz引用相应的属性配置,为了方便,可以先定义,再引用。以app模块为例,其build.gradle配置文件如下:

apply plugin: 'com.android.application'

def config = rootProject.ext.android
def supportLibrary = rootProject.ext.supportDependencies
def thirdParty = rootProject.ext.thirdParty

android {
    compileSdkVersion config.compileSdkVersion
    buildToolsVersion config.buildToolsVersion
    defaultConfig {
        applicationId "com.bottle.mproject"
        minSdkVersion config.minSdkVersion
        targetSdkVersion config.targetSdkVersion
        versionCode 1
        versionName "0.0.1.1"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    lintOptions {
        abortOnError true
    }
}

dependencies {
    compile project(':util')
    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'
    })
    testCompile thirdParty.junit
    compile supportLibrary.supportAppcompatV7
    compile supportLibrary.design
}

二、高级配置

有了统一配置依赖管理的概念后,有必要先总结gradle Android插件的基本配置属性,最基本的有 compileSdkVersion,buildToolsVersion,minSdkVersion,versionCode ,targetSdkVersion,versionName 等。此外还有 signingConfigs,buildConfigField,manifestPlaceholders等。

2.1 签名 signingConfigs

定义签名

android {
    signingConfigs {
        release {
            keyAlias 'alias'
            keyPassword 'password'
            storeFile file('xyz.keystore')
            storePassword 'password'
        }
    }
}

配置签名

buildTypes {
    release {
        signingConfig signingConfigs.release 
    }
}

当为flavor分配一个配置时,实际上它是覆盖了构建类型的签名配置,如果你不想那样,那么在使用flavor时,就应该为每个构建类型的每个flavor分配不同的密钥

/* productFlavors.red和productFlavors.blue是productFlavors中定义的两个flavor;signigConfigs.red和signigConfigs.blue是signingConfigs中定义的两个签名 */
buildTypes {
    release{
        productFlavors.red.signingConfig signigConfigs.red
        productFlavors.blue.signingConfig signigConfigs.blue
    }
}

2.2 BuildConfig buildConfigField

可以在flavor或者构建类型中定义buildConfigField,SDK工具版本升级到17后,构建工具会生成一个com.your.packageid.BuildConfig的类,如果需要为每个构建变体配置不同的常量,如切换功能,或者设置服务器URL,则可以在flavor或者构建类型中增加buildConfigField。如下,定义了一个boolean值的常量,控制是否需要打印文件日志,release设置为false,debug设置为true。在代码中通过com.your.packageid.BuildConfig.OUTPUT_LOG引用即可。

buildTypes {
    release{
        buildConfigField "boolean", "OUTPUT_LOG", "false"
    }
    debug {
        buildConfigField "boolean", "OUTPUT_LOG", "true"
    }
}

2.3 manifest.xml 条目

通过构建文件可以配置applicationId、minSdkVersion、targetSdkVersion、versionCode、versionName,而不是直接在manifest.xml中填写。此外,还可以通过manifestPlaceholders,为不同的构建变体设置不同的manifest.xml的条目的值,如下举个栗子,定义了豌豆荚和应用宝两个flavor,并为它们分配不同的CHANNEL_ID:

productFlavors {
    wandoujia {
        manifestPlaceholders = [
                CHANNEL_ID: "wandoujia"
        ]
    }
    yingyongbao {
        manifestPlaceholders = [
                CHANNEL_ID: "yingyongbao"
        ]
    }
}

在manifest.xml中应用:

<meta-data
    android:name="CHANNEL_ID"
    android:value="${CHANNEL_ID}" />

2.4 ProGuard

配置混淆:

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

2.5 忽略 lint

Lint是一个静态代码分析工具,会在布局和代码中标记潜在的bug,缺失翻译等,在某些情况下甚至会阻塞构建过程,如果项目从未使用lint,而现在迁移至gradle,那么lint会出现很多错误导致项目构建失败,一个临时的解决方案是忽略lint:

android {
    lintOptions {
        abortOnError false
    }
}

这只是一个临时的方案,最好的做法,当然是将lint报告的错误一一解决。

2.6 更多配置属性

  1. multiDexEnabled Boolean 多个dex包
  2. minifyEnabled Boolen 清除无用的资源
  3. debuggable Boolean 是否允许debug
  4. applicationIdSuffix String buildTypes 中为不同构建类型的applicationId追加后缀
  5. versionNameSuffix String buildTypes 中为不同构建类型的版本名称追加后缀
  6. …未完,待续

三、创建构建变体实现多渠道打包

开发一个应用的时候,通常会有几个不同的版本。如手头上现有的项目,有一个测试用的测试版本,一个工厂员工用于调试硬件的工厂版本,还有一个是生产版本,正式发布给用户使用的。各个版本在配置甚至功能上都有所不同,如测试版本使用的是测试服的服务器地址,而生产版本和工厂版本则是外网地址,工厂版本的部分功能是被阉割掉的。此外有些APP还有可能有免费版本和付费版本等等。通过构建类型buildTypes和product flavor就可以构建不同需求的不同版本。

3.1 构建类型

新建一个module,build.gradle中默认配置了一个构建类型,叫做release,还有一个隐藏的debug

android {
    buildTypes {
        debug {
            signingConfig signingConfigs.release_jimu1
        }
        release {
            signingConfig signingConfigs.release_jimu1
        }
    }
}

3.2 product flavor

与被用来配置相同APP或library的不同构建类型相反,product flavor被用来创建不同的版本。典型的例子是一个应用有免费版和付费版。另一个常见的情况是,一个机构为多个客户端构建相同功能而品牌不同的应用。这在出租车企业或者银行应用中十分常见,一个公司创建一个可以在同一类别所有客户端重复使用的应用。唯一改变的是颜色、图标、和后台的URL。product flavor极大地简化了基于相同代码构建多个版本的应用的进程。

创建product flavor

android {
    productFlavors {
        red {
            applicationId "com.bottle.red"
        }
        blue {
            applicationId "com.bottle.blue"
        }
    }
}

3.3 构建variant

构建variant是构建类型buildTypes和product flavor的结合结果。例如,有debug和release两种构建类型,blue和red两个product flavor,那么组合起来就有blueDebug,blueRelease,redDebug和redRelease,四个构建variant。在Android studio中的Build Variants面板中可以查看对应的各个module的构建variants。

3.4 源集

每一个构建类型/product flavor /构建variant 可以有一个源集,源集的默认名称和构建类型/product flavor/构建variant 相同。该目录不会自动创建,而是在需要在构建类型使用自定义的源码和资源之前,手动创建这个目录。
源集让一切皆有可能,例如,可以针对特定的构建类型覆写某些属性,为某些构建类型添加自定义代码,以及为同的构建类型添加自定义布局或者字符串。

  1. 当添加Java类到源集时,是互斥的,即,添加一个Java类到release,也可添加到debug,但是不能同时添加到main。
  2. 当使用不同的源集时,drawable和layout文件将完全覆盖main源集中的同名资源,但是values中的文件则不会覆盖,如string.xml,而是合并,manifest.xml也是,会合并到main中的manifest.xml中。

release和debug与main源集目录结构:
app
——src
————main
————release (release构建类型的源集)
————debug (debug构建类型的源集)
——————java
————————com.bottle

3.5 选择构建类型还是product flavor

如果你不确定是否需要一个新的构建类型或新的product flavor,那么你应该问自己,是否想要创建一个供内部使用的应用,或一个会发布到Google play的新apk。如果你需要一个全新的APP,独立于你已有的应用发布,那么product flavor就是你需要的,否则你应该坚持使用构建类型

四、多模块构建

Android studio管理多模块和eclipse不同,Android studio的一个project相当于eclipse的一个workspace,Android studio的module相当于eclipse workspace里面的project。通常,一个project有一个或多个module,每一个module都在根目录的setting.gradle中声明,都可以提供自己的build.gradle。
如果一个模块,如app模块,需要引入另一个模块library,那么需要在app module的build.gradle中添加依赖:

dependencies {
    compile project(':library')
}

如果需要子文件夹类管理模块,可以通过gradle来配置:

// setting.gradle,所有路径相对于setting.gradle,"/"":"代替
include 'app','libraries:library1','libraries:library2'

// 同样,应用这个library也需要添加路径
compile project (':libraries:library1')

配置多模块项目构建策略的方式有一下几种:
1. 在根目录的build.gradle中配置所有模块的设置。这让概览一个项目的整个构建配置变得容易,但是其也会变得非常凌乱,特别是当你的模块需要不同的插件,而他们的插件又有其自己的DSL是。
2. 每个模块都有一个独立的build.gradle文件。该策略可确保模块不紧密耦合,也使得跟踪构建变化变得容易。
3. 根目录设置一个构建文件,用来定义所有模块的通用属性,然后每个模块的构建文件用于配置只针对该模块的设置。Android studio遵循该策略,在根目录创建一个build.grale,然后每个模块内创建一个build.gradle。

五、运行测试

如何搭建测试环境,编写运行测试用例,参考Testing Apps on Android

单元测试

常规的单元测试,可以用JUnit,涉及到Android特有的类或资源的可以Robolectric,它提供了一种简单的方法来测试Android的功能,而不需要设备或者模拟器。如何编写单元测试?参考 JUnitRobotlectric
运行单元测试:

// 运行所有测试,一个用例失败会导致整个test任务失败
gradle test

// 一个失败并不会影响后面的用例运行
gradle test -continue

// 只针对一个特定的类运行测试
gradle testDebut --tests="*.LogicTest"

// 只想在debug variant上运行所有测试

gradle testDebug

功能测试

功能测试用于测试应用中的几个组件是否可以一起正常工作。例如你可以创建一个功能测试用来确认某个按钮是否打开了一个新的Activity。

如何搭建功能测试环境,编写功能测试用例,参考 Espresso

测试覆盖率

激活测试覆盖率:

buildTypes {
    debug {
        testCoverageEnabled true
    }   
}

运行测试覆盖率任务:

gradle connectedCheck

任务执行后,在your_app/build/outputs/reports/coverage/debug/index.html查看测试覆盖率报告

六、构建过程

初始化

项目实例会在这个阶段被创建。如果项目有多个模块(setting.gradle文件中定义),并且每个模块都有一个其对应的build.gradle,那么就会创建多个项目实例。

配置

在这个阶段,构建脚本会被执行,并为每个项目实例创建和配置任务

执行

在这个阶段,gradle将决定哪个任务会被执行。哪些任务被执行取决于开始该次构建的参数配置和该gradle文件的当前目录

七、创建任务——自动重命名apk

要创建自定义任务,需要学习Groovy,了解Groovy的工作原理,可以使自定义任务和插件变得更容易,以及可以帮助理解gradle是如何工作的。学习Groovy不在本总结的范围(ps:because 我也没怎么看呀),这里只举个自动重命名apk的任务的栗子,抛砖引玉,这个栗子将版本号(versionName)追加到默认的apk名字之后:

android {
    android.applicationVariants.all{ variant ->
        variant.outputs.each { output ->
            def file = output.outputFile
            output.outputFile = new File(file.parent, file.name.replace(".apk", "-${variant.versionName}.apk"))
        }
    }
}

八、 Jenkins持续集成配置

前面写过一篇Jenkins环境配置的总结:Jenkins持续集成环境部署,按照这个总结即可集成打包Android应用,并上传到蒲公英平台。

九、扩展

build.gradle文件使用 Groovy 作为脚本语言编写,为了更好、更高效地使用Gradle构建Android项目,有必要了解 Groovy

参考

Testing Apps on Android
JUnit
Robotlectric
Espresso
Groovy

原创文章 25 获赞 12 访问量 1万+

猜你喜欢

转载自blog.csdn.net/half_bottle/article/details/78463385