• Android Gradle权威指南读书笔记


    第一章 Gradle入门

    1. 生成Gradle Wrapper
      命令:gradle wrapper --gradle-version 版本号
    2. 自定义Gradle Wrapper

    1. task wrapper(type : Wrapper) {
    2. gradleVersion = '2.4'
    3. archiveBase = 'GRADLE USER HOME'
    4. archivePath = 'wrapper/dists'
    5. distributionBase = 'GRADLE USER HOME'
    6. distributionPath = 'wrapper/dists'
    7. distributionUrl = 'http\://services.gradle.org/distributions/gradle-2.4-all.zip'
    1. 打印日志
    • print或者logger
    • print 定向为 QUIET 级别日志

    1. logger.quiet('quiet日志信息'
    2. logger.error('error日志信息’)
    3. logger.warn('warn日志信息'
    4. logger.lifecycle(`lifecycle日志信息')
    5. logger.info('info 日志信息.'
    6. logger.debug('debug 日志信息.'
    1. 命令行
    • 查看帮助:./gradlew tasks,./gradlew -h,/gradlew help --task [task]
    • 强制刷新依赖:./gradlew --refresh-dependencies assemble

    第二章 Groovy语法

    Groovy完全兼容 Java ,这就意味着你可以在 build 本文件里写任何的 Java 代码。

    字符串

    1. 单引号和双引号都可以定义字符串常量
    2. 单引号不支持字符串表达运算,但双引号支持

    1. task printStringVar <{
    2. def name 张三
    3. println '单引号的变量计算:${name}'
    4. println "双引号的变量计算:${name}"
    5. }

    ./gradlew printStringVar 运行后输出:

    1. 单引号的变量计算: ${name}
    2. 双引号的变量计算:张三

    双引号可以直接进行表达式计算的这个能力非常好用,我们可以用这种方式进行字符串连接运算,再也不用 Java 中烦琐的“+”号了。记住这个嵌套的规则,一个美元符号紧跟着一对花括号,花括号里放表达式,比如${name}${1+1}等,只有一个变量的时候可以省略花括号,如$name.

    集合

    1. List

    1. task printList {
    2. def numList =[1,2,3,4,5,6];
    3. println numList. getClass().name //ArrayList
    4. println numList[1//访问第二个元素
    5. println numList[-1//访问最后一个元素
    6. println numList[-2//访问倒数第二个元素
    7. println numList[1..3//访问第二个到第四个元素
    8. numList.each { //遍历
    9. println it //it 变量就是正在迭代的元素
    10. }
    11. }
    1. Map

    1. task printlnMap << {
    2. def mapl =[ 'width': 1024 ,'height': 768]
    3. println mapl.getClass() . name //LinkedHashMap
    4. println mapl ['width' ]
    5. println mapl.height
    6. mapl.each {
    7. println "Key:${it . key),Value:${it.value}"
    8. }
    9. }
    1. 方法
      3.1 括号可以省略

    1. task invokeMethod << {
    2. method1(1,2)
    3. method1 1,2
    4. }
    5. def method1(int a,int b) {
    6. println a+b
    7. }

    3.2 return 是可以不写的
    当没有return时,Groovy 会把方法执行过程中的最后一句代码作为其返回值:

    1. task printMethodReturn << {
    2. def add1 = method2 1 , 2
    3. def add2 = method2 5 , 3
    4. println "addl1:${add1},add2:${add2 }"
    5. }
    6. def method2{int a,int b) {
    7. if (a>b){
    8. a
    9. }else{
    10. b
    11. }
    12. }

    3.3 代码块可作为参数传递
    以集合的each方法为例:

    1. //呆板的写法其实是这样
    2. numList.each({println it})
    3. //我们格式化一下,是不是好看一些
    4. numList.each({
    5. println it
    6. })
    7. //好看一些, Groovy 规定,如果方法的最后一个参数是闭包 ,可以放到方法外面
    8. numList.each() {
    9. println it
    10. }
    11. //然后方法可以省略括号,就变成我们经常看到的样式。这种写法其实是个方法调用
    12. numList.each {
    13. println it
    14. }

    JavaBean

    并不是一定要定义成员变量才能作为类的属性访问,直接用getter/setter方法,也一样可以当作属性访问:

    1. task helloJavaBean <{
    2. Person p = new Person()
    3. println "名字是:${p.name}"
    4. p.name = "张三"
    5. println "名字 ${p.name}"
    6. println "年龄是${p.age}"
    7. }
    8. class Person {
    9. private String name
    10. public int getAge() {
    11. 12
    12. }
    13. }

    上面的例子发现,有定会getAge()方法时,一样可以使用点运算把getter方法当成属性运算。但是不能修改age的值,因为我们并没有定义setter

    闭包

    闭包,其实就是一个代码块。

    1. task helloClosure <<{
    2. customEach{//调用闭包
    3. print it//it关键字代表传入的参数
    4. }
    5. }
    6. def customEach(closure){
    7. for(int i in 1..10){
    8. closure(i)//闭包的调用,传入闭包需要接收的参数。如果只有一个参数,那么就是我们的it变量了
    9. }
    10. }
    1. 向闭包传递参数

    1. task helloClosure << {
    2. //多个参数
    3. eachMap {k,v -> //"->"符号用于把闭包的参数和主体区分开来。
    4. println "${k} is ${v}"
    5. }
    6. }
    7. def eachMap(closure) {
    8. def map1 = ["name": "张三", "age":18]
    9. map1.each{
    10. closure(it.key,it.value)
    11. }
    12. }
    1. 闭包委托
      Groovy 闭包的强大之处在于它支持闭包方法的委托 Groovy 的闭包有 thisObject, owner,delegate 三个属性当你在闭包内调用方法时,由它们来确定使用哪个对象来处理。默认情况下delegate, owner是相等的,但是 delegate 是可以被修改的,这个功能是非常强大的,Gradle中的闭包的很多功能都是通过修改 delegate 实现的:

    1. task helloDelegate << {
    2. new Delegate().test {
    3. println "this0bject:${thisObject. getClass()} "
    4. println "owner:${owner.getClass ()}"
    5. println "delegate:${delegate.getClass() }"
    6. method1()
    7. it.method()
    8. }
    9. }
    10. def method1() {
    11. println "Context this:${this.getClass()) in root"
    12. println "method1 in root"
    13. }
    14. class Delegate {
    15. def method1() {
    16. println "Delegate this:${this.getClass()) in Delegate"
    17. println "methodl in Delegate"
    18. }
    19. def test(Closure<Delegate> closure) {
    20. closure(this)
    21. }
    22. }

    输出结果:

    1. thisObject:class build_e27c427w88bo0afju9niqltzf
    2. owner:class build_e27c427w88bo0afju9niqltzf$_run_closure2
    3. delegate : class build_e27c427w88bo0afju9niqltzf$_ run_closure2
    4. Context this : class build_e27c427w88bo0afju9niqltzf in root
    5. method1 in root
    6. Delegate this:class Delegate in Delegate
    7. method1 in Delegate

    通过上面的例子我们发现,thisObject的优先级最高,默认情况下,优先使用 thisObect来处理闭包中调用 的方法,如果有则执行从输出中我们也可以看到,这个thisObject 其实就是这个构建脚本的上下文,它和脚本中this对象是相等的。
    从例子中也证明了 delegateowner 是相等的,它们两个的优先级是: owner 要比 delegate高。所以闭包内方法的处理顺序是 thisObject > owner> delegate
    DSL (Domain Specific Language,领域特定语言)中,比如 Gradle ,我们一般会指定delegate为当前的 it ,这样我们在闭包内就可以对该进行配置,或者调用其方法:

    1. task configClosure << {
    2. person {
    3. personName = "张三"
    4. personAge = 20
    5. dumpPerson ()
    6. }
    7. }
    8. class Person {
    9. String personName
    10. int personAge
    11. def dumpPerson() {
    12. println " name is ${personName} age is ${personAge}"
    13. }
    14. }
    15. def person(Closure<Person> closure) {
    16. Person p =new Person() ;
    17. closure.delegate = p
    18. //委托模式优先
    19. closure.setResolveStrategy(Closure.DELEGATE_FIRST);
    20. closure(p)
    21. }

    例子中我们设置了委托对象为当前创建的 Person 实例,并且设置了委托模式优先,所以,我们在使用 person方法创建一个 Person 的实例时,可以在闭包里直接对该 Person 实例配置有没有发现和我们在 Gradle中使用 task 创建一个 Task 的用 法很像,其实在 Gradle 中有很多类似的用法,在 Gradle 中也基本上都是使用 delegate 的方式使用闭包进行配置等操作。

    第三章 Gradle构建脚本基础

    Settings文件

    一个设置文件,用于初始化以及工程树的配置。

    1. rootProject.name = 'android-gradle-book-code'
    2. include ':example02'
    3. project(':example02').projectDir = new File(rootDir, 'chapter01/example02')

    Projects和tasks

    一个Project 包含很多Task,也就是说每个 Project 是由多个 Task组成的。而Task 就是个操作,一个原子性的操作。task其实是Project的一个函数:

    1. /**
    2. *

      Creates a {@link Task} with the given name and adds it to this project. Calling this method is equivalent to

    3. * calling {@link #task(java.util.Map, String)} with an empty options map.

    4. *
    5. *

      After the task is added to the project, it is made available as a property of the project, so that you can

    6. * reference the task by name in your build file. See here for more details

    7. *
    8. *

      If a task with the given name already exists in this project, an exception is thrown.

    9. *
    10. * @param name The name of the task to be created
    11. * @return The newly created task object
    12. * @throws InvalidUserDataException If a task with the given name already exists in this project.
    13. */
    14. Task task(String name) throws InvalidUserDataException;

    创建task有以下两种方式,一个是task,一个是tasks方法创建TaskContainer

    1. task customTask1 {
    2. doFirst {
    3. println 'customTask1:doFirst'
    4. }
    5. doLast {
    6. println 'customTask1:doLast'
    7. }
    8. }
    9. tasks.create("customTask2") {
    10. doFirst {
    11. println 'customTask2:doFirst'
    12. }
    13. doLast {
    14. println 'customTask2:doLast'
    15. }
    16. }

    任务依赖

    通过dependsOn方法。dependsOnTask类的一个方法,可以接受多个依赖的任务作为参数

    1. task ex35Hello << {//<<符号是doLast方法的缩写
    2. println 'hello'
    3. }
    4. task ex35World << {
    5. println 'world'
    6. }
    7. task ex35Main(dependsOn: ex35Hello) {
    8. doLast {
    9. println 'main'
    10. }
    11. }
    12. task ex35MultiTask {
    13. dependsOn ex35Hello,ex35World
    14. doLast {
    15. println 'multiTask'
    16. }
    17. }

    任务间通过API控制和交互

    创建一个任务和我们定义一个变量是一样的,变量名就是我们定义的任务名,类型Task。所以我们可以通过任务名,使用 Task API 访问它的方法属性或者对任务重新配置等。
    对于直接通过任务名操纵任务的原理是:Project 在创建该任务的时候,同时把该任务对应的任务名注册Project 个属性,类型是 Task

    1. task ex36Hello << {
    2. println 'dowLast1'
    3. }
    4. ex36Hello.doFirst {
    5. println 'dowFirst'
    6. }
    7. ex36Hello.doLast {
    8. println project.hasProperty('ex36Hello')//true
    9. println 'dowLast2'
    10. }

    自定义属性

    ProjectTask 都允许用户添加额外的自定义属性,要添加额外的属性,通过 ext 属性即可实现。相比局部变量,自定义属性有更为广泛的作用域,你可以跨 Project,跨 Task访问这些自定义属性。只要你能访问这些属性所属的对象,那么这些属性都可以被访问到。

    1. apply plugin: "java"
    2. //自定义一个Project的属性
    3. ext.age = 18
    4. //通过代码块同时自定义多个属性
    5. ext {
    6. phone = 1334512
    7. address = ''
    8. }
    9. sourceSets.all {
    10. ext.resourcesDir = null
    11. }
    12. sourceSets {
    13. main {
    14. resourcesDir = 'main/res'
    15. }
    16. test {
    17. resourcesDir = 'test/res'
    18. }
    19. }
    20. task ex37CustomProperty << {
    21. println "年龄是:${age}"
    22. println "电话是:${phone}"
    23. println "地址是:${address}"
    24. sourceSets.each {
    25. println "${it.name}的resourcesDir是:${it.resourcesDir}"
    26. }
    27. }

    第四章Gradle任务

    创建Task

    1. 方法一,调用Projecttask(string)方法

    1. def Task ex41CreateTask1 = task(ex41CreateTask1)
    2. ex41CreateTask1.doLast {
    3. println "创建方法原型为:Task task(String name) throws InvalidUserDataException"
    4. }
    1. 方法二,调用Projecttask(name,map),以一个任务名字+一个对该任务配置的 Map 对象。Map可配置项有限(type,overwrite,dependsOn,action,description,group),参考TaskContainer#Task create(Map options)
    配置项描述默认值
    type基于一个存在 Task 来创建,和我们类继承 不多DefaultTask
    overwrite是否替换存在 Task ,这个和 type 合起来用false
    dependsOn用于配直任务的依赖[]
    action添加到任务中的 Actio 或者一个闭包null
    description用于配置任务的描述null
    group用于配置任务的分组null

    1. def Task ex41CreateTask2 = task(ex41CreateTask2,group:BasePlugin.BUILD_GROUP)
    2. ex41CreateTask2.doLast {
    3. println "创建方法原型为:Task task(Map args, String name) throws InvalidUserDataException"
    4. println "任务分组:${ex41CreateTask2.group}"
    5. }
    1. 方法三,Projecttask(Closure)。闭包里的委托对象就是Task ,所以你可以使用Task对象的任何方法、属性等信息进行配置。

    1. task ex41CreateTask3 {
    2. description '演示任务创建'
    3. doLast {
    4. println "创建方法原型为:Task task(String name, Closure configureClosure)"
    5. println "任务描述:${description}"
    6. }
    7. }
    8. # 核心都是调用TaskContainer 对象中的 create 方法。
    9. tasks.create('ex41CreateTask4') {
    10. description '演示任务创建'
    11. doLast {
    12. println "创建方法原型为:Task create(String name, Closure configureClosure) throws InvalidUserDataException"
    13. println "任务描述:${description}"
    14. }
    15. }

    访问Task

    1. 我们创建的任务都会作为项目(Project )的一个属性,属性名就是任务名,所以我们可以直接通过该任务名称访问和操纵该任务:

    1. task ex42AccessTask1
    2. ex42AccessTask1.doLast {
    3. println 'ex42AccessTask1.doLast'
    4. }
    1. Project 中我们可以通过 tasks 属性访问 TaskContainer,以访问集合元素的方式访问我们创建的任务:

    1. task ex42AccessTask2
    2. tasks['ex42AccessTask2'].doLast {
    3. println 'ex42AccessTask2.doLast'
    4. }

    访问时,任务名就是 Key(关键索引)。 其实这里说 Key 不恰当,因为 tasks 并不是Map 。这里再顺便扩展一下 Groovy 的知识,[]Groovy中是操作符,我们知道 Groovy的操作符有对应方法让我们重载, a[b]对应的是a.getAt(b )这个方法,对应的例tasks[''42AccessTask2'] 其实就是调用 tasks.getAt('ex42AccessTask2')这个方法 如果我们查看Gradle代码的源码最后发现调用 findByName(String name)实现的。

    1. 通过路径访问。get找不到任务就会抛出 UnknownTaskException异常 ,而 find找不到该任务的时候会返 null:

    1. task ex42AccessTask3
    2. tasks['ex42AccessTask3'].doLast {
    3. println tasks.findByPath(':example42:ex42AccessTask3')
    4. println tasks.getByPath(':example42:ex42AccessTask3')
    5. println tasks.findByPath(':example42:asdfasdfasdf')
    6. }
    1. 通过名称访问。getfind区别同上。值得强调的是,通过路径访问的时候, 参数值可以是任务路径也可以是任务的名字。但通过名字访问,参数值只能是任务的名称,不能为路径。

    1. task ex42AccessTask4
    2. tasks['ex42AccessTask4'].doLast {
    3. println tasks.findByName('ex42AccessTask4')
    4. println tasks.getByName('ex42AccessTask4')
    5. }

    任务分组和描述

    任务是可以分组和添加描述的,任务的分组其实就是对任务的分类,便于我们对任务进行归类整理,这样清晰明了 任务的描述就是说明这个任务有什么作用,是这个任务的大概说明。这在使用tasks命令行或Android Studio工具查看时会更直观。

    1. def Task myTask = task ex43GroupTask
    2. myTask.group = BasePlugin.BUILD_GROUP
    3. myTask.description = '这是一个构建的引导任务'
    4. myTask.doLast {
    5. println "group:${group},description:${description}"
    6. }

    << 操作符

    <<操作符在 Gradle Task 上是 doLast 方法的短标记形式,也就是说<<可以代替doLast<< 对应的是 a.leftShift(b)

    1. /**
    2. *

      Adds the given closure to the end of this task's action list. The closure is passed this task as a parameter

    3. * when executed. You can call this method from your build script using the << left shift operator.

    4. *
    5. * @param action The action closure to execute.
    6. * @return This task.
    7. *
    8. * @deprecated Use {@link #doLast(Closure action)}
    9. */
    10. @Deprecated
    11. Task leftShift(Closure action);

    任务执行顺序

    1. def Task myTask = task ex45CustomTask(type: CustomTask)
    2. myTask.doFirst{
    3. println 'Task执行之前执行 in doFirst'
    4. }
    5. myTask.doLast{
    6. println 'Task执行之后执行 in doLast'
    7. }
    8. class CustomTask extends DefaultTask {
    9. @TaskAction
    10. def doSelf() {
    11. println 'Task自己本身在执行 in doSelf'
    12. }
    13. }

    Gradle会解析其带有TaskAction 注解的方法作为 Task要执行的 Action。然后通过 TaskprependParallelSafeAction方法将 Action 加到 actionsList中。而doFirstdoLast的原理其实就是将Action插入到List的最前面和最后面。

    任务排序

    通过Task的属性,shouldRunAftermustRunAfter干预任务的执行顺序而并非我们理解的任务排序。

    1. task ex46OrderTask1 << {
    2. println 'ex46OrderTask1'
    3. }
    4. task ex46OrderTask2 << {
    5. println 'ex46OrderTask2'
    6. }
    7. ex46OrderTask1.mustRunAfter ex46OrderTask2

    任务的启用和禁用

    使用Taskenabledisable属性

    1. task ex47DisenabledTask << {
    2. println 'ex47DisenabledTask'
    3. }
    4. ex47DisenabledTask.enabled =false

    任务的onlyIf断言

    断言就是一个条件表达式。 Task 一个onlyIf方法,它接受一个闭包作为参数如果该闭包返回 true该任务执行,否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。

    1. final String BUILD_APPS_ALL="all";
    2. final String BUILD_APPS_SHOUFA="shoufa";
    3. final String BUILD_APPS_EXCLUDE_SHOUFA="exclude_shoufa";
    4. task ex48QQRelease << {
    5. println "打应用宝的包"
    6. }
    7. task ex48BaiduRelease << {
    8. println "打百度的包"
    9. }
    10. task ex48HuaweiRelease << {
    11. println "打华为的包"
    12. }
    13. task ex48MiuiRelease << {
    14. println "打Miui的包"
    15. }
    16. task build {
    17. group BasePlugin.BUILD_GROUP
    18. description "打渠道包"
    19. }
    20. build.dependsOn ex48QQRelease,ex48BaiduRelease,ex48HuaweiRelease,ex48MiuiRelease
    21. ex48QQRelease.onlyIf {
    22. def execute = false;
    23. if(project.hasProperty("build_apps")){
    24. Object buildApps = project.property("build_apps")
    25. if(BUILD_APPS_SHOUFA.equals(buildApps)
    26. || BUILD_APPS_ALL.equals(buildApps)){
    27. execute = true
    28. }else{
    29. execute = false
    30. }
    31. }else{
    32. execute = true
    33. }
    34. execute
    35. }
    36. ex48BaiduRelease.onlyIf {
    37. def execute = false;
    38. if(project.hasProperty("build_apps")){
    39. Object buildApps = project.property("build_apps")
    40. if(BUILD_APPS_SHOUFA.equals(buildApps)
    41. || BUILD_APPS_ALL.equals(buildApps)){
    42. execute = true
    43. }else{
    44. execute = false
    45. }
    46. }else{
    47. execute = true
    48. }
    49. execute
    50. }
    51. ex48HuaweiRelease.onlyIf {
    52. def execute = false;
    53. if(project.hasProperty("build_apps")){
    54. Object buildApps = project.property("build_apps")
    55. if(BUILD_APPS_EXCLUDE_SHOUFA.equals(buildApps)
    56. || BUILD_APPS_ALL.equals(buildApps)){
    57. execute = true
    58. }else{
    59. execute = false
    60. }
    61. }else{
    62. execute = true
    63. }
    64. execute
    65. }
    66. ex48MiuiRelease.onlyIf {
    67. def execute = false;
    68. if(project.hasProperty("build_apps")){
    69. Object buildApps = project.property("build_apps")
    70. if(BUILD_APPS_EXCLUDE_SHOUFA.equals(buildApps)
    71. || BUILD_APPS_ALL.equals(buildApps)){
    72. execute = true
    73. }else{
    74. execute = false
    75. }
    76. }else{
    77. execute = true
    78. }
    79. execute
    80. }

    打包时,只需要在命令行(或者新建task设置property,调用Task#setProperty(name,value)):

    1. #打所有渠道包
    2. . /gradlew :example48:build
    3. . /gradlew -Pbuild_apps=all :example48 : build
    4. #打首发包
    5. . /gradlew -Pbuild_apps=shoufa :example48 : build
    6. #打非首发包
    7. ./gradlew -Pbuild_apps=exclude shoufa :example48:build

    其中,命令行中-P意思是为Project指定K-V格式的属性键值对,使用格式为-PK=V。(命令行task -h可查看用法)

    任务规则

    通过TaskContaineraddRule()方法添加规则。

    1. Rule addRule(Rule var1);
    2. Rule addRule(String var1, Closure var2);

    当我们执行、依赖一个不存在的任务时, Gradle会执行失败,失败信息是任务不存在。我们使用规则对其进行改进,当执行、依赖不存在的任务时,不会执行失败,而是打印提示信息,提示该任务不存在:

    1. tasks.addRule("对该规则的一个描述,便于调试、查看等") { String taskName ->
    2. task(taskName) << {
    3. println "该${taskName}任务不存在,请查证后再执行"
    4. }
    5. }
    6. task ex49RuleTask {
    7. dependsOn missTask//此任务不存在,会打印上面的提示
    8. }

    第五章 Gradle插件

    插件作用

    • 可以添加任务到你的项目中,帮你完成一些事情,比如测试、编译、打包
    • 可以添加依赖配置到你的项目中,我们可以通过它们配置我们项目在构建过程中需要的依赖,如我们编译的时候依赖的第三方库等。
    • 可以向项目中现有的对象类型添加新的扩展属性、方法等,让你可以使用它们帮助我们配置、优化构建,比如 android {}这个配置块就是Android Gradle插件为Project对象添加的 个扩展。
    • 可以对项目进行一些约定,比如应用 Java件之后,约定 src/main/java目录下是我们的源代码存放位置,在编译的时候也是编译这个目录下的 Java 源代码文件。

    使用插件

    1. 二进制插件
      二进制插件就是实现了org.gradle.api.Plugin接口的插件。

    1. apply plugin:'java'
    2. apply plugin:org.gradle.api.plugins.JavaPlugin
    3. apply plugin:JavaPlugin

    上面三种写法是等价的。对于Gradle内置的二进制插件,都有一个容易记的短名,成为plugin id,如上面的java。非内置的需要使用全路径名导入,就是第二种写法。而包 org.gradle.api.plugins又是Gradle默认导入的,所以可以省去,也就是第三种写法。

    1. 脚本插件

    1. apply from:'version.gradle'
    2. task ex52PrintlnTask << {
    3. println "App版本是:${versionName},版本号是:${versionCode}"
    4. }

    其实这不能算一个插件,它只是一个脚本。应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它使用的是from关键字,后面紧跟的是 个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用 HTTP URL
    虽然它不是一个真正的插件,但是不能忽视它的作用,它是脚本文件模块化的基础,我们可以把庞大的脚本文件,进行分块、分段整理,拆分成一个个共用、职责分明的文件,然后使apply from 来引用它们,比如我们可以把常用的函数放在一个 utils.gradle 脚本里,供其他脚本文件引用。

    1. 第三方插件
      第三方插件需要先使用buildscript{}进行配置。更多的第三方插件可以通过https://plugins.gradle.org/查找。

    自定义插件

    如果仅在自身项目中使用,可以在build文件中继承Plugin类实现apply()接口。

    1. apply plugin: Ex53CustomPlugin
    2. class Ex53CustomPlugin implements Plugin<Project> {
    3. void apply(Project project) {
    4. project.task('ex53CustomTask') << {//创建了一个任务供导入方使用
    5. println "这是一个通过自定义插件方式创建的任务"
    6. }
    7. }
    8. }

    现在我们就可以用也gradlew :example53 :ex53CustomTask 来执行这个任务,这个任务是我们通过自定义插件创建的。

    如果我们想开发个独立的插件给所有想用的人怎么做呢?这就需要我们单独创建一个Groovy 工程作为开发自定义插件的工程了。

    1. 创建 Groovy 工程,然后配置我们插件开发所需的依赖:

    1. apply plugin: 'groovy'
    2. dependencies {
    3. compile gradleApi()
    4. compile localGroovy()
    5. }
    1. 实现插件类

    1. # Ex53CustomPlugin.groovy
    2. package com.github.rujews.plugins
    3. import org.gradle.api.Plugin
    4. import org.gradle.api.Project
    5. class Ex53CustomPlugin implements Plugin<Project>{
    6. @Override
    7. void apply(Project target) {
    8. target.task('ex53CustomTask') << {
    9. println "这是一个通过自定义插件方式创建的任务"
    10. }
    11. }
    12. }
    1. 因为每个插件都有一个唯一的 plugin id,所以需要在META-INFO文件夹里的properties 文件来定义对应插件实现类。在src/main/resources/META-INFO/gradle-plugins目录下新建[plugin_id].properties文件,其中,文件名就是plugin id。在文件中写入:

    implementation-class=com.github.rujews.plugins.Ex53CustomPlugin
    

    keyimplementation class 固定不变, value 就是我们自定义的插件的实现类,上面的例子中就是com.github.rujews.plugins.Ex53CustomPlugin 。现在都配置好了,我们就可以生成一个 jar 包分发给其他人使用我们的插件了.

    1. buildscript {
    2. dependencies {
    3. classpath files (’ libs/example53 . jar’)
    4. }
    5. }
    6. apply plugin:'com.github.rujews.plugins.ex53customplugin'

    第六章 Java Gradle插件

    构建Java项目

    执行build这个任务,输出如下:

    1. main
    2. test
    3. :example64:compileJava
    4. :example64:processResources
    5. :example64:classes
    6. :example64:jar
    7. :example64:assemble
    8. :example64:compileTestJava UP-TO-DATE
    9. :example64:processTestResources UP-TO-DATE
    10. :example64:testClasses UP-TO-DATE
    11. :example64:test UP-TO-DATE
    12. :example64:check UP-TO-DATE
    13. :example64:build

    从上面可以看出这个任务在执行过程中都做了什么,最后在build/libs生成jar包。clean任务则是删除build目录以及其他构建生成的文件。
    assemble 任务,该任务不会执行单元测试,只会编译和打包。这个任务在 Android里也有,执行它可以打 apk 包,所以它不止会打 jar 包,其实它算是一个引导类的任务,根据不同的项目类型打出不同的包。
    check 任务,它只 执行单元测试,有时候还会做一些质量检查,不会打 jar 包,也是个引导任务。

    源码集合(SourceSet)

    Java插件在Project下为我们提供了一个sourceSets属性以及 sourceSets{}闭包来访问和配置源集。sourceSets{}闭包配置的都是 SourceSet对象。

    1. apply plugin:'java'
    2. sourceSets {
    3. main {
    4. //这里对main SourceSet配置
    5. }
    6. }
    7. task ex65SourceSetTask {
    8. sourceSets.all{
    9. println name
    10. }
    11. }

    常用源集属性

    Java 插件添加的通用任务

    源集任务


    运行任务的时候,列表中的任务名称中 sourceSet 要换成你的源集的名称,比如 main源集的名称就是compileMainJava

    Java插件添加的属性

    Java 插件添加了很多常用的属性,这些属性都被添加到Project 中,我们可以直接使用比如前面己经用到的 sourceSets:

    Jav 插件添加的源集属性

    多项目构建

    多项目构建,其实就是多个 Gradle 项目一起构建。

    目录结构


    如上图目录结构,同一个模块下也可以在setting.gradle中进行配置以达到模块化的作用,供外部依赖使用。

    1. include ':example68app'
    2. project(':example68app').projectDir = new File(rootDir, 'chapter06/example68/app')
    3. include ':example68base'
    4. project(':example68base').projectDir = new File(rootDir, 'chapter06/example68/base')

    这样,我们可以在app项目中使用base项目了:

    1. apply plugin:'java'
    2. dependencies {
    3. compile project(':example68base')
    4. }

    发布构建

    Gradle 构建的产物,我们称之为构件。一个构件可以是一个 Jar ,也可以是一个 Zip或者 WAR等。

    1. 定义发布的构件

    1. apply plugin:'java'
    2. task publishJar(type: Jar)
    3. artifacts {
    4. archives publishJar //通过一个 Task 来为我们发布提供构件
    5. }

    也可以发布文件:

    1. def publishFile = file ( ’ build/buildile ’)
    2. artifacts {
    3. archives publishFile
    4. }
    1. 发布构件,也就是上传构件到指定的地方。可以是一个指定的目录,Maven库等。

    1. apply plugin:'java'
    2. apply plugin:'maven'
    3. task publishJar(type: Jar)
    4. group 'org.flysnow.androidgradlebook.ex69'
    5. version '1.0.0'
    6. artifacts {
    7. archives publishJar
    8. }
    9. uploadArchives {
    10. repositories {
    11. flatDir {
    12. name 'libs'
    13. dirs "$projectDir/libs" //发布到本地libs目录
    14. }
    15. mavenLocal()//发布到本地Maven库,也就是用户目录的.m2/repository文件夹
    16. mavenDeployer {
    17. repository(url: "http://repo.mycompany.com/nexus/content/repositories/releases") {
    18. authentication(userName: "usrname", password: "pwd")
    19. }
    20. snapshotRepository(url: "http://repo.mycompany.com/nexus/content/repositories/snapshots") {
    21. authentication(userName: "usrname", password: "pwd")
    22. }
    23. }
    24. }
    25. }

    第七章 Android Gradle插件

    Android Gradle插件的分类

    App 插件 id: com.android.application
    Library 插件 id: com.android.library
    Test 插件 id: com.android.test

    Android Gradle 工程

    1. Android Gradle 工程的配置,都是在 android{}中,这个是唯一的入口。其具体实现是在com.android build.gradle.AppExtension,是一个Project的扩展。参考源码,以及Doc文档
      其创建原型:

    1. extension = project.extensions.create ('android', getExtensionClass() ,
    2. (Projectlnternal) project, instantiator, androidBuilder, sdkHandler,
    3. buildTypeContainer, productFlavorContainer, signingConfigContainer,
    4. extraModelinfo, isLibrary())
    1. defaultConfigdefaultConfig 是默认的配置,它是一个ProductFlavor

    1. /**
    2. * The default configuration, inherited by all build flavors (if any are defined).
    3. */
    4. public void defaultConfig(Action action) {
    5. checkWritability();
    6. action.execute(defaultConfig);
    1. buildTypesbuildTypes是一个NamedDomainObjectContainer 类型,是一个域对象。这个和 SourceSet一样。buildTypes里面有release debug 等。我们可以在 buildTypes{}里新增任意多个我们需要构建的类型,比如debug,Gradle 会帮我们自动创建一个对应 BuildType ,名字就是我们定义的名字。
      proguardFiles 方法可以接受一个可变参数。所以我们可以同时配置多个配置文件。

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

    getDefaultProguardFileAndroid 扩展的方法,它可以获取你的Android SDK录下默认proguard 配置文件。在 android-sdk/tools proguard/目录下,文件名就是我们传入的参数名字 proguard-android.txt

    Android Gradle任务

    Android插件是基于Java插件的,因此 Android插件基本上包含了所有Java插件的功能,包括继承的任务,比如 assemble check build等。除此之外, Android在大类上还添加了connectedCheck deviceCheck lint install uninstall等任务,这些是属于Android特有功能。具体作用可以参考java_plugin_tasks说明

    第八章 自定义Android Gradle工程

    defaultConfig默认属性

    defaultConfig{}配置中applicationldminSdkVersionversionCode,versionName属性在没有配置时,会去manifest文件中查找。

    配置签名信息

    一个 SigningConfig就是一个签名配置,其可配置的元素如下:storeFile签名证书文件, storePassword签名证书文件的密码,storeType 签名证书的类型, keyAlias 签名证书中密钥别名, keyPassword 签名证书中该密钥的密码。
    Android SDK自动生成的debug证书。它一般位于$HOME/.android/debug.keystore路径下。

    启用zipalign优化

    zipalign是Android 为我们提供的一个整理优化 apk文件的工具,它能提高系统和应用的运行效率,更快地读写apk中的资源,降低内存的使用。所以对于要发布的App,在发布之前一定要使用 zipalign 进行优化。

    第九章 Android Gradle高级自定义

    使用共享库

    那些不包含在Android SDK库里,比如 com.google.android.maps,android.test.runner等,这些库是独立的,并不会被系统自动链接,所以我们要使用它们的话,就需要单独进行生成使用,这类库我们称为共享库。
    AndroidManifest 文件中,我们可以指定要使用的库:

    1. <uses-library
    2. android:name=” com.google.android.maps”
    3. android:required="true" />

    这样我们就声明了需要使用maps这个共享库。声明之后,在安装生成的apk包的时候,系统会根据我们的定义,帮助检测手机系统是否有我们需要的共享库 。因为我们设置的android:required="true",如果子机系统不满足,将不能安装该应用。
    Android中,除了标准的SDK还存在两种库: 一种是 add-ons 库,它们位于 add-ons目录下,,这些库大部分是第三方厂商或者公司开发的, 一般是为了让开发者使用,但是又不想暴露具体标准实现的;第二类是optional可选库,它们位于 platforms/android-xx/optional 目录下,一般是为了兼容旧版本的 API,比如org.apache.http.legacy ,这是 HttpClient的库 。从
    API 23 开始,标准的 Android SDK 中不再包含 HttpClient 库,如果还想使用 HttpClient 库,就必须使用org.apache.http.legacy这个可选库。后者不会自动添加到我们的classpath里。需要使用下面的方法:

    1. android {
    2. useLibrary 'org.apache.http.legacy'
    3. }

    批量修改apk名

    1. applicationVariants.all { variant ->
    2. variant.outputs.each { output ->
    3. if (output.outputFile != null && output.outputFile.name.endsWith('.apk')
    4. &&'release'.equals(variant.buildType.name)) {
    5. def flavorName = variant.flavorName.startsWith("_") ? variant.flavorName.substring(1) : variant.flavorName
    6. def apkFile = new File(
    7. output.outputFile.getParent(),
    8. "Example92_${flavorName}_v${variant.versionName}_${buildTime()}.apk")
    9. output.outputFile = apkFile
    10. }
    11. }
    12. }

    Android对象为我们提供了 个属性:applicationVariants(仅仅适用于 Android应用 Gradle 插件) , libraryVariants( 仅仅适用于 AndroidGradle插件),testVariants(以上两种Gradle插件都适用)。
    ps:
    allgradleDomainObjectCollection 接口的方法。
    eachgroovyListMap 等集合类的方法。
    它们的区别:
    1.all 会对集合内现有的元素和之后加入的元素,都执行给定的闭包操作。
    each 只会对集合内现有的元素执行给定的闭包操作。
    2.all接收的闭包,可以直接访问集合内元素的属性和方法。
    each接收的闭包,不能直接访问集合内元素的属性和方法。
    原因:
    all 接收的闭包是一个配置闭包,集合内元素既会作为参数传给这个闭包,也会被设为闭包的委托对象,这样闭包就可以直接访问委托对象(集合内元素)的属性和方法。
    each 接收的是一个普通闭包,集合内元素只会作为参数传给这个闭包。

    从git获取apk的版本信息

    1. /**
    2. * 以git tag的数量作为其版本号
    3. * @return tag的数量
    4. */
    5. def getAppVersionCode(){
    6. def stdout = new ByteArrayOutputStream()
    7. exec {
    8. commandLine 'git','tag','--list'
    9. standardOutput = stdout
    10. }
    11. return stdout.toString().split("\n").size()
    12. }
    13. /**
    14. * 从git tag中获取应用的版本名称
    15. * @return git tag的名称
    16. */
    17. def getAppVersionName(){
    18. def stdout = new ByteArrayOutputStream()
    19. exec {//执行shell命令
    20. commandLine 'git','describe','--abbrev=0','--tags'
    21. standardOutput = stdout
    22. }
    23. return stdout.toString().replaceAll("\n","")
    24. }

    动态配置manifest

    1. productFlavors.all { flavor ->
    2. manifestPlaceholders.put("UMENG_CHANNEL",name)
    3. }

    自定义BuildConfig

    1. productFlavors {
    2. google {
    3. buildConfigField 'String','WEB_URL','"http://www.google.com"'
    4. }
    5. baidu {
    6. buildConfigField 'String','WEB_URL','"http://www.baidu.com"'
    7. }
    8. }

    动态添加自定义资源

    1. productFlavors {
    2. google {
    3. resValue 'string','channel_tips','google渠道欢迎你'
    4. }
    5. baidu {
    6. resValue 'string','channel_tips','baidu渠道欢迎你'
    7. resValue 'color','main_bg','#567896'
    8. resValue 'integer-array','months','1</item>'
    9. }
    10. }

    其效果和在 res/values 文件中定义一个资源是等价的。最终生成的值可以在build/generated/res/resValues/[productFlavor]/[buildType]/ values/generated.xml中找到。

    Java编译选项

    1. compileOptions {
    2. encoding = ’ utf-8
    3. sourceCompatibility = JavaVersion.VERSION_1_6
    4. targetCompatibility = JavaVersion.VERSION_1_6
    5. }

    adb 操作选项配置

    1. adbOptions {
    2. timeOutinMs = 5*1000//
    3. installOptions '-r','-s'
    4. }

    adb installl,r,t,s,d,g六个选项。-l 锁定该应用程序。-r:替换己存在的应用程序,也就是我们说的强制安装 ;-t :允许测试包; -s: 也把应用程序安装到SD卡上。-d: 允许进行降级安装,也就是安装的程序比于机上带的版本低。-g: 为该应用授予所有运行时的权限。

    自动清理未使用的资源

    1. 手动清除
    2. Android Lint工具
    3. Resource Shrinking,需要配合Code Shrinking一起使用。

    1. release {
    2. minifyEnabled true
    3. shrinkResources true
    4. proguardFiles getDefaultProguardFile ('proguard-android.txt '),'proguard�rules.pro'
    5. }

    如果想看详细日志,想知道哪些资源被自动清理了,可以使--info 标记。

    ./gradlew clean:example912:assembleRelease --info I grep "unused resource "
    

    自动清理未使用的资源这个功能虽好,但是有时候会误删有用的程 ,为什么呢? 因为我在代码编写的时候,可能会使用反射去引用资源文件,尤其很多你引用的第三方库会这么做,这时候Android Gradle 就区分不出来了,可能会误认为这些资源没有被使用。针对这种情况,Android Gradle 提供了keep方法来让我们配置哪些资源不被清理。
    keep方法使用非常简单,我们要新建xml 文件来配 ,这个文件 res/raw/keep.xml,然后通过 tools:keep 属性来配置。这个tools:keep接受一个以逗号(,)分割的配置资源列表,并且支持星号(*)通配符。

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <resources xmlns:tools http://schemas.android.com/tools”
    3. tools:keep= @layout/used*_c,@layout/l_used_a,@layout/l_used_b* />
    1. resConfigs方法

    1. defaultConf {
    2. ...
    3. resConfigs 'zh'
    4. }

    第十章 Android Gradle多项目构建

    库项目的引用和配置

    默认情况下, Android 库项目发布出来的包都是release版本的,当然可以通过配置来改变它,比如改成默认发布的,这就是 debug 版本的:

    1. android{
    2. defaultPublishConfig "flavor1Debug"//flavor+buildtype
    3. }

    如果要针对不同的版本,引用不同的发布的 aar

    1. android{
    2. publishNonDefault true
    3. }

    在引用时使用以下方法:

    1. dependencies {
    2. flavor1Compile project(path:':lib1', configuration:'flavorlRelease')
    3. flavor2Compile project(path :':lib1', configuration:'flavor2Release')
    4. }

    库项目发布到Nexus

    应用过Maven插件之后,我们需要配置 Maven构建的 要素,它们分别是 group:artifact:version。使用 groupversion 比较方便,直接指定即可。应用 version 还要理解一个概念,快照版本 SNAPSHOT ,比如配置成 1.0.0-SNAPSHOT ,这时候就会发布到 snapshot 中心库里,每次发布版本号不会变化,只会在版本号后按顺序号+1 ,比如 1.0 0-1, 1.0.0-2, 1.0.0-3 等。类似于这样的版本号,我们引用的时候版本号写成 1.0.0-SNAPSHOT 即可, Maven 会帮我们下载最新(序号最大的)的快照版本 。这种方式适用于联调测试的时候,每次修复好测试的问题就发布一个快照版本,直到没有问题为止,然后再放出 release 版本,正式发布。

    1. uploadArchives {
    2. repositories.mavenDeployer {
    3. name = 'mavenCentralReleaseDeployer' //名称固定
    4. repository(url: "http://xxx/nexus/repository/releases/") { //仓库地址
    5. authentication(userName: "admin", password: "admin") //仓库用户名和密码
    6. }
    7. snapshotRepository (url: "http://xxx/nexus/repository/snapshots/") { //仓库地址
    8. authentication(userName: "admin", password: "admin") //仓库用户名和密码
    9. }
    10. pom.version = '3.5.23-dev' //版本号
    11. pom.artifactId = "test" //aar名称
    12. pom.groupId = "com.example.abc" //包名
    13. pom.name = "sdk"
    14. pom.packaging = 'aar' //打包格式,aar固定
    15. }
    16. }

    然后配置好仓库的地址告知Gradle

    1. allprojects {
    2. repositories {
    3. ...
    4. maven {
    5. url "http://xxx/nexus/content/groups/xxx/"
    6. }
    7. }

    第十一章 Android多渠道构建

    多渠道构建定制

    1. consumerProguardFiles。既是一个属性,也有一个同名的方法,它只对 Android库项目有用。它使得混淆文件列表也会被打包到aar里一起发布 ,这样 当应用项目 引用这个 aar 包, 并且启用混淆的时候,会自动使用aar包里的混淆文件对 aar 里的代码进行混淆 ,这样我们就不用对该 aar 包进行混淆配置了。

    1. android {
    2. productFlavors {
    3. google {
    4. consumerProguardFiles 'proguard-rules.pro','proguard-rules.txt'
    5. }
    6. }
    7. }
    1. flavorDimensionsProductFlavor的一个属性,接受一个字符串,作为该 ProductFlavor的维度。

    1. flavorDimensions "abi", "version"
    2. productFlavors {
    3. free {
    4. dimension 'version'
    5. }
    6. paid {
    7. dimension 'version'
    8. }
    9. x86 {
    10. dimension 'abi'
    11. }
    12. arm {
    13. dimension 'abi'
    14. }
    15. }

    提高多渠道构建的效率

    参考美团技术的方法:利用了在 apkMETA-INF 目录下添加空文件不用重新签名的原理,非常高效,其大概就是:

    1. 利用 Android Gradle打一个基本包(母包);
    2. 然后基于该包复制一个,文件名要能区分产品 、打包时间、版本、渠道等;
    3. 然后对复制出来的 apk 文件进行修改,在其 META-INF 目录下新增空文件,但是空文件的文件名要有意义,必须包含能区分渠道的名字 ,如 mtchannel _google;
    4. 重复步骤2 、步骤3生成我们所需的所有渠道包 apk ,这个可以使用 Python 这类脚本来做;
    5. 这样就生成了我们所有发布渠道的 apk 包了。
  • 相关阅读:
    SEGGER Embedded Studio IDE移植FreeRTOS
    想知道电脑录屏软件哪个好用?这三个工具轻松实现屏幕录制
    [Unity独立/合作开发]实现背包系统中物品的拾取拖拽掉落还有换位置
    UE4 C++:TSet容器
    十三、SpringBoot错误处理底层组件和异常处理流程分析
    pandas教程:Pivot Tables and Cross-Tabulation 数据透视表和交叉表
    数据与视图的完美契合:Vue响应式的交织魅力
    1623. 三人国家代表队
    SlicerPro超级切片家具建模插件使用教程
    《Python 3网络爬虫开发实战 》崔庆才著 第五章笔记
  • 原文地址:https://blog.csdn.net/xiaopangcame/article/details/133917747