2.基本自定义构建
2.1 理解gradle文件
三个.gradle文件:项目下的build.gradle和settings.gradle, app下的build.gradle
2.1.1 settings文件
settings文件在初始化阶段被执行,并且定义了哪些模块应该包含在构建内,单模块项目不一定需要settings文件,但多模块项目必须要有该文件,在这背后,Gradle为settings文件创建一个Settings对象,并调用该对象的相关方法。davdroid的settings文件如下
include ':app',
':libraries:cert4android',
':libraries:dav4android',
':libraries:ical4android',
':libraries:vcard4android'
2.1.2 顶层构建文件
buildscript {
repositories {
maven {//自定义仓库地址
url project.MVN_REPOSITORY_URL//在gradle.properties中配置
}
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}
allprojects {//声明需要被用于所有模块的属性
repositories {
maven {
url project.MVN_REPOSITORY_URL
}
jcenter()
}
}
2.1.3 模块的构建文件
模块的构建文件应用与模块,它可以覆盖顶层构建文件的属性
davdroid的模块构建文件
apply plugin: 'com.android.application' //android应用插件
android {
compileSdkVersion 24//编译应用的android版本
buildToolsVersion "25.0.0"//构建工具和编译器使用的版本号,构建工具包括apt zipalign dx等
defaultConfig {//配置应用的核心属性,该代码块中的内容可以覆盖manifest文件中对应的条目
applicationId "at.bitfire.davdroid" //覆盖了manifest中的pacakgename
minSdkVersion 9//最小api级别
targetSdkVersion 24 //用于通知系统,该应用已经在某特定android版本通过测试,从而操作系统不必启用任何向前兼容的行为
versionCode 114
buildConfigField "long", "buildTime", System.currentTimeMillis() + "L"
buildConfigField "boolean", "customCerts", "true"
}
buildTypes {//定义如何构建和打包不同构建类型的应用
debug {
minifyEnabled false
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
lintOptions {
disable 'GoogleAppIndexingWarning' // we don't need Google indexing, thanks
disable 'GradleDependency'
disable 'GradleDynamicVersion'
disable 'IconColors'
disable 'IconLauncherShape'
disable 'MissingTranslation'
disable 'MissingQuantity'
disable 'Recycle' // doesn't understand Lombok's @Cleanup
}
packagingOptions {
exclude 'LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
}
dependencies {//依赖
//depend libs
compile fileTree(include: ['*.jar'], dir: 'libs')
//depend modules
compile project(':cert4android')
compile project(':dav4android')
compile project(':ical4android')
compile project(':vcard4android')
compile 'com.android.support:appcompat-v7:24.+'
compile 'com.android.support:cardview-v7:24.+'
provided 'org.projectlombok:lombok:1.16.10'
testCompile 'junit:junit:4.12'
testCompile 'com.squareup.okhttp3:mockwebserver:3.4.1'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.4.1'
}
applicationId和packagename的对比
在gradle之前,pacakgename有两个用途:1.应用的唯一标识,2.在R文件中被用作包名。
使用variants,gradle可以创建不同版本的应用,如可以构建一个免费版和付费版的应用,这两个版本需要有独立的标示符,这样在appstore中才能以不同的应用出现,并且可以被同时安装。然而资源代码和R类必须在任何时候都是用相同的包名,否则你的所有资源文件都要随着正在构建的版本去改变,也就是解耦了packagename两种不同用法,packagename继续在资源代码和R类中使用,而之前被用作设备和appstore唯一标识的packagename现在则被称之为applicationId。
2.2 任务入门
- assemble:为每个构建版本创建一个apk
- clean:删除所有的构建内容,例如apk文件
- check:运行lint检查,如lint发现问题则终止构建
- build:同时运行assemble和check
- installdebug和installRelease在连接的设备上安装指定版本
assemble任务默认依赖于assembledebug和assemblerelease,如果添加了更多的构建类型,那么就会有更多的任务,也就是说运行assemble会触发每一个拥有的构建类型并执行相应的构建操作
assemble依赖于check。
除了可以在命令行界面运行任务,as有一个包含了所有可用任务的工具窗,该工具窗被成为gradle###
2.3 自定义构建
2.3.1 操控manifest文件条目
在as内部通过file菜单打开project structure对话框可以修改基本的设置
2.3.2 BuildConfig和资源
BuildConfig类包含一个按照构建类型设置值的DEBUG常量,如果有一部分代码只想在debuging时期运行,如果logging,那DEBUG就非常有用,可以通过Gradle来扩展该文件,这样在debug和release时就可以拥有不同的常量
buildTypes {
debug {
buildConfigField "String", "API_URL", "\"http:test.example.com/api\""
buildConfigField "boolean", "LOG_HTTP_CALLS", "true"
}
release {
buildConfigField "String", "API_URL", "\"http:example.com/api\""
buildConfigField "boolean", "LOG_HTTP_CALLS", "false"
}
}
添加buildConfigField之后,就可以在java代码中使用BuildConfig.API_URL和BuildConfig.LOG_HTTP_CALLS######
也可以通过类似的方式来配置资源值
buildTypes {
debug {
resValue "string", "app_name", "EX Debug"
}
release {
resValue "string", "app_name", "EX"
}
}
2.3.3 项目属性
三种常见的属性定义方式
- ext代码块
- gradle.properties文件
- -P命令行参数
在build.gradle中
ext{
local = 'hello from build.gradle'
}
gradle.properties文件中
propertiesFile = Hello from propertiesfile
定义任务
task myprint << {
println local
println propertiesFile
if (project.hasProperty('cmd')) {
println cmd
}
}
通过命令行参数运行任务
gradlew myprint -Pcmd='hello from cmd'
3.依赖管理
3.1 依赖仓库
gradle支持三种不同的依赖仓库:maven,ivy,和静态文件,在构建的执行阶段,依赖从依赖仓库被获取出来,gradle有本地缓存,所以一个特定版本的依赖只会在你的机器上被下载一次
一个依赖通常由三个要素组成:group name和version
3.1.1 预定义依赖仓库
gradle有三个预定义maven仓库,JCenter和Maven Central以及本地Maven仓库
JCenter是Maven Central的超集,JCenter支持https
3.1.2 远程仓库
1.maven远程仓库
repositories {
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
2.ivy远程仓库
repositories {
ivy {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
}
3.自定义仓库以及认证
repositories {
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
credentials {
username 'user'
password 'password'
}
}
}
3.2 本地依赖
3.2.1 文件依赖
dependencies {
//depend libs
compile fileTree(include: ['*.jar'], dir: 'libs')
}
3.2.2 原生库依赖
第一种方式
app下创建jniLibs目录
第二种方式
如果上一种方式不生效,可以在build.gralde中设置相关位置
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
3.2.3 依赖项目
应用项目将生成一个apk,依赖项目则生成一个.aar文件,该文件可被android应用项目用作依赖库
1.创建和使用依赖项目
apply plugin: 'com.android.library'
如果实在项目中当做依赖项目,那么需要在setttings文件中添加该模块并在dependencies模块下添加
如davdroid的cert模块
cert本身的构建文件如下
pply plugin: 'com.android.library'
android {
compileSdkVersion 24
buildToolsVersion '25.0.0'
defaultConfig {
minSdkVersion 9
targetSdkVersion 24
}
}
dependencies {
compile 'com.android.support:appcompat-v7:24.+'
compile 'com.android.support:cardview-v7:24.+'
}
settings文件如下
include ':app'
include ':cert4android'
app的构建文件如下
dependencies {
compile project(':cert4android')
}
2.使用.aar文件
repositories {
flatDir {
dirs 'aars'
}
}
dependencies {
compile (name:'libraryname', ext:'aar')
}
4.创建构建variant
4.1 构建类型 buildTypes
4.1.1 自定义构建类型
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
staging{ //自定义构建类型
applicationIdSuffix ".staging" //applicationId后缀
versionNameSuffix "-staging" //versionName后缀
}
//创建一个新的构建类型,并且复制了一个已经存在的构建类型的属性到新的构建类型中
newtype.initWith(buildTypes.release);
newtype{
debuggable = false;
}
}
debug构建类型虽然没有显示,但却是存在的,在variant视图中,以及gradle视图中都存在
4.1.2 每个构建类型都可以有自己的依赖
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.0.1'
stagingCompile 'com.android.support:cardview-v7:24.+'//这个依赖只存在staging类型下
}
4.2 product flavors
buildtype用来为app或library配置不同的构建类型
product flavors用来创建不同的版本,典型的例子就是付费版和免费版
例如,一个公司创建一个可以在同类别所有客户重复使用的应用,唯一改变的是颜色图标和后台url,productflavors简化了基于相同代码构建多个版本的进程
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors{
red{
applicationId 'com.xihe.red'
versionCode 3
}
blue{
applicationId 'com.xihe.blue'
versionCode 4
}
}
这个gradle文件将会产生四个不同的版本 分别是blueDebug,blueRelease,redDebug,redRelease
4.3 构建variant
variant是productflavor和buildtype结合的结果。
variant过滤器概念
4.4 签名配置
signingConfigs{
staging.initWith(signingConfigs.debug);
release{
storeFile file("release.keystore");
storePassword "123456"
keyAlias "xiheapp"
keyPassword "123456"
}
}
android插件使用一个通用的keystore和一个已知密码自动创建了debug配置
staging配置使用了initWith,它会从另一个签名配置中复制所有的属性
release配置通过storeFile指定keystore文件路径,之后定义了密钥别名以及两个密码
在定义签名配置之后,可以将他们应用到buildtypes或者productflavor中,他们都有一个signingConfig属性
结合gradle.properties
signingConfigs {
red {
storeFile file(KEY_STORE_FILE)
storePassword KEY_STORE_PASSWORD
keyAlias KEY_STORE_KEY_ALIAS
keyPassword KEY_STORE_KEY_PASSWORD
}
blue {
storeFile file(BLUE_KEY_STORE_FILE)
storePassword BLUE_KEY_STORE_PASSWORD
keyAlias BLUE_KEY_STORE_KEY_ALIAS
keyPassword BLUE_KEY_STORE_KEY_PASSWORD
}
}
productFlavors {
xihe {
applicationId "com.xihe.app"
signingConfig signingConfigs.blue
}
}
5.多模块构建
5.1 结构
根目录提供settings文件,每个模块都提供自己的build.gradle
其中library可以和app同级别,也可以放到libraries目录下,重要的是在settings中是从根目录为基础
include ':app',
':libraries:slidingmenu',
':libraries:zxing',
':libraries:ical4android',
dependencies {
//depend libs
compile fileTree(include: ['*.jar'], dir: 'libs')
//depend modules
compile project(':libraries:slidingmenu')
compile project(':libraries:zxing')
compile project(':libraries:ical4android')
5.2 构建声明周期
初始化阶段,gradle查找settings文件,如果没有找到,则认为是一个单模块应用
如果有多个模块,那么就再settings定义子目录,子目录中有自己的build.gradle,则gradle会处理,并把他们合并到构建进程中。
5.3 加速构建
通过并行运行所有模块来使得构建过程更快,此功能已经存在,但默认不开启,
如果要开启,在gradle.properties中设置parallel属性
org.gradle.parallel=true
gradle会基于可用的cpu内核来选择合适的线程数量。
7. 任务和插件
7.1 groovy基础
a.打印语句
println 'Hello,world'
b.单引号和双引号的区别
对于字符串都单双引号可以使用,不同的是,双引号可以插入表达式
def name = 'xihe'
def greeting = "hello , $name"
插入表达式还可以动态执行代码
def method = 'toString'
new Date()."$method"()
c.类和成员变量
class GroovyClass {
String greeting;
}
无论是类还是成员变量都没有明确的访问修饰符,groovy中默认的访问级别:类和方法是公有的,成员是私有的。
def instance = new GroovyClass();
instance.setGreeting("hello xihe");
instance.getGreeting();
d.方法
def square(def num){
num * num//即使没有return语句,也会默认返回的
}
square 4
无论是返回类型还是参数类型都没有明确的定义,这里使用的def关键字;在方法调用时,不需要括号和分号
def square = { num->
num * num
}
e. closure
closure是匿名代码块,可以接受参数和返回值,他们可以被视为变量被当做参数传递给方法
Closure square = {
it * it//如果没有明确为其添加参数,closure会自动添加一个,这个参数通常被称为it,没有参数的话则it为空
}
square 4
f.集合
List list = [1,2,3,4,5];//创建list
//遍历list
list.each(){ element->
println element
}
//用it来访问list
list.each(){
println it
}
Map prices = [abc:10,xyz:12]//
//两种get的方式
prices.get('abc');
prices['xyz']
7.2 任务
7.2.1 定义任务
task hello {//配置阶段
println 'hello,xihe'
}
task hi {//配置阶段
println 'hi,jingli'
}
task meme << { // << 执行阶段
println 'memeda'
}
执行:gradlew hello
hello,xihe
hi,jingli
:app:hello UP-TO-DATE
结果:hello和hi在任务配置阶段就执行了
执行:gradlew meme
hello,xihe
hi,jingli
:app:meme
memeda
结果:hello和hi在配置阶段执行,meme在执行阶段执行
7.2.2 任务剖析
Task接口是所有任务的基础,它定义了一系列的属性和方法。
每个任务都包含一个action对象的集合,当一个任务被执行时,所有这些action会连续执行,可以使用doFirst和doLast来为一个任务添加动作。如果需要在执行阶段执行代码,就需要用doFirst和doLast,<<就是doFirst方法的简写形式。
task mytask {
println 'Configuration'
doLast {
println 'doLast'
}
doFirst {
println 'doFirst'
}
}
doFirst和doLast
task mytask {
println 'Configuration'//配置阶段
doLast {
println 'a'
}
doLast {
println 'b'
}
doFirst {
println 'c'
}
doFirst {
println 'd'
}
}
执行:gradlew mytask
Configuration
:app:mytask
d
c
a
b
doFirst:总是添加一个动作到最前面,doLast总是添加一个动作到最后面
mustRunAfter:
task one << {
println 'one'
}
task two << {
println 'two'
}
two.mustRunAfter one
执行:gradlew two
:app:two
two
执行:gradlew two one
:app:one
one
:app:two
two
执行一个的时候,并不会有什么特别的地方
两个同时执行的时候,无论参数是哪个在前面,始终先执行one
dependsOn
task one << {
println 'one'
}
task two << {
println 'two'
}
two.dependsOn one
这个时候执行two一定会先执行 one再执行two
7.2.3 实例:使用任务来简化release过程
场景:如果项目需要开源,那么password不能在以明文的形式存放在构建文件中
解决方法
根目录下创建private.properties文件,并配置键值对
me.password = 123456
在gradle中设置如下任务
signingConfigs{//在gradle文件中不再使用明文来保存password
release{
storeFile file("grademo.jks");
keyAlias "xiheapp"
}
}
//获取properties中的密码
task getPassword << {
def password = ''
if (rootProject.file('private.properties').exists()) {
println 'file exists'
Properties properties = new Properties();
properties.load(rootProject.file('private.properties').newDataInputStream());
password = properties.getProperty('me.password');
println password;
if(!password?.trim()){
password = new String(
System.console().readPassword("\n what is the password")
);
}
//拿到结果并使用
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password
println 'end'
}
}
//使用dependsOn来运行getPassword任务,使用whenTaskAdded来检测
tasks.whenTaskAdded { theTask->
if(theTask.name.equals("packageRelease")){//packageRelease为打包release版本任务
theTask.dependsOn "getPassword"
}
}
执行assembledebug最后的几个任务
:app:packageDebug
:app:zipalignDebug
:app:assembleDebug
release类似,所以可以通过任务名来判断
7.3 hook android插件
hook到android插件的方式之一是:
android.applicationVariants.all {variant ->
//do sth有了一个构建对象variant的引用,就可以访问和操作它的属性
}
如果要操作依赖库,则是使用libraryVariants
7.3.1 自动重命名apk
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")
)
}
}
7.3.2
android.applicationVariants.all { variant->
if(variant.install){//如果是install
tasks.create(name:"run${variant.name.capitalize()}",
dependsOn:variant.install
){
description "Installs the ${variant.description} and runs the activity"
doFirst {
println '---ok---'
exec{
executable = 'adb'
args = ['shell','am','start','-n'
,"${variant.applicationId}/.MainActivity"]
}
}
}
}
}
7.4 自定义插件
apply plugin: RunPlugin
class RunPlugin implements Plugin {
@Override
void apply(Project project) {
project.android.applicationVariants.all { variant->
if(variant.install){
project.tasks.create(name:"run${variant.name.capitalize()}",
dependsOn: variant.install
){
doFirst{
//TODO
}
doLast{
// TODO
}
}
}
}
}
}
9.自定义构建
9.1 减少apk文件大小
9.1.1 proguard
proguard是一个java工具,不仅可以缩减apk大小,还可以在编译期优化混淆和预检验代码,它通过所有的代码路径来找到未被使用的代码,并将其删除
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
激活proguarg:通过设置minifyEnabled为true来激活,当proguard被激活,在构建过程中proguardRelease task会被执行,并调用proguard,
激活proguard之后需要重新测试整个应用,因为proguard可能会移除一些扔然需要的代码,为了解决这个问题,可以自定义proguard规则,排除那些被删除和混淆的类,可以使用proguardFiles 来定义包含proguard规则的文件,比如为了保留一个类,可以使用 -keep pulbic class
getDefaultProguardFile('proguard-android.txt')方法从androidsdk的tools/proguard文件夹下的proguard-android.txt文件中获取默认的proguard设置,as中proguard-rules.pro被默认添加到了android模块,可以在这里简单的添加规则
9.1.2 缩减资源
1.自动缩减
在构建中设置shrinkResources属性,如果设置该属性为true,那么android构建工具会自动判断哪些资源没有被使用,并将它们排除在apk之外。
使用这个功能有一个要求:必须开启proguard
设置shrinkResources之后,gradle视图中会多一个shrinkResources任务,可以通过执行该任务查看缩减了多少资源
自动缩减有一个问题,它可能移除过多的资源,尤其是那些被动态使用的资源可能会被意外移除,我们可以在res/raw下一个叫keep.xml中定义这些例外
2.手动缩减
还可以通过去除某些语言文件或者默写密度的图片来缩减资源,一些依赖库的语言很多,如果我们的应用只需要几种语言,那么就可以通过resCongifs这种方式来做
//只保留英语和丹麦语
android {
defaultConfig {
resConfigs "en","da"
}
}
//只保留这两种密度的图片
android {
defaultConfig {
resConfigs "hdpi","xhdpi"
}
}
9.2 加速构建
和ant相比,gradle的构建时间很长,这是因为执行每一个任务时,gradle都要经历生命周期的三个阶段。
1.并行构建
2.启动gradle daemon
他会在第一次运行构建的时候启动一个守护进程,后续的构建都会复用这个进程,从而减少启动成本。只要使用gradle,这个进程就会一直存活,并且在空闲三小时时候终止,
org.gradle.daemon=true
as中,daemon是默认开启的,也就是说如果用ide构建,第一次构建后,下一次构建会快些,但是如果是通过命令行来构建则daemon需要通过配置开启
3.java虚拟机参数
在gradle.properties中配置合适的jvm参数
org.gradle.jvmargs=-Xms256m -Xmx1024m
4. org.gradle.configureondemand
如果是多模块应用,则该参数很有用,它会忽略正在执行的task不需要的模块来限制时间消耗
这些配置在setting的complier中都可以进行可视化的操作
profile
如果想找出构建中速度变慢的具体位置,可以在执行构建任务的时候添加 --profile标志来实现,这时gradle会出一份报告,并告诉你哪一部分最耗时,报告会生成在 /build/reports/profile下。
9.3 忽略lint
通过gradle执行release构建时会执行一个lint检查,lint是一个静态代码分析工具,会标记布局和代码中潜在的bug,某些情况下甚至会阻塞构建过程,可以通过禁用abortOnError来忽略lint错误
//禁用abortOnError
android {
lintOptions {
abortOnError false
}
}
9.4 在gradle中使用ant
任务名前加ant就可以了
task archive << {
ant.echo 'ant is archiving'
ant.zip(destfile:'xxx.zip'){
fileset(dir:'zipme')
}
}
task gradleArchive(type:Zip) << {
from 'zipme/'
archiveName 'xxx.zip'
}
导入整个ant脚本
ant.importBuild 'build.xml'
9.5 应用部署
错误总结
1. Could not find property ' release' on SigningConfig container.
signingConfigs应该放到buildType的前面,否则的话就会报这个错误