gradle总结

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的前面,否则的话就会报这个错误

你可能感兴趣的:(gradle总结)