• 《Android Gradle》权威指南笔记


    文章目录

    Gradle

    1. Gradle入门
    1.2 gradle版helloWorld
    • build.gradle
    1. // 这个构建脚本定义了一个(任务)Task,任务名hello
    2. // doLast意味着在Task执行完毕之后,要回调doLast里的闭包
    3. task hello {
    4. doLast {
    5. // groovy中,单引号和双引号都代表字符串
    6. println 'hello world'
    7. }
    8. }

    执行:gradle hello

    Configure project :
    hello world

    build.gradle是Gradle默认的构建脚本文件,执行Gradle命令时,会默认加载当前目录下的build.gradle文件

    1.3 Gradle Wrapper

    Wrapper顾名思义,是对Gradle的一层包装,便于在团队开发时统一Gradle构建的版本。

    • 生成wrapper

      1. wangzhiping@wangzhiping-PC:~/GradleProject$ gradle wrapper
      2. BUILD SUCCESSFUL in 2s
      3. 1 actionable task: 1 executed
    • 目录结构

      wangzhiping@wangzhiping-PC:~/GradleProject/gradle$ tree
      .
      └── wrapper
      ├── gradle-wrapper.jar
      └── gradle-wrapper.properties

    1.4 Gradle 日志
    级别用途
    error错误信息
    quiet重要信息
    warning警告信息
    lifecycle进度信息
    info信息
    debug调试信息
    开关选项输出的日志级别
    无选项lifecycle及其更高级别
    -q 或者 --quietquiet及其更高级别
    -i 或者 --infoinfo及其更高级别
    -d 或者 --debugdebug及其更高级别,这一般会输出所有日志
    1.4.2 输出错误信息

    默认情况下,堆栈信息的输出是关闭的,需要通过命令打开。

    命令行选项用于
    无选项没有堆栈输出
    -s 或者–stacktrace输出关键性的堆栈信息
    -S 或者 --full-stacktrace输出全部堆栈信息

    一般推荐使用-s,-S过于冗余

    1.5 Gradle 命令行
    • 1.5.1 使用帮助

      gradle -h

    • 1.5.2 查看所有可执行的tasks

      gradle tasks

    • 1.5.5 多任务执行

      gradle clean hello

      多个task用空格隔开

    • 1.5.6 通过任务名缩写执行

      例如名为helloWorld的task可以执行为:

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Task :helloWorld
      hello world

    2. Groovy基础

    Groovy是基于jvm的一种动态语言,完全兼容java,在此基础上又增加了很多动态类型和灵活的特性,比如支持闭包,支持dsl,可以说是一门非常灵活的脚本语言。

    2.1 字符串

    在groovy中,单引号双引号都可以定义一个字符串常量(java里单引号定义一个字符),不同的是单引号标记的是纯粹的字符串常量,而不是对字符串里的表达式做运算,但是双引号可以

    • build.gradle

      1. task helloWorld {
      2. def str1 = 'str1'
      3. def str2 = "str2"
      4. println "单引号定义的字符类型:" + str1.getClass().name
      5. println "双引号定义的字符类型:" + str2.getClass().name
      6. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      单引号定义的字符类型:java.lang.String
      双引号定义的字符类型:java.lang.String

      BUILD SUCCESSFUL in 438ms
      <-------------> 0% WAITING

    • build.gradle

      1. task helloWorld {
      2. def str1 = 'str1'
      3. def str2 = "str2"
      4. println '单引号定义的字符类型:${str1}'
      5. println "双引号定义的字符类型:${str2}"
      6. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      单引号定义的字符类型:${str1}
      双引号定义的字符类型:str2

      可见,在单引号中不能对字符串里的表达式做运算

    2.2 集合
    • 2.1 List

      1. task helloWorld {
      2. def numList = [1,2,3,4,5]
      3. println numList.getClass().name
      4. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      java.util.ArrayList

      可见,numList是一个ArrayList

    • 2.2 打印list

      1. task helloWorld {
      2. def numList = [1,2,3,4,5]
      3. println numList.get(0) // 访问第一个元素
      4. println numList[0] // 访问第一个元素
      5. println numList[-2] // 访问倒数第二个元素
      6. println numList[1..3] // 访问第2-4个元素
      7. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      1
      1
      4
      [2, 3, 4]

    • 制造异常

      1. task helloWorld {
      2. def numList = [1,2,3,4,5]
      3. println numList[-20] // 抛出异常
      4. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -stacktrace hW

      Caused by: java.lang.ArrayIndexOutOfBoundsException: Negative array index [-20] too large for array size 5
      at build_atjutgku0sx4akor6tqhpkg7lr u n c l o s u r e 1. d o C a l l ( / h o m e / w a n g z h i p i n g / G r a d l e P r o j e c t / b u i l d . g r a d l e : 5 ) a t o r g . g r a d l e . u t i l . i n t e r n a l . C l o s u r e B a c k e d A c t i o n . e x e c u t e ( C l o s u r e B a c k e d A c t i o n . j a v a : 72 ) a t o r g . g r a d l e . u t i l . i n t e r n a l . C o n f i g u r e U t i l . c o n f i g u r e T a r g e t ( C o n f i g u r e U t i l . j a v a : 155 ) a t o r g . g r a d l e . u t i l . i n t e r n a l . C o n f i g u r e U t i l . c o n f i g u r e S e l f ( C o n f i g u r e U t i l . j a v a : 131 ) a t o r g . g r a d l e . a p i . i n t e r n a l . A b s t r a c t T a s k . c o n f i g u r e ( A b s t r a c t T a s k . j a v a : 669 ) a t o r g . g r a d l e . a p i . D e f a u l t T a s k . c o n f i g u r e ( D e f a u l t T a s k . j a v a : 309 ) a t o r g . g r a d l e . a p i . i n t e r n a l . p r o j e c t . D e f a u l t P r o j e c t . t a s k ( D e f a u l t P r o j e c t . j a v a : 1275 ) a t o r g . g r a d l e . i n t e r n a l . m e t a o b j e c t . B e a n D y n a m i c O b j e c t _run_closure1.doCall(/home/wangzhiping/GradleProject/build.gradle:5) at org.gradle.util.internal.ClosureBackedAction.execute(ClosureBackedAction.java:72) at org.gradle.util.internal.ConfigureUtil.configureTarget(ConfigureUtil.java:155) at org.gradle.util.internal.ConfigureUtil.configureSelf(ConfigureUtil.java:131) at org.gradle.api.internal.AbstractTask.configure(AbstractTask.java:669) at org.gradle.api.DefaultTask.configure(DefaultTask.java:309) at org.gradle.api.internal.project.DefaultProject.task(DefaultProject.java:1275) at org.gradle.internal.metaobject.BeanDynamicObjectr​unc​losure1.doCall(/home/wangzhiping/GradleProject/build.gradle:5)atorg.gradle.util.internal.ClosureBackedAction.execute(ClosureBackedAction.java:72)atorg.gradle.util.internal.ConfigureUtil.configureTarget(ConfigureUtil.java:155)atorg.gradle.util.internal.ConfigureUtil.configureSelf(ConfigureUtil.java:131)atorg.gradle.api.internal.AbstractTask.configure(AbstractTask.java:669)atorg.gradle.api.DefaultTask.configure(DefaultTask.java:309)atorg.gradle.api.internal.project.DefaultProject.task(DefaultProject.java:1275)atorg.gradle.internal.metaobject.BeanDynamicObjectMetaClassAdapter.invokeMethod(BeanDynamicObject.java:484)
      at org.gradle.internal.metaobject.BeanDynamicObject.tryInvokeMethod(BeanDynamicObject.java:196)
      at org.gradle.internal.metaobject.CompositeDynamicObject.tryInvokeMethod(CompositeDynamicObject.java:98)
      at org.gradle.internal.extensibility.MixInClosurePropertiesAsMethodsDynamicObject.tryInvokeMethod(MixInClosurePropertiesAsMethodsDynamicObject.java:34)
      at org.gradle.groovy.scripts.BasicScriptS c r i p t D y n a m i c O b j e c t . t r y I n v o k e M e t h o d ( B a s i c S c r i p t . j a v a : 135 ) a t o r g . g r a d l e . i n t e r n a l . m e t a o b j e c t . A b s t r a c t D y n a m i c O b j e c t . i n v o k e M e t h o d ( A b s t r a c t D y n a m i c O b j e c t . j a v a : 163 ) a t o r g . g r a d l e . g r o o v y . s c r i p t s . B a s i c S c r i p t . i n v o k e M e t h o d ( B a s i c S c r i p t . j a v a : 84 ) a t b u i l d a t j u t g k u 0 s x 4 a k o r 6 t q h p k g 7 l . r u n ( / h o m e / w a n g z h i p i n g / G r a d l e P r o j e c t / b u i l d . g r a d l e : 1 ) a t o r g . g r a d l e . g r o o v y . s c r i p t s . i n t e r n a l . D e f a u l t S c r i p t R u n n e r F a c t o r y ScriptDynamicObject.tryInvokeMethod(BasicScript.java:135) at org.gradle.internal.metaobject.AbstractDynamicObject.invokeMethod(AbstractDynamicObject.java:163) at org.gradle.groovy.scripts.BasicScript.invokeMethod(BasicScript.java:84) at build_atjutgku0sx4akor6tqhpkg7l.run(/home/wangzhiping/GradleProject/build.gradle:1) at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactoryScriptDynamicObject.tryInvokeMethod(BasicScript.java:135)atorg.gradle.internal.metaobject.AbstractDynamicObject.invokeMethod(AbstractDynamicObject.java:163)atorg.gradle.groovy.scripts.BasicScript.invokeMethod(BasicScript.java:84)atbuilda​tjutgku0sx4akor6tqhpkg7l.run(/home/wangzhiping/GradleProject/build.gradle:1)atorg.gradle.groovy.scripts.internal.DefaultScriptRunnerFactoryScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:91)
      … 152 more

      增加-stacktrace打印堆栈信息,可以看到是index溢出异常

    • 遍历list

      1. task helloWorld {
      2. def numList = [1,2,3,4,5]
      3. numList.each {
      4. println it
      5. }
      6. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -stacktrace hW

      Configure project :
      1
      2
      3
      4
      5

    • 2.2.2 map

      1. task helloWorld {
      2. def map = ['width':1024, 'height':768]
      3. println map.getClass().name
      4. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -stacktrace hW

      Configure project :
      java.util.LinkedHashMap

    • 取map的key或者value

      1. task helloWorld {
      2. def map = ['width':1024, 'height':768]
      3. println map.getClass().name
      4. println map['width']
      5. println map.height
      6. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -stacktrace hW

      Configure project :
      java.util.LinkedHashMap
      1024
      768

    • 遍历map

      1. task helloWorld {
      2. def map = ['width':1024, 'height':768]
      3. map.each{
      4. println it.key
      5. println it.value
      6. }
      7. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -stacktrace hW

      Configure project :
      width
      1024
      height
      768

    2.3 方法
    • 2.3.1 括号是可以省略的

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

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      3
      3

    • 2.3.2 return是可以不写的

      1. task helloWorld {
      2. println method1(1,2)
      3. }
      4. static def method1(int a, int b) {
      5. if (a > b) {
      6. a
      7. } else {
      8. b
      9. }
      10. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      2

      groovy取最后一行作为返回值,可以不写return

    • 代码块可以作为参数传递

      类似于kotlin的闭包优化规则

      1. task helloWorld {
      2. def numList1 = [1,2,3,4,5]
      3. numList1.each({println it})
      4. // 只有一个参数时,可以把闭包提出来
      5. def numList2 = [1,2,3,4,5]
      6. numList2.each(){println it}
      7. // 括号内没有参数时,可以把括号去掉
      8. def numList3 = [1,2,3,4,5]
      9. numList3.each{
      10. println it
      11. }
      12. }
    2.4 javaBea
    • demo

      1. task helloWorld {
      2. Person p = new Person()
      3. println "名字是: ${p.name}"
      4. p.name = "张三"
      5. println "名字是: ${p.name}"
      6. }
      7. class Person {
      8. private String name
      9. }

    ​ wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

    Configure project :
    名字是: null
    名字是: 张三

    2.5 闭包

    闭包是groovy一个非常重要的特性,可以说是dsl的基础。

    • 2.5.1 初识闭包

      1. task helloWorld {
      2. customEach {
      3. println it
      4. }
      5. }
      6. // 参数名closure是自定义的,可以随便起
      7. def customEach(closure) {
      8. for (int i in 1..10) {
      9. closure(i)
      10. }
      11. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

    • 2.5.2 向闭包传递参数

      1. task helloWorld {
      2. customEach { k,v ->
      3. println "key:" + k + ",value:" + v
      4. }
      5. }
      6. def customEach(closure) {
      7. def map = ["name":"张三", "age":18]
      8. map.each {
      9. closure(it.key, it.value)
      10. }
      11. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle hW

      Configure project :
      key:name,value:张三
      key:age,value:18

    • 2.5.3闭包委托

      1. task helloWorld {
      2. new Delegate().test {
      3. println "thisObject: ${thisObject.getClass()}"
      4. println "owner: ${owner.getClass()}"
      5. println "delegate: ${delegate.getClass()}"
      6. method1()
      7. it.method1()
      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 "context this : ${this.getClass()} in delegate"
      17. println "method1 in delegate"
      18. }
      19. def test(Closure<Delegate> closure) {
      20. closure(this)
      21. }
      22. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -stacktrace hW

      Configure project :
      thisObject: class build_atjutgku0sx4akor6tqhpkg7l
      owner: class build_atjutgku0sx4akor6tqhpkg7lr u n c l o s u r e 1 d e l e g a t e : c l a s s b u i l d a t j u t g k u 0 s x 4 a k o r 6 t q h p k g 7 l _run_closure1 delegate: class build_atjutgku0sx4akor6tqhpkg7lr​unc​losure1delegate:classbuilda​tjutgku0sx4akor6tqhpkg7l_run_closure1
      context this : class build_atjutgku0sx4akor6tqhpkg7l in root
      method1 in root
      context this : class Delegate in delegate
      method1 in delegate

      thisObject的优先级最高,默认情况下使用thisObject来处理闭包中调用的方法,如果有则执行。thisObject其实就是这个构建脚本的上下文,它和脚本中的this对象是相等的。从例子中也证明了delegate和owner是相等的(owner: class build_atjutgku0sx4akor6tqhpkg7lr u n c l o s u r e 1 d e l e g a t e : c l a s s b u i l d a t j u t g k u 0 s x 4 a k o r 6 t q h p k g 7 l _run_closure1 delegate: class build_atjutgku0sx4akor6tqhpkg7lr​unc​losure1delegate:classbuilda​tjutgku0sx4akor6tqhpkg7l_run_closure1)。它们两个的优先级是:owner > delegate高,所以闭包内的方法处理顺序是thisObject > owner > delegate

    • 在dsl中,我们一般指定delegate为当前的it,这样就可以在闭包内对该it进行配置,或者调用其方法:

      1. task helloWorld {
      2. person {
      3. // 可以操作person对象属性,访问person对象的方法
      4. personName = "张三"
      5. personAge = 20
      6. dumpPerson()
      7. }
      8. }
      9. class Person {
      10. String personName
      11. int personAge
      12. def dumpPerson() {
      13. println "name is ${personName}, age is ${personAge}"
      14. }
      15. }
      16. def person(Closure<Person> closure) {
      17. Person p = new Person()
      18. closure.delegate = p
      19. closure.setResolveStrategy(Closure.DELEGATE_FIRST)
      20. closure(p)
      21. }

      拿一段安卓项目的build.gradle文件对比

    • build.gradle

      1. defaultConfig {
      2. applicationId "com.example.myapplication"
      3. minSdk 21
      4. targetSdk 32
      5. versionCode 1
      6. versionName "1.0"
      7. testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
      8. }
      9. // demo
      10. person {
      11. personName = "张三"
      12. personAge = 20
      13. dumpPerson()
      14. }

    2.6 dsl

    dsl(Domain Specific Language),专门关注某一领域的语言,对比java这种通用全面的语言。

    gradle就是一门dsl,它基于groovy,专门解决自动化构建的dsl。

    3. Gradle 构建脚本基础
    3.1 Setting文件

    在Gradle中,定义了一个设置文件,用于初始化以及工程树的配置,设置文件的默认名字是setting.gradle,放在根目录工程下。

    设置文件大多数的作用是为了配置子工程,在gradle中多工程是通过工程树表示的,相当于在android studio中看到的project和module一样,根工程相当于android studio中的project,一个根工程可以有很多子工程,也就是很多的module。

    一个子工程只有在setting里设置了gradle才会去识别,才会在构建的时候被包含进去。

    • setting.gradle

      1. rootProject.name = "My Application"
      2. include ':app'

      对应于目录结构:

      MyApplication/
      ├── app

    3.2 Build文件

    每个project都会有一个Build文件,该文件是该project构建的入口,可以在这里针对project进行配置,比如配置版本,需要哪些插件,依赖哪些库等。

    既然每个project都会有一个build文件,那么root project也不例外。root project可以获得所有child project,所以可以在root project的build文件里对child project统一配置,比如应用的插件,依赖的maven中心库等。

    • build.gradle

      1. subprojects {
      2. repositories {
      3. jcenter()
      4. }
      5. }

      对所有子project配置maven仓库,制定为jcenter

    3.3 projects及tasks

    多个project组成整个gradle的构建,一个project又包含多个task,task是一个原子操作,比如打个jar包,复制一份文件,编译一次java代码。

    3.4 创建一个任务

    task其实是Project对象的一个函数,原型为create(String name, Closure configureClosure)
    参数1:任务的名字,可以自定义
    参数2:一个闭包,也就是花括号内的代码块

    • 写法1

      1. task helloWorld {
      2. doFirst {
      3. println 'customTask:doFirst'
      4. }
      5. doLast {
      6. println 'customTask:doLast'
      7. }
      8. }
    • 写法2

      1. tasks.create("customTask") {
      2. doFirst {
      3. println 'customTask:doFirst'
      4. }
      5. doLast {
      6. println 'customTask:doLast'
      7. }
      8. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle customTask

      Task :customTask
      customTask:doFirst
      customTask:doLast

      两种写法的效果是一样的

    3.5 任务依赖

    使用dependsOn:可以指定多个依赖任务作为参数,dependsOn是Task类的一个方法。

    task之间是有依赖关系的,这样我们就可以控制哪些任务优先于哪些任务执行。比如执行jar任务之前,compile任务一定要先执行过,android的install任务一定要依赖package任务打包生成apk。

    • ex35Hello

      1. task ex35Hello {
      2. println 'hello'
      3. }
      4. task ex35Main(dependsOn: ex35Hello) {
      5. doLast {
      6. println 'Main'
      7. }
      8. }

      通过dependsOn:指定依赖的任务ex35Hello,运行结果:

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex35Main

      Configure project :
      hello

      Task :ex35Main
      Main

    • 指定多个依赖task

      1. task ex35Hello {
      2. println "hello"
      3. }
      4. task ex35World {
      5. println "World"
      6. }
      7. task ex35MultiTask {
      8. dependsOn ex35Hello,ex35World
      9. doLast {
      10. println "multiTask"
      11. }
      12. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex35MultiTask

      Configure project :
      hello
      World

      Task :ex35MultiTask
      multiTask

    3.6 任务间通过API控制、交互

    创建一个任务和定义一个变量是一样的,变量名就是任务名,类型是Task,所以我们可以通过任务名,使用Task的API访问它的方法、属性或者重新配置等。

    • ex35Hello

      1. task ex35Hello {
      2. println "hello"
      3. }
      4. ex35Hello.doFirst {
      5. println "doFirst"
      6. }
      7. ex35Hello.doLast {
      8. println "doLast"
      9. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex35Hello

      Configure project :
      hello

      Task :ex35Hello
      doFirst
      doLast

    • 判断是否有ex35Hello这个变量

      1. task ex35Hello {
      2. println "hello"
      3. }
      4. ex35Hello.doFirst {
      5. println "doFirst"
      6. }
      7. ex35Hello.doLast {
      8. println "has property ${project.hasProperty('ex35Hello')}"
      9. println "doLast"
      10. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex35Hello

      Configure project :
      hello

      Task :ex35Hello
      doFirst
      has property true
      doLast

      project.hasProperty(‘ex35Hello’)运行结果是true,说明每个task都是project的一个属性

    3.7 自定义属性

    project和task都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对应的ext属性即可实现。添加之后可以通过ext属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过ext代码块。

    ext一般用来自定义版本号名称,把版本号和版本名单独放在一个gradle文件中,便于管理。

    • ex37CustomProperty

      1. ext.age = 18
      2. ext {
      3. phone = 122222
      4. address = "xxxddd"
      5. }
      6. task ex37CustomProperty {
      7. println "年龄是 ${age}"
      8. println "电话是 ${phone}"
      9. println "地址是 ${address}"
      10. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex37CustomProperty

      Configure project :
      年龄是 18
      电话是 122222
      地址是 xxxddd

    3.8 脚本即代码,代码也是脚本

    虽然我们在gradle文件中写脚本,但是我们写的都是代码,这一点要记住,这样才能时刻使用groovy,java以及gradle的任何语法和api帮你完成想做的事情。是脚本吗?是,但并不是简单的脚本,这脚本上可以定义class、内部类、导入包、定义方法等。

    • 给打包的apk定义生成的文件名

      1. android {
      2. android.applicationVariants.all { variant ->
      3. variant.outputs.all {
      4. outputFileName = "my_${buildTime()}.apk"
      5. }
      6. }
      7. }
      8. def buildTime() {
      9. def date = new Date()
      10. def formattedDate = date.format('yyyyMMdd')
      11. return formattedDate
      12. }
    4. Gradle任务
    4.1 Gradle多种方式创建任务
    1. 直接以任务名字创建

      1. def Task helloWorld = task(helloWorld)
      2. helloWorld.doLast {
      3. println "helloooo"
      4. }

      该方法完整的定义是:Task task(String name) throws InvalidUserDataExceptions

    2. 任务+一个对该任务配置的map对象来创建

      1. def Task helloWorld = task(helloWorld, group:BasePlugin.BUILD_GROUP)
      2. helloWorld.doLast {
      3. println "helloooo"
      4. println "任务分组${helloWorld.group}"
      5. }

      该函数的原型是:Task task(Map args, String name) throws InvalidUserDataException

      map可配置的参数如下:

      配置项描述默认值
      type基于一个存在的task来创建,和继承差不多DefaultTask
      overwrite是否替换存在的task,和type配合使用false
      dependsOn用于配制任务的依赖[]
      action添加到任务中的一个action或者一个闭包null
      description用于配制任务的描述null
      group用于配制任务的分组null
    3. 任务名+闭包

      1. task ex41CreateTask {
      2. description '演示'
      3. doLast {
      4. println "创建方法的原型为 : Task task(String name, Closure configureClosure)"
      5. println "任务描述, ${description}"
      6. }
      7. }
    • task原型

      1. public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
      2. Task task(String var1) throws InvalidUserDataException;
      3. Task task(Map<String, ?> var1, String var2) throws InvalidUserDataException;
      4. Task task(Map<String, ?> var1, String var2, Closure var3);
      5. Task task(String var1, Closure var2);
      6. Task task(String var1, Action<? super Task> var2);
      7. ...
      8. }
    4.2 多种方式访问任务
    1. 首先,我们创建的任务都会作为项目的一个属性,属性名就是任务名,所以可以直接通过任务名访问和操纵该任务

      1. task ex41CreateTask
      2. ex41CreateTask.doLast {
      3. println "hello world"
      4. }
    2. 其次,任务都是通过taskContainer创建的,其实taskContainer就是我们创建任务的集合,在project中,可以通过tasks属性访问taskContainer,所以我们可以以访问集合的方式创建我们的任务

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

      Task :app:ex41CreateTask
      ex41CreateTask doLast

      这里的[]指的不是map,而是a.getAt(b),对应的例子tasks[‘ex41CreateTask’]就是调用了tasks.getAt(‘ex41CreateTask’)

    3. 通过路径访问

      通过路径访问有两种方式

      1. get路径访问

        get的时候如果找不到该任务,会抛出UnknownTaskException异常

        1. task ex41CreateTask
        2. tasks['ex41CreateTask'].doLast {
        3. println tasks.getByPath(':app:ex41CreateTask')
        4. }

        Task :app:ex41CreateTask
        task ‘:app:ex41CreateTask’

      2. find路径访问

        find的时候如果找不到任务,返回null

        1. task ex41CreateTask
        2. tasks['ex41CreateTask'].doLast {
        3. println tasks.findByPath('ex41CreateTask')
        4. }

        Task :app:ex41CreateTask
        task ‘:app:ex41CreateTask’

        通过路径访问时,参数值可以是任务路径也可以是任务的名字。

        通过名字访问时,参数值只能是名字不能是路径。

    4.3 任务分组和描述

    任务的分组就是对任务的分类,便于我们对任务进行归类整理。

    任务的描述就是说明这个任务的作用。

    • 添加分组和描述

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

      ./gradlew tasks

      Build tasks

      assemble - Assemble main outputs for all the variants.
      assembleAndroidTest - Assembles all the Test applications.
      build - Assembles and tests this project.
      buildDependents - Assembles and tests this project and all projects that depend on it.
      buildNeeded - Assembles and tests this project and all projects it depends on.
      bundle - Assemble bundles for all the variants.
      clean - Deletes the build directory.
      cleanBuildCache - Deletes the build cache directory.
      compileDebugAndroidTestSources
      compileDebugSources
      compileDebugUnitTestSources
      compileReleaseSources
      compileReleaseUnitTestSources
      ex43GroupTask - 这是一个构建的引导任务

    4.5 任务的执行分析

    当我们执行tasks的时候,其实就是执行其拥有的actions列表,这个列表保存在task对象实例中actions成员变量中,其类型是一个list

    private List actions = new ArrayList();

    • 现在我们把task之前执行、task本身执行以及task之后执行分别称为doFirst、doSelf以及doLast,举个例子

      1. // 创建了task,所以task里的actions有了元素
      2. def Task myTask = task ex45CustomTask(type : CustomTask)
      3. // 把doFirst这个action放在actions开头
      4. myTask.doFirst {
      5. println "task执行之前执行do first"
      6. }
      7. // 把doLast这个action放在actions末尾
      8. myTask.doLast {
      9. println "task执行之后执行do last"
      10. }
      11. class CustomTask extends DefaultTask {
      12. @TaskAction
      13. def doSelf() {
      14. println "task 自己本身在执行in doSelf"
      15. }
      16. }

      Task :app:ex45CustomTask
      task执行之前执行do first
      task 自己本身在执行in doSelf
      task执行之后执行do last

    • AbstractTask

      1. public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
      2. // 执行task时,就是执行task中该actions集合
      3. private List actions;
      4. public Task doFirst(final String actionName, final Actionsuper Task> action) {
      5. this.hasCustomActions = true;
      6. if (action == null) {
      7. throw new InvalidUserDataException("Action must not be null!");
      8. } else {
      9. this.taskMutator.mutate("Task.doFirst(Action)", new Runnable() {
      10. public void run() {
      11. // 在list最前面添加
      12. AbstractTask.this.getTaskActions().add(0, AbstractTask.this.wrap(action, actionName));
      13. }
      14. });
      15. return this;
      16. }
      17. }
      18. public Task doLast(final String actionName, final Actionsuper Task> action) {
      19. this.hasCustomActions = true;
      20. if (action == null) {
      21. throw new InvalidUserDataException("Action must not be null!");
      22. } else {
      23. this.taskMutator.mutate("Task.doLast(Action)", new Runnable() {
      24. public void run() {
      25. // 在list最后面添加
      26. AbstractTask.this.getTaskActions().add(AbstractTask.this.wrap(action, actionName));
      27. }
      28. });
      29. return this;
      30. }
      31. }
      32. }

      当我们使用Task方法创建ex45CustomTask这个任务时,Gradle会解析所有其带有TaskAction标注的方法作为其Task执行的Action,然后通过Task的prependParallelSafeAction方法把该Action添加到actions List里。

      1. public void prependParallelSafeAction(Actionsuper Task> action) {
      2. if (action == null) {
      3. throw new InvalidUserDataException("Action must not be null!");
      4. } else {
      5. this.getTaskActions().add(0, this.wrap(action));
      6. }
      7. }
    4.6 任务排序
    • mustRunAfter

      1. task order1 {
      2. println "order1"
      3. doFirst {
      4. println "order1 doFirst"
      5. }
      6. }
      7. task order2 {
      8. println "order2"
      9. doFirst {
      10. println "order2 doFirst"
      11. }
      12. }
      13. order1.mustRunAfter order2

      Configure project :
      order1
      order2

      Task :order2 // order2先执行
      order2 doFirst

      Task :order1
      order1 doFirst // order1后执行

      BUILD SUCCESSFUL in 393ms
      2 actionable tasks: 2 executed

    • 去掉order1.mustRunAfter order2

      1. task order1 {
      2. println "order1"
      3. doFirst {
      4. println "order1 doFirst"
      5. }
      6. }
      7. task order2 {
      8. println "order2"
      9. doFirst {
      10. println "order2 doFirst"
      11. }
      12. }

      Configure project :
      order1
      order2

      Task :order1
      order1 doFirst

      Task :order2
      order2 doFirst

      order1比order2先执行

      或者使用shouldRunAfter,但有可能还是按原顺序执行

    • shouldRunAfter

      1. task order1 {
      2. println "order1"
      3. doFirst {
      4. println "order1 doFirst"
      5. }
      6. }
      7. task order2 {
      8. println "order2"
      9. doFirst {
      10. println "order2 doFirst"
      11. }
      12. }
      13. order1.shouldRunAfter order2

      Configure project :
      order1
      order2

      Task :order2
      order2 doFirst

      Task :order1
      order1 doFirst

    4.7 任务的启用和禁用
    • task.enabled

      order1.enabled = false
      

      开启enabled = false,没有执行task1

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle order1

      Configure project :
      order1
      order2

      关闭enabled,成功执行了task1

      BUILD SUCCESSFUL in 394ms
      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle order1

      Configure project :
      order1
      order2

      Task :order1
      order1 doFirst

    4.8 任务的OnlyIf断言

    断言就是一个条件表达式,Task有一个oflyIf方法,它接受一个闭包作为参数,如果该闭包返回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. doFirst {
      7. println "打应用宝的包 doFirst"
      8. }
      9. }
      10. task ex48BaiduRelease {
      11. println "打百度的包"
      12. doFirst {
      13. println "打百度的包 doFirst"
      14. }
      15. }
      16. task ex48HuaweiRelease {
      17. println "打华为的包"
      18. doFirst {
      19. println "打华为的包 doFirst"
      20. }
      21. }
      22. task ex48MiuiRelease {
      23. println "打小米的包"
      24. doFirst {
      25. println "打小米的包 doFirst"
      26. }
      27. }
      28. task build {
      29. group BasePlugin.BUILD_GROUP
      30. description "打渠道包"
      31. doFirst {
      32. println "打渠道包 doFirst"
      33. }
      34. }
      35. build.dependsOn ex48BaiduRelease,ex48HuaweiRelease,ex48MiuiRelease,ex48QQRelease
      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 == buildApps || BUILD_APPS_ALL == buildApps) {
      41. execute = true
      42. } else {
      43. execute = false
      44. }
      45. } else {
      46. execute = true
      47. }
      48. execute
      49. }
      50. ex48QQRelease.onlyIf {
      51. def execute = false
      52. if (project.hasProperty("build_apps")) {
      53. Object buildApps = project.property("build_apps")
      54. if (BUILD_APPS_SHOUFA == buildApps || BUILD_APPS_ALL == buildApps) {
      55. execute = true
      56. } else {
      57. execute = false
      58. }
      59. } else {
      60. execute = true
      61. }
      62. execute
      63. }
      64. ex48HuaweiRelease.onlyIf {
      65. def execute = false
      66. if (project.hasProperty("build_apps")) {
      67. Object buildApps = project.property("build_apps")
      68. if (BUILD_APPS_EXCLUDE_SHOUFA == buildApps || BUILD_APPS_ALL == buildApps) {
      69. execute = true
      70. } else {
      71. execute = false
      72. }
      73. } else {
      74. execute = true
      75. }
      76. execute
      77. }
      78. ex48MiuiRelease.onlyIf {
      79. def execute = false
      80. if (project.hasProperty("build_apps")) {
      81. Object buildApps = project.property("build_apps")
      82. if (BUILD_APPS_EXCLUDE_SHOUFA == buildApps || BUILD_APPS_ALL == buildApps) {
      83. execute = true
      84. } else {
      85. execute = false
      86. }
      87. } else {
      88. execute = true
      89. }
      90. execute
      91. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -Pbuild_apps=all build

      Configure project :
      打应用宝的包
      打百度的包
      打华为的包
      打小米的包

      Task :ex48BaiduRelease
      打百度的包 doFirst

      Task :ex48HuaweiRelease
      打华为的包 doFirst

      Task :ex48MiuiRelease
      打小米的包 doFirst

      Task :ex48QQRelease
      打应用宝的包 doFirst

      Task :build
      打渠道包 doFirst

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle -Pbuild_apps=shoufa build

      Configure project :
      打应用宝的包
      打百度的包
      打华为的包
      打小米的包

      Task :ex48BaiduRelease
      打百度的包 doFirst

      Task :ex48QQRelease
      打应用宝的包 doFirst

      Task :build
      打渠道包 doFirst

      打包命令为gradle -Pbuild_apps=shoufa build时,比gradle -Pbuild_apps=all build,少执行了小米和华为两个打包任务,应为它们的onlyIf表达式返回false

    4.9 任务规则

    我们创建的任务都在TaskContainer里,由其进行管理。所以当我们访问任务的时候都是通过TaskContainer进行访问,二TaskContainer又是一个NamedDomainObjectCollection,所以说我们的任务规则是NamedDomainObjectCollection的规则。

    NamedDomainObjectCollection是一个具有唯一名字的域对象的集合,它里面所有的元素都有一个唯一不变的名字,该名字是String类型,所以我们可以通过名字获取该元素,比如我们通过任务名获取该任务

    我们提供的任务名在NamedDomainObjectCollection中可能并不存在,这时候就会调用我们添加的规则来处理这种异常情况。如理:

    • addRule

      1. tasks.addRule("对该规则的一个描述,便于调试") { String taskName ->
      2. println "addRule 开始执行"
      3. task(taskName) {
      4. println "该${taskName}不存在,请查证后再执行"
      5. }
      6. }
      7. task ex49RuleTask {
      8. println "ex49RuleTask 开始执行"
      9. dependsOn missTask
      10. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex49RuleTask

      Configure project :
      ex49RuleTask 开始执行
      addRule 开始执行
      该missTask不存在,请查证后再执行

    • 如果不在addRule中创建task,则程序崩溃

      1. tasks.addRule("对该规则的一个描述,便于调试") { String taskName ->
      2. println "addRule 开始执行"
      3. }
      4. task ex49RuleTask {
      5. println "ex49RuleTask 开始执行"
      6. dependsOn missTask
      7. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex49RuleTask

      Configure project :
      ex49RuleTask 开始执行
      addRule 开始执行

      FAILURE: Build failed with an exception.

      • Where:
        Build file ‘/home/wangzhiping/GradleProject/build.gradle’ line: 7

      • What went wrong:
        A problem occurred evaluating root project ‘GradleProject’.

      Could not get unknown property ‘missTask’ for task ‘:ex49RuleTask’ of type org.gradle.api.DefaultTask.

      • Try:

      Run with --stacktrace option to get the stack trace.
      Run with --info or --debug option to get more log output.
      Run with --scan to get full insights.

      • Get more help at https://help.gradle.org

      BUILD FAILED in 401ms

    • 如果依赖的task存在,addRule回调不会执行

      1. tasks.addRule("对该规则的一个描述,便于调试") { String taskName ->
      2. println "addRule 开始执行"
      3. }
      4. task missTask {
      5. println "missTask 开始执行"
      6. }
      7. task ex49RuleTask {
      8. println "ex49RuleTask 开始执行"
      9. dependsOn missTask
      10. }

      wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex49RuleTask

      Configure project :
      missTask 开始执行
      ex49RuleTask 开始执行

    • 函数原型

      1. public class DefaultNamedDomainObjectCollection extends DefaultDomainObjectCollection implements NamedDomainObjectCollection, MethodMixIn, PropertyMixIn {
      2. public Rule addRule(Rule rule) {
      3. this.rules.add(rule);
      4. return rule;
      5. }
      6. ...
      7. }
    • findByName

      1. public T findByName(String name) {
      2. T value = this.findByNameWithoutRules(name);
      3. if (value != null) {
      4. return value;
      5. } else {
      6. ProviderInternal provider = this.index.getPending(name);
      7. if (provider != null) {
      8. provider.getOrNull();
      9. return this.index.get(name);
      10. } else {
      11. // 要执行的task不存在时,执行applyRules()
      12. return !this.applyRules(name) ? null : this.findByNameWithoutRules(name);
      13. }
      14. }
      15. }

      从findByName中可以看出,如果依赖的任务存在,findByName会直接返回,不存在会执行rules

    5. Gradle插件

    Gradle本身提供一些基本的概念和整体核心框架,其他用于描述真实使用场景逻辑的都以插件扩展,这样的设计可以抽象的方式提供一个核心框架,其他具体的功能和业务都通过插件的扩展的方式来实现,比如构建java应用,就是通过java插件来实现的。

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

      什么是二进制插件?二进制插件就是实现了org.gradle.api.Plugin接口的插件,它们可以有plugin id。

      apply plugin:'java'
      

      这句代码就把java插件应用到我们项目中了,其中’java’是Java插件的plugin id,它是唯一的。对于Gradle自带的核心插件都有一个容易记的短名,称其为plugin id,比如这里的java,其实它对应的类型是org.gradle.api.plugins.JavaPlugin,所以通过该类型我们也可以应用这个插件:

      apply plugin:org.gradle.api.plugins.JavaPlugin
      

      又因为包org.gradle.api.plugins是默认导入的,所以我们可以去掉包名直接改为

      apply plugin:JavaPlugin
      

      以上三种写法是等价的,不过第一种用的最多,因为它容易记住,第二种写法一般适用于我们在build文件中自定义的插件,也就是脚本插件。

      二进制插件一般被打包在一个jar里面独立发布,比如我们自定义的插件,在发布的时候我们也可以指定其plugin id,这个plugin id最好是一个全限定名称,就像包名一样,这样发布的插件plugin id就不会重复,比如org.flysnow.tools.plugin.xxx

    • 应用脚本插件

      version.gradle

      1. ext {
      2. versionName = '1.0.0'
      3. versionCode = 1
      4. }

      build.gradle

      1. apply from : 'version.gradle'
      2. task ex52PrintTask {
      3. println "app version is ${versionName}"
      4. println "app version code is ${versionCode}"
      5. }

      其实这不能算是一个插件,只能算是一个脚本。应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它用的是from关键字,后面紧跟着一个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用html url。

      虽然它不是一个正真的插件,但是不能忽视它的作用,它是脚本文件模块化的基础,我们可以把庞大的脚本文件,进行分块,分段整理,拆分成一个个公用、职责分明的文件,然后使用apply from来引用它们,比如我们可以把常用的函数放在一个个utils.gradle文件里,供其他脚本文件引用。示例中我们把app各个版本名称和版本号单独放在一个脚本文件里,清晰、简单。我们也可以使用自动化对该文件自动处理,生成版本。

    • apply方法的其他用法

      Project.apply()方法有3种方式,它们是以接受参数的不同区分的。我们上面用的是接受一个Map类型参数的方式。此外还有两种。

      1. public abstract class ProjectDelegate public constructor() : org.gradle.api.Project {
      2. public open fun apply(closure: groovy.lang.Closure<*>): kotlin.Unit { /* compiled code */ }
      3. public open fun apply(options: kotlin.collections.Map<kotlin.String, *>): kotlin.Unit { /* compiled code */ }
      4. public open fun apply(action: org.gradle.api.Action<in org.gradle.api.plugins.ObjectConfigurationAction>): kotlin.Unit { /* compiled code */ }
      5. }

      闭包的方式如下:

      1. apply {
      2. plugin 'java'
      3. }

      该闭包被用来配置一个ObjectConfigurationAction对象,所以可以在闭包里使用ObjectConfigurationAction对象的方法、属性等进行配置。

      • ObjectConfigurationAction
      1. public interface ObjectConfigurationAction {
      2. ObjectConfigurationAction to(Object... var1);
      3. ObjectConfigurationAction from(Object var1);
      4. ObjectConfigurationAction plugin(Class<? extends Plugin> var1);
      5. ObjectConfigurationAction type(Class<?> var1);
      6. ObjectConfigurationAction plugin(String var1);
      7. }

      plugin 'java’对应ObjectConfigurationAction plugin(Class var1);

      Action的方式:

      1. apply(new Action<ObjectConfigurationAction>() {
      2. @Override
      3. void execute(ObjectConfigurationAction objectConfigurationAction) {
      4. objectConfigurationAction.plugin('java')
      5. }
      6. })

      对应public open fun apply(action: org.gradle.api.Action

    5.3 自定义插件
    1. apply plugin : ExCustomPlugin
    2. class ExCustomPlugin implements Plugin<Project> {
    3. @Override
    4. void apply(Project project) {
    5. project.task('ex53CustomTask') {
    6. println "这是一个通过自定义插件创建的task"
    7. }
    8. }
    9. }

    wangzhiping@wangzhiping-PC:~/GradleProject$ gradle ex53CustomTask
    Starting a Gradle Daemon (subsequent builds will be faster)

    Configure project :
    这是一个通过自定义插件创建的task

    自定义插件必须要实现Plugin接口,这个接口只有一个apply方法,该方法在插件被应用时调用,所以我们可以实现这个方法,做我们想做的事情,比如这里创建一个名称为ex53CustomTask的任务。

    这个插件定义在build脚本里,只能是自己的项目用,如果我们想开发一个独立的插件给所有人使用,应该怎么做呢?这就需要单独创建一个Groovy工程作为开发自定义插件的工程了。

    • groovy模块目录层级

      1. ── buildSrc
      2. │   ├── build
      3. │   │   ├── classes
      4. │   │   │   └── groovy
      5. │   │   │   └── main
      6. │   │   │   └── com
      7. │   │   │   └── henyiwu
      8. │   │   │   └── gradle
      9. │   │   │   ├── Ex53CustomPlugin$_apply_closure1$_closure2.class
      10. │   │   │   ├── Ex53CustomPlugin$_apply_closure1.class
      11. │   │   │   └── Ex53CustomPlugin.class
      12. │   │   ├── generated
      13. │   │   │   └── sources
      14. │   │   │   └── annotationProcessor
      15. │   │   │   └── groovy
      16. │   │   │   └── main
      17. │   │   ├── libs
      18. │   │   │   └── buildSrc.jar
      19. │   │   ├── resources
      20. │   │   │   └── main
      21. │   │   │   └── META-INF
      22. │   │   │   └── gradle-plugins
      23. │   │   │   └── com.henyiwu.gradle.Ex53CustomPlugin.properties
      24. │   │   ├── source-roots
      25. │   │   │   └── buildSrc
      26. │   │   │   └── source-roots.txt
      27. │   │   └── tmp
      28. │   │   ├── compileGroovy
      29. │   │   │   └── groovy-java-stubs
      30. │   │   └── jar
      31. │   │   └── MANIFEST.MF
      32. │   ├── build.gradle
      33. │   └── src
      34. │   └── main
      35. │   ├── groovy
      36. │   │   └── com
      37. │   │   └── henyiwu
      38. │   │   └── gradle
      39. │   │   └── Ex53CustomPlugin.groovy
      40. │   └── resources
      41. │   └── META-INF
      42. │   └── gradle-plugins
      43. │   └── com.henyiwu.gradle.Ex53CustomPlugin.properties
      44. ├── gradle
      45. │   └── wrapper
      46. │   ├── gradle-wrapper.jar
      47. │   └── gradle-wrapper.properties
      48. ├── gradle.properties
      49. ├── gradlew
      50. ├── gradlew.bat
      51. ├── local.properties
      52. └── settings.gradle

    • Ex53CustomPlugin.groovy

      1. class Ex53CustomPlugin implements Plugin<Project>{
      2. @Override
      3. void apply(Project project) {
      4. project.task('ex53CustomTask') {
      5. println "这是一个通过自定义插件创建的task"
      6. doLast {
      7. println "ex53CustomTask do Last"
      8. }
      9. }
      10. }
      11. }
    • resources/META-INF/gradle-plugins/{pluginId}.properties

      这里文件名对应com.henyiwu.gradle.Ex53CustomPlugin.properties

      implementation-class=com.henyiwu.gradle.Ex53CustomPlugin
      
    • buildSrc/build.gradle

      1. apply plugin: 'groovy'
      2. dependencies {
      3. // 引用gradleApi(),才能够检测到Plugin插件
      4. implementation gradleApi()
      5. implementation localGroovy()
      6. }
    • 运行结果

      1. wangzhiping@wangzhiping-PC:~/AndroidStudioProjects/GradleTEst$ gradle ex53CustomTask
      2. > Configure project :app
      3. 这是一个通过自定义插件创建的task
      4. > Task :app:ex53CustomTask
      5. ex53CustomTask do Last
      6. BUILD SUCCESSFUL in 984ms
    6. Java/Kotlin Gradle插件

    Java/Kotlin gradle插件,指的是org.jetbrains.kotlin.android或者java插件,它们帮助我们编译源文件,进行单元测试,打包发布等。

    6.1 引入插件
    • app/build.gradle

      1. plugins {
      2. // 这是一个app模块
      3. id 'com.android.application'
      4. // 引入kotlin插件
      5. id 'org.jetbrains.kotlin.android'
      6. }
    6.2 Java/Kotlin插件约定的项目结构

    只有我们遵循这些项目结构约定,插件才能找到我们的Java类,找到我们的资源进行编译。

    1. ├── app
    2. │   ├── build.gradle
    3. │   └── src
    4. │   ├── test
    5. │   ├── main
    6. │   │   ├── AndroidManifest.xml
    7. │   │   ├── java
    8. │   │   │   └── com
    9. │   │   │   └── example
    10. │   │   │   └── myapplication
    11. │   │   │   └── MainActivity.kt
    12. │   │   └── res
    13. ├── build.gradle

    默认情况下,src/main/Java为我们的项目源代码存放位置:src/main/res为要打包的文件存放目录,比如一些Properties配置文件和图片等。

    main和test是插件为我们内置的两个源代码集合,我们可以添加新的源代码集合,例如添加一个vip文件夹,并且依葫芦画瓢照着main文件夹新建java、res文件夹,然后在app/build.gradle中添加:

    1. android {
    2. sourceSets {
    3. vip {
    4. }
    5. }
    6. }

    新建源代码集合,默认的路径是src/sourceName/java、src/sourceName/res

    6.3 如何配置第三方依赖

    想要使用这些第三方依赖,你要告诉Gradle如何找到这些依赖,也就是我们要讲的依赖配置。一般情况下都是我们都是从仓库找我们要的jar包,在gradle中要配置一个仓库的jar依赖,首先我们得告诉gradle我们要使用什么类型的仓库,这些仓库的位置在哪里,这样gradle知道从哪里去搜寻我们的jar:

    • build.gradle

      1. buildscript {
      2. repositories {
      3. mavenCenter()
      4. }
      5. }
      6. allprojects {
      7. repositories {
      8. mavenCenter()
      9. }
      10. }

      以上脚本配置了一个Maven中心库,告诉gradle可以在Maven中心搜寻我们依赖的jar包。

      有了仓库,就需要告诉Gradle我们需要依赖什么。

      1. dependencies {
      2. implementation 'com.squareup.retrofit2:retrofit:2.9.0'
      3. }

      它完整的写法应该是

      1. dependencies {
      2. implementation group:'com.squareup.retrofit2',name:'retrofit',version:'2.9.0'
      3. }
    • api和implementation的区别

      1. implementation
      2. Gradle 会将依赖项添加到编译类路径,并将依赖项打包到构建输出。不过,当您的模块配置 implementation 依赖项时,会让 Gradle 了解您不希望该模块在编译时将该依赖项泄露给其他模块。也就是说,其他模块只有在运行时才能使用该依赖项。
      3. 使用此依赖项配置代替 api 或 compile(已弃用)可以显著缩短构建时间,因为这样可以减少构建系统需要重新编译的模块数。例如,如果 implementation 依赖项更改了其 API,Gradle 只会重新编译该依赖项以及直接依赖于它的模块。大多数应用和测试模块都应使用此配置。
      4. api:
      5. Gradle 会将依赖项添加到编译类路径和构建输出。当一个模块包含 api 依赖项时,会让 Gradle 了解该模块要以传递方式将该依赖项导出到其他模块,以便这些模块在运行时和编译时都可以使用该依赖项。
      6. 此配置的行为类似于 compile(现已弃用),但使用它时应格外小心,只能对您需要以传递方式导出到其他上游消费者的依赖项使用它。 这是因为,如果 api 依赖项更改了其外部 API,Gradle 会在编译时重新编译所有有权访问该依赖项的模块。 因此,拥有大量的 api 依赖项会显著增加构建时间。除非要将依赖项的 API 公开给单独的模块,否则库模块应改用 implementation 依赖项。
    • 引用一个本地module,例如我们新建一个mylibrary

    • setting.gradle

      include ':mylibrary'
      
    • app/build.gradle

      1. dependencies {
      2. implementation project(':mylibrary')
      3. }

      然后我们就可以在app模块使用mylibrary的类了

    • 引入本地jar包、arr包

      1. dependencies {
      2. api fileTree(dir: 'libs', include:['*.jar'])
      3. api fileTree(dir: 'libs', include:['*.aar']
      4. }

      这样配置后,libs文件下的扩展名为jar的都会被依赖,这里用到的是Project的fileTree()方法

    6.4 如何构建一个java项目

    在gradle中,执行任何操作都是任务驱动的,构建java项目也不例外。java插件为我们提供了很多任务,通过运行它们来达到我们构建java项目的目的。最常用的是build任务,运行它会构建你的整个项目,我们可以通过gradle build来运行,然后gradle就会开始编译源码文件,处理资源文件,打成jar包,然后编译测试用例代码,处理测试资源,最后运行单元测试。

    运行build后大致的task执行流程
    compileJava -> processResource -> classes -> jar -> assemble -> compileTestJava -> processTestResource -> testClasses
    -> test -> check -> build

    最后在build/libs生成jar包

    • clean

      这个是删除build目录以及其他构建生成的文件。如果编译中有问题,可以先clean再重新编译。

    • Assemble

      该任务不会执行单元测试,只会编译和打包。这个任务在android里也有,执行它可以打apk包,所以它不止会打jar包,其实它也算是一个引导类任务,根据不同的项目打不同的包。

    • check

      只会执行单元测试,有时候还会做一些质量检查,不会打jar包。

    6.5 源码集合SourceSet的概念

    SourceSet-源代码合集-源集,是java插件用来描述和管理源代码及其资源的一个抽象概念,是一个java源代码文件和资源文件的集合。通过源集,我们可以非常方便地访问源代码目录,设置源集的属性,更改源集的java目录或者资源文件等。

    有了源集,我们就能针对不同的业务和应用对我们源代码进行分组,比如用于主要业务产品的main以及用于单元测试的test,责任分明、清晰。它们两个也是java插件默认内置的两个标准集。

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

    • 遍历源代码集合

      1. sourceSets.all {
      2. println "源代码集:name: ${name}"
      3. }
    • 输出源代码集路径

      1. sourceSets.all {
      2. println "源代码集:srcDirs: ${java.srcDirs}"
      3. }

      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/androidTest/java]
      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/androidTestDebug/java]
      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/debug/java]
      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/main/java]
      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/release/java]
      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/test/java]
      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/testDebug/java]
      源代码集:srcDirs: [/Users/xx/AndroidStudioProjects/GradleApplication/app/src/testRelease/java]

    • 源代码集合的资源文件

      1. sourceSets.all {
      2. println "源代码集:srcDirs: ${resources}"
      3. }

      source=[src/androidTest/resources]
      source=[src/androidTestDebug/resour
      source=[src/debug/resources]
      source=[src/main/resources]
      source=[src/release/resources]
      source=[src/test/resources]
      source=[src/testDebug/resources]
      source=[src/testRelease/resources]

    • 更改源代码集路径

      1. def modules = src_root.list().toList().stream()
      2. .filter(
      3. new Predicate<String>() {
      4. @Override
      5. boolean test(String name) {
      6. return name == 'main' || (name.startsWith('xx_') && new File(src_root, name).isDirectory())
      7. }
      8. }).collect(Collectors.toList())
      9. def moduleSrc = modules.stream()
      10. .map(
      11. new Function() {
      12. @Override
      13. Object apply(Object moduleName) {
      14. return ['src', moduleName, 'java'].join('/')
      15. }
      16. })
      17. .collect(Collectors.toList())
      18. def moduleRes = p_modules.stream()
      19. .map(
      20. new Function() {
      21. @Override
      22. Object apply(Object moduleName) {
      23. return ['src', moduleName, 'res'].join('/')
      24. }
      25. }).collect(Collectors.toList())
      26. sourceSets {
      27. main {
      28. // srcDirs是一个集合,以上代码表示指定java源代码集合为src下,main文件夹和xx_开头的文件夹
      29. java.srcDirs = moduleSrc
      30. // 同理,资源文件集
      31. res.srcDirs = moduleRes
      32. }
      33. }

    6.6 java插件添加的任务

    java插件主要包含以下task

    • java插件添加的通用任务

      1. 任务名称 类型 描述
      2. compileJava JavaCompile 使用 javac 编译 Java 源文件
      3. processResources Copy 把资源文件 copy 到生产的资源文件目录中
      4. classes Task 组装产生的类和资源文件目录
      5. compileTestJava JavaComplie 使用 javac 编译测试的 Java 源文件
      6. ProcessTestResource Copy 把测试的资源文件 copy 到生产的资源文件目录中
      7. testClass Task 组装产生的测试类和资源文件目录
      8. jar Jar 组装 Jar 文件
      9. javadoc Javadoc 使用 javadoc 生成 Java API 文档
      10. test Test 使用 Junit 或 TestNG 进行单元测试
      11. uploadArchives Upload 上传包含 Jar 的构建,用 archives{} 闭包进行配置
      12. clean Delete 清理构建生成的目录文件
      13. cleanTaskName Delete 删除指定任务生成的文件,比如 cleanJar 会删除 Jar 任务生成的文件
    • 源集任务

      1. 任务名称 类型 描述
      2. compileSourceaSetJava JavaCompile 使用 javac 编译指定源集的 Java 源代码
      3. processSourceSetResources Copy 把指定源集的资源文件复制到生产文件的资源目录中
      4. sourceSetClasses Task 组装给定源集类和资源文件目录

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

    6.7 Java插件添加的属性
    1. 属性名称 类型 描述
    2. sourceSets SourceSetContainer Java 项目的源集,可以访问和配置源集
    3. sourceCompatiblity JavaVersion 编译 Java 源文件使用的版本
    4. targeCompatiblity JavaVersion 编译生成的类的 Java 版本
    5. archivesBaseName String 打包 Java 或者 Zip 文件的名字
    6. manifest Manifest 用于访问或者配置 manifest 清单文件
    7. libsDir File 存放生成的类库目录
    8. distsDir File 存放生成发布的文件目录
    6.8 多项目构建

    多项目构建,其实就是多个gradle项目一起构建,它们一起通过Settings.gradle配置管理。每个项目都有一个build文件对该项目进行配置,然后采用项目依赖,就可以实现多项目协作,这对于大项目开发,进行模块化非常有用。

    • 目录结构

      1. ├── app
      2. │ ├── build.gradle
      3. │ ├── libs
      4. │ ├── proguard-rules.pro
      5. │ └── src
      6. │ ├── androidTest
      7. │ ├── main
      8. │ └── test
      9. ├── build.gradle
      10. ├── mylibrary
      11. │ ├── build.gradle
      12. │ └── src
      13. │ ├── androidTest
      14. │ ├── main
      15. │ └── test
      16. └── settings.gradle

    • settings.gradle

      1. rootProject.name = "GradleApplication"
      2. include ':app'
      3. include ':mylibrary'
    • app/build.gradle

      1. dependencies {
      2. implementation project(':mylibrary')
      3. }
    • subprojects

      在根目录中使用,遍历所有子项目

      1. subprojects {
      2. apply plugin : 'kotlin-android'
      3. }

      例如,这样就让所有子项目都依赖了kotlin-android插件

    • buildscript、allprojects、subprojects的区别

      1. buildScript块的repositories主要是为了Gradle脚本自身的执行,获取脚本依赖插件。也就是说,buildScript是用来加载Gradle脚本自身需要使用的资源,可以声明的资源包括依赖项、第三方插件、maven仓库地址等。
      2. allprojects块的repositories用于多项目构建,为所有项目提供共同的所需依赖包。而子项目可以配置自己的repositories以获取自己独需的依赖包。
      3. subprojects块的repositories用于配置这个项目的子项目。使用多模块项目时,不同模块之间有相同的配置,导致重复配置,可以将相同的部分抽取出来,使用配置注入的技术完成子项目的配置。根项目就像一个容器, subprojects 方法遍历这个容器的所有元素并且注入指定的配置。allprojects是对所有project的配置,包括Root Project。而subprojects是对所有Child Project的配置。
    6.9 如何发布构件

    基于gradle6.7.1

    1. apply plugin: 'groovy'
    2. apply plugin: 'kotlin'
    3. apply plugin: 'maven-publish'
    4. apply plugin: 'maven'
    5. dependencies {
    6. implementation gradleApi()
    7. implementation localGroovy()
    8. ....
    9. }
    10. repositories {
    11. mavenCentral()
    12. google()
    13. }
    14. buildscript {
    15. ext.kotlin_version = '1.4.10'
    16. repositories {
    17. mavenCentral()
    18. }
    19. dependencies {
    20. classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    21. }
    22. }
    23. compileKotlin {
    24. kotlinOptions {
    25. jvmTarget = "1.8"
    26. }
    27. }
    28. compileTestKotlin {
    29. kotlinOptions {
    30. jvmTarget = "1.8"
    31. }
    32. }
    33. def MAVEN_PATH = "$MAVEN_PATH"
    34. def ARTIFACT_ID = 'xxxxxx'
    35. def VERSION_NAME = '1.0.0'
    36. def GROUP_ID = "xxxxxx"
    37. uploadArchives {
    38. repositories {
    39. mavenDeployer {
    40. repository(url: MAVEN_PATH) {
    41. authentication(userName: "userName", password: "password")
    42. }
    43. pom.project {
    44. groupId GROUP_ID
    45. artifactId ARTIFACT_ID
    46. version VERSION_NAME
    47. packaging 'aar'
    48. }
    49. }
    50. }
    51. }

    同步后,运行publish任务即可,该任务属于maven-publish插件

    7. Android Gradle 插件
    7.1 Android Gradle 插件简介

    从Gradle的角度来看,Android插件其实就是Gradle的一个第三方插件,它是由Google的Android团队开发的

    7.2 Android Gradle 插件分类
    1. App插件id:com.android.application
    2. Library插件id:com.android.library
    3. Test插件id:com.android.test

    通过应用以上三种不同的插件,可以配置项目为一个Android App工程,或者Library工程,或者是一个Android Test测试工程。

    7.3 应用Android Gradle 插件

    应用一个插件,必须知道它的id,并且配置它们的依赖classpath。

    • 根目录下build.gradle

      1. buildscript {
      2. repositories {
      3. // jcenter()仓库,目前已过期
      4. jcenter()
      5. }
      6. dependencies {
      7. classpath "com.android.tools.build:gradle:4.0.2"
      8. }
      9. }
    • app/build.gradle

      1. apply plugin : 'com.android.application'
      2. android {
      3. }
    7.4 Android Gradle 工程示例

    Android Gradle 插件继承于Java插件,具有Java插件的特性,它也需要在Setting文件里通过include配置包含的子工程,也需要应用Android插件等。

    • app目录结构

      1. ├── app
      2. │   ├── build.gradle
      3. │   ├── libs
      4. │   ├── proguard-rules.pro
      5. │   └── src
      6. │   ├── androidTest
      7. │   │   └── java
      8. │   ├── main
      9. │   │   ├── AndroidManifest.xml
      10. │   │   ├── java
      11. │   │   └── res
      12. │   └── test
      13. │   └── java
      14. ├── build.gradle

      main文件夹与Java文件结构相比,多了AndroidManifest.xml和res这两个属于Android特有的文件目录。

    • app/build.gradle

      1. apply plugin: 'com.android.application'
      2. android {
      3. defaultConfig {
      4. applicationId application_id
      5. versionCode version_code
      6. versionName version_name
      7. }
      8. signingConfigs {
      9. // 签名相关配置
      10. }
      11. buildTypes {
      12. debug {
      13. }
      14. release {
      15. }
      16. }
      17. productFlavors {
      18. publish {
      19. }
      20. develop {
      21. buildConfigField "boolean", "IS_RELEASE_PACKAGE", "false"
      22. }
      23. }
      24. aaptOptions {
      25. cruncherEnabled = false
      26. useNewCruncher = false
      27. }
      28. compileOptions {
      29. sourceCompatibility JavaVersion.VERSION_1_8
      30. targetCompatibility JavaVersion.VERSION_1_8
      31. }
      32. kotlinOptions {
      33. jvmTarget = '1.8'
      34. }
      35. buildFeatures {
      36. viewBinding true
      37. }
      38. }
      39. dependencies {
      40. implementation fileTree(dir: 'libs', include: ['*.jar'])
      41. implementation fileTree(dir: 'libs', include: ['*.aar'])
      42. implementation project(":lib-center")
      43. ...
      44. }
      45. project.afterEvaluate { project ->
      46. project.android.buildTypes.all { buildType ->
      47. println(">>>>>> 当前编译的签名信息 = ${buildType.signingConfig.toString()}")
      48. }
      49. }
      50. println(">>>>>>当前使用的gradle版本=" + project.gradle.gradleVersion)

      Android Gradle 工程的配置,都是在android{}中,这是唯一的入口,通过它,可以对Android Gradle工程进行自定义的配置,具体的实现是com.android.build.gradle.AppExtension

    7.4.1 compileSdkVersion

    编译Android工程的SDK版本,原型是一个compileSdkVersion方法

    1. public void compileSdkVersion(String version) {
    2. checkWritability()
    3. this.target = version;
    4. }
    • build.gradle

      1. android {
      2. compileSdkVersion Integer.parseInt("${CompileSdkVersion}")
      3. }
    • 此外,还有一个setCompileSdkVersion

      1. public void setCompileSdkVersion(int apiLevel) {
      2. compileSdkVersion(apiLevel)
      3. }
    • 使用方法

      1. android.compileSdkVersion = 23
      2. android.compileSdkVersion = 'android-23'
    7.4.2 buildToolsVersion

    buildToolsVersion "23.0.1"表示我们使用的android构件工具的版本,我们可以在Android SDK目录里看到,它是一个工具包,包括aapt、dex等工具,它的原型也是一个方法:

    1. public void buildToolsVersion(String version) {
    2. checkWritability();
    3. buildToolsVersion = FullRevision.parseRevision(version);
    4. }
    5. @Override
    6. public String getBuildToolsVersion() {
    7. return buildToolsRevision.toString();
    8. }

    从源代码可以看出,我们可以通过buildToolsVersion方法赋值,也可以通过android.buildToolsVersion属性读写它的值。

    7.4.3 defaultConfig

    defaultConfig是默认的配置,它是一个ProductFlavor。ProductFlavor允许我们根据不同的情况生成多个不同的APK包,比如多渠道打包。如果不针对我们自定义的ProductFlavor单独配置的话,会为这个ProductFlavor使用默认的defaultConfig的配置。

    • build.gradle

      1. defaultConfig {
      2. // 包名
      3. applicationId application_id
      4. // 版本号
      5. versionCode version_code
      6. // 版本名
      7. versionName version_name
      8. // 最低支持的android系统api
      9. minSdkVersion Integer.parseInt("${MinSdkVersion}")
      10. // 基于哪个android版本开发的
      11. targetSdkVersion Integer.parseInt("${TargetSdkVersion}")
      12. }

      以上所有类都对应ProductFlavor类里的方法或属性。

    7.4.4 buildTypes

    BuildTypes是一个NamedDomainObjectContainer类型,是一个域对象,和sourceSet一样。buildTypes里有release、debug等。我们可以在buildTypes{}里增加任意多个我们需要构建的类型,Gradle会帮我们自动创建一个对应的buildType,名字就是我们定义的名字。

    • build.gradle

      1. buildTypes {
      2. debug {
      3. ...
      4. }
      5. release {
      6. debuggable = false
      7. jniDebuggable false
      8. renderscriptDebuggable false
      9. renderscriptOptimLevel 3
      10. //混淆
      11. minifyEnabled true
      12. //Zipalign优化
      13. zipAlignEnabled true
      14. // 移除无用的resource文件
      15. shrinkResources true
      16. // 两个混淆文件
      17. // 1. getDefaultProguardFile('proguard-android.txt'),默认的文件,在android-sdk/tools/proguard/目录下,主要是基本组件的防混淆,例如四大组件。
      18. // 2. proguard-rules.pro我们自己写的混淆文件
      19. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
      20. signingConfig signingConfigs.release
      21. }
      22. }

    7.5 Android Gradle 任务

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

    8. 自定义Android Gradle 工程
    8.1 defaultConfig 默认配置

    DefaultConfig是Android对象中的一个配置块,负责定义所有的默认配置,它是一个ProductFlavor,如果ProductFlavor没有被特殊定义配置的话,默认就会使用defaultConfig{}块指定的配置,比如报名、版本号、版本名等。

    • 一个基本的defaultConfig配置

      1. android {
      2. compileSdkVersion 23
      3. buildToolsVersion "23.0.1"
      4. defaultConfig {
      5. applicationId "xxx.xxx.xxx"
      6. minSdkVersion 19
      7. targetSdkVersion 23
      8. versionCode 1
      9. versionName "1.0"
      10. }
      11. }

      以上配置可以满足一个app的基本开发

    8.1.1 applicationId

    applicationId是ProductFlavor的一个属性,用于指定生成app的包名,默认情况下是null,那么构建时,会从我们的androidManifest.xml文件中读取,manifest标签package属性值。

    8.1.2 minSdkVersion

    minSdkVersion是ProductFlavor的一个方法,它可以指定我们的app最低支持的Android操作系统,其对应的值是Android SDK的api level。

    8.1.3 targetSdkVersion

    这个用于配制我们是基于哪个Android SDK开发,它的可选值与minSdkVersion一样,没有配置的时候也会从androidManifest.xml中读取。

    8.1.4 versionCode

    也是ProductFlavor中的一个属性,用于配制app内部版本号,是一个常数值,没有配置的时候从androidManifest.xml读取。

    8.1.5 versionName

    和versionCode相似,用于配制app的版本名称,其值是字符串,让用户知道当前的app版本。

    8.1.8 signingConfig

    配制默认签名信息,对生成的app签名,也是ProductFlavor的一个属性。

    • signingConfig

      1. signingConfigs {
      2. Properties keyProperties = new Properties()
      3. keyProperties.load(new FileInputStream(file("${project.rootDir}/buildcfg/xxx/keystore.properties")))
      4. debug {
      5. storeFile file("${project.rootDir}" + keyProperties["debugStoreFile"])
      6. keyAlias keyProperties["debugKeyAlias"]
      7. storePassword keyProperties["debugStorePassword"]
      8. keyPassword keyProperties["debugKeyPassword"]
      9. }
      10. release {
      11. storeFile file("${project.rootDir}" + keyProperties["releaseStoreFile"])
      12. keyAlias keyProperties["releaseKeyAlias"]
      13. storePassword keyProperties["releaseStorePassword"]
      14. keyPassword keyProperties["releaseKeyPassword"]
      15. }
      16. }
    • keystore.properties

      1. releaseStoreFile=/buildcfg/xxxx/xxxx.keystore
      2. releaseStorePassword=xxxx
      3. releaseKeyAlias=xxxx
      4. releaseKeyPassword= xxxx
      5. debugStoreFile=/buildcfg/xxxx/xxxx.keystore
      6. debugStorePassword= xxxx
      7. debugKeyAlias=xxxx
      8. debugKeyPassword= xxxxx
    8.1.9 proguardFile

    用于配制app proguard混淆所使用的ProGuard配置文件,它是ProductFlavor的一个方法,接受一个文件作为参数。

    8.1.10 proguardFiles

    同上,配制多个混淆文件。

    8.2 配制签名信息

    一个app只有在签名后才能发布、安装、应用,签名是保护app的方式,标记该app的唯一性。如果app被恶意篡改,签名就不一样了,就无法安装升级,一定程度上也保护了我们的app。

    要对app进行签名,需要有一个签名证书文件。

    一般app有debug和release两种模式,我们可以针对两种模式采用不同的签名方式,一般debug的时候,android SDK已经提供了一个默认的debug签名证书,我们可以直接使用。但是发布的时候,release模式构建时,我们要配制使用自己的签名。

    • 签名配制SigningConfigs是Android的一个方法,它接受一个域对象作为参数,一个SigningConfig就是一个签名配制

      storeFile:签名证书文件

      storePassword:签名证书文件的密码

      storeType:签名证书的类型

      keyAlias:签名证书中密钥别名

      keyPassword:签名证书中该密钥的密码

      默认情况下,debug模式的签名已经被配置好了,使用的是android SDK自动生成的debug证书,它一般位于$HOME/.android/debug.keystore,其key和密码都是已知的,一般情况下我们不需要单独配置debug模式的签名信息。

    • 使用签名

      1. buildTypes {
      2. debug {
      3. signingConfig signingConfigs.debug
      4. }
      5. release {
      6. signingConfig signingConfigs.release
      7. }
      8. }
    8.3 构建的应用类型

    在android gradle 工程中,android gradle 已经帮我们配置了debug和release两个构建类型,这两种模式的主要差别在于,能够在设备上调试以及签名不一样,其他代码资源都是一样的。

    • buildTypes

      1. buildTypes {
      2. debug {
      3. }
      4. release {
      5. }
      6. // 自定义
      7. vip {
      8. }
      9. }

      如果还想新增构建类型,在buildTypes{}代码块中继续添加元素就可以,buildTypes和signingConfigs一样,也是Android的一个方法,接受的参数是一个域对象NamedDomainObjectContainer。添加的每一个都是BuildType类型。

    8.3.1 applicationIdSuffix

    BuildType的一个属性,用于配制基于默认applicationId的后缀,比如默认defaultConfig中配制的applicationId为com.hello.world,我们在debug的BuildType中指定applicationIdSuffix为.debug,那么生成的debug apk包名为com.hello.world.debug。

    8.3.2 debuggable

    BuildType的一个属性,用于配制是否可以生成一个可供调试的apk。

    • buildType

      1. buildTypes {
      2. debug {
      3. debuggable true
      4. }
      5. release {
      6. debuggable = false
      7. }
      8. }
    8.3.3 jniDebuggable

    类似于debuggable,是否生成可供jni调试的apk包。

    8.3.4 minifyEnabled

    BuildType的一个属性,用于配制该BuildType是否启用Proguard混淆。

    8.3.5 multiDexEnabled

    BuildType的一个属性,用于配制该BuildType是否启用自动拆分多个Dex的功能。一般用于程序中代码太多了,超过了65535个方法的时候。

    8.3.6 proguardFile/proguardFiles

    配制混淆文件

    8.3.8 shrinkResources

    BuildType的一个属性,用于配制是否启动自动清理未使用的资源,默认为false

    8.3.9 signingConfig

    配制签名文件

    每一个BuildType都会生成一个sourceSet,默认位置为src//,一个sourceSet包含源代码,资源文件信息,在android中就包含了java源代码,res资源文件以及androidManifest文件。所以针对不同的BuildType,我们可以单独为其指定java源代码,res资源等。只要把他们放到src//下相应的位置即可,在构建的时候,Android Gradle 会优先使用它们代替main下相关文件。

    另外需要注意,因为每个buildType都会生成一个SourceSet,所以新增的buildType名字一定要注意,不能是main和androidTest,因为它们已经被系统占用,同时每个buildType之间名称不能相同。

    除了会生成对应的sourceSet外,每一个BuildType还会生成相应的assemble任务,比如常用的assmebleRelease和assembleDebug就是Android Gradle自动生成的两个task任务,它们对应release和debug这两个BuildType自动生成的,执行相应的assemeble任务,就能生成对应BuildType的所有apk。

    8.4 使用混淆

    混淆是一个非常有用的功能,不仅能够优化我们的代码,让apk包变得更小,还可以混淆我们的代码,让反编译的人也不容易看懂业务逻辑,一般release版本需要混淆,debug版本不需要,混淆后就无法断电跟踪调试了。

    要启用混淆,把BuildType属性的minifyEnabled值设置为true即可。

    再配制混淆文件。

    1. buildTypes {
    2. release {
    3. minifyEnabled true
    4. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    5. }
    6. }
    8.5 启用zipAlignEnabled优化

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

    Android Gradle提供了开启zipalign优化更简便的方式,我们只需要打开配制即可,剩下的操作,如调用sdk目录下的zipalign工具进行处理等,Android Gradle会帮我们搞定。

    9. Android Gradle 高级自定义
    9.1 使用公共库

    android的包,比如android.app、android.content、android.view、android.widget等,是默认就包含在android sdk库里的,所有应用都可以直接使用它们,系统会帮我们自动链接它们,不会出现找不到的情况。还有一些库,比如com.google.android.maps,这些库是独立的,并不会被系统自动链接,所以需要单独进行生成使用。

    9.2 批量修改生成的apk文件名

    既然要修改生成的apk文件名,就需要修改android gradle打包输出。android对象为我们提供了三个属性(不只是用来修改apk名)

    1. ApplicationVariant
    2. Library Vaniant
    3. TestVaniant

    这三个元素的名字直译是变体,通俗来讲是android构建的产物,比如applicationVaniant代表google渠道的release包,也可以代表dev开发用的debug包。

    访问以上3个集合都会触发创建所有的任务,这意味着访问这些集合后无需重新配置就会产生,也就是说假如我们通过访问这些集合,修改apk输出的文件名,那么会自动触发创建所有任务,此时我们修改后的新的apk文件名就会起作用。

    • 修改apk名

      1. android {
      2. applicationVariants.all { variant ->
      3. variant.outputs.all {
      4. // 定义apk的名字,拼接variant的版本号
      5. def apkName = "${variant.versionName}"
      6. // 判断是否为空
      7. if (!variant.flavorName.isEmpty()) {
      8. apkName += "_${variant.flavorName}"
      9. }
      10. // 赋值属性
      11. outputFileName = apkName + "_${getBuildTime()}" + ".apk"
      12. }
      13. }
      14. }

      ApplicationVariants是一个DomainObjectCollection集合,我们可以通过all方法遍历,遍历的每一个variant都是一个生成的产物。例如默认的有debug和release两个产物。

      ApplicationVariants中的variant都是ApplicationVariant,它有一个outputs作为它的输出。每个ApplicationVariants至少有一个输出,也可以有多个,所有这里的outputs是一个list集合,遍历它,如果它的名字是以.apk结尾的话,就是我们要修改的文件。

    • variant

      遍历的variant是buildType,比如我们创建一个vip的buildType,再遍历

      1. applicationVariants.all { variant ->
      2. variant.outputs.all {
      3. println "遍历到一个variant flavorName $variant.name"
      4. println "遍历到一个variant versionName $variant.versionName"
      5. }
      6. }

      遍历到一个variant flavorName debug
      遍历到一个variant versionName 1.0
      遍历到一个variant flavorName release
      遍历到一个variant versionName 1.0
      遍历到一个variant flavorName vip
      遍历到一个variant versionName 1.0

    9.3 动态生成版本信息

    一般版本号由3部分构成:major.minor.patch,第一个是主版本号,第二个是副版本号,第三个是补丁号。对应1.0.0

    9.3.1 最原始方式

    最开始的时候我们都是配置在build文件下的,例如:

    1. android {
    2. compileSdkVersion 23
    3. buildToolsVersion "23.0.1"
    4. defaultConfig {
    5. application "org.xxx.xxx"
    6. minSdkVersion 14
    7. targetSdkVersion 23
    8. versionCode 1
    9. versionName "1.0.0"
    10. }
    11. }

    这种方式我们直接写在versionName后面,比较直观,但这个方式有个很大的问题,就是修改不方便,特别是当build文件中有很多代码时,不容易找到,而且修改容易出错。

    9.3.2 分模块的方式

    新建一个version.gradle文件

    • ~/version.gradle

      1. ext {
      2. appVersionApp = 1
      3. appVersionName = "1.0.0"
      4. }

      ext表明要为当前project创建扩展属性,以供其他脚本引用。它就像我们的变量一样,创建好之后,在build.gradle中引用它。

    • app/build.gradle

      1. // 引用上一级目录的version.gradle脚本
      2. apply from : '../version.gradle'
      3. android {
      4. ...
      5. defaultConfig {
      6. versionCode appVersionCode
      7. versionName appVersionNam
      8. }
      9. }
    9.3.3 使用命令行

    gradle提供了执行shell命令非常简便的办法,就是exec。它是一个task任务,可以创建一个继承exec的任务来执行shell命令,但是比较麻烦。更方便的办法是直接在project里使用exec方法:

    • build.gradle

      1. exec {
      2. println "执行exec"
      3. commandLine 'git', 'status'
      4. }

      执行exec
      On branch wangzhiping/dev-v1.0.00
      Your branch is up to date with ‘origin/wangzhiping/dev-v1.0.00’.

    9.5 动态配制AndroidManifest文件

    动态配制AndroidManifest文件,顾名思义就是可以在构建的过程中,动态修改AndroidManifest文件中的一些内容。

    • build.gradle

      1. android {
      2. defaultConfig {
      3. manifestPlaceholders = [
      4. "JPUSH_CHANNEL" : jpush_channel,
      5. ]
      6. }
      7. }
    • AndroidManifest.xml

      1. <application
      2. <meta-data
      3. android:name="JPUSH_CHANNEL"
      4. android:value="${JPUSH_CHANNEL}"/>
      5. </application>

      如果我们的渠道非常多的话,可以这样操作

    • build.gradle

      1. // producFlavor可以理解为策略,例如不同的渠道包
      2. // buildType是指开发周期,例如debug和release,对用户来说无感知
      3. productFlavor {
      4. google {
      5. }
      6. baidu {
      7. }
      8. productFlavors.all { flavor ->
      9. println "flavor $name"
      10. manifestPlaceholders.put("UMENG_CHANNEL", name)
      11. }
      12. }

      Configure project :app
      flavor develop
      flavor publish

    9.6 自定义你的BuildConfig

    gradle提供了buildConfigField,我们可以自己添加常量到BuildConfig中。

    buildConfigField "boolean", "IS_RELEASE_PACKAGE", "true"
    

    它的函数原型是:

    1. // 参数1:字段类型
    2. // 参数2:字段名称
    3. // 参数3:字段的值钱
    4. public void buildConfigField(
    5. @Nonable String type,
    6. @Nonable String name,
    7. @Nonable String value){
    8. }
    9.7 动态添加自定义的资源

    这里讲的自定义资源,是专门针对res/values类型资源的,它们不光可以在res/values文件夹里使用xml的方式定义,还可以在android gradle里定义,大大增加了构建的灵活性。

    • build.gradle

      1. productFlavors {
      2. publish {
      3. resValue 'string','channel_tips', 'boxPublish'
      4. }
      5. develop {
      6. buildConfigField "boolean", "IS_RELEASE_PACKAGE", "false"
      7. resValue 'string', 'channel_tips', 'boxDevelop'
      8. }
      9. }
    • build后,自动生成文件:build/generated/res/resValues/develop/debug/values/gradleResValues.xml

      1. "1.0" encoding="utf-8"?>
      2. <resources>
      3. <string name="channel_tips" translatable="false">boxDevelopstring>
      4. resources>

      当前build的是develop,所以生成的string是boxDevelop

    9.8 编译选项

    有时候我们需要对我们java源代码的编码、源文件使用的jdk版本进行调优修改。比如需要配制源文件的编码为utf-8的编码,以兼容更多的字符;还比如我们想配制编译java源代码的级别为1.8,这样就可以使用override接口方法的继承等特性。

    1. android {
    2. // 字段位于gradle.properties
    3. compileSdkVersion Integer.parseInt("${CompileSdkVersion}")
    4. buildToolsVersion "${BuildToolsVersion}"
    5. }
    6. compileOptions {
    7. encoding = 'utf-8'
    8. sourceCompatibility JavaVersion.VERSION_1_8
    9. targetCompatibility JavaVersion.VERSION_1_8
    9.9 adb 操作选项配制
    1. adbOptions {
    2. timeOutInMs = 5 * 1000 //设置超时时间5秒,执行adb命令超时的时间
    3. // 有六个选项
    4. // 1. -l 锁定该应用程序
    5. // 2. -r 替换已经存在的应用程序,也就是强制安装
    6. // 3. -t 允许测试包
    7. // 4. -s 把应用安装到sd卡上
    8. // 5. -d 允许进行降级安装
    9. // 6. -g 为该应用授予所有运行时的权限
    10. installOptions '-r','-s'
    11. }
    9.10 DEX选项选择

    android中的java源代码被编译成class字节码后,在打包成apk的时候又会被dx命令优化成android虚拟机可执行的dex文件。dex文件比较紧凑,android做这个dex格式目的是为了让程序在android上运行得更快。对于这些dex文件的生成,android gradle插件已经帮我们做好了,android gradle会调用sdk中的dx命令进行处理。但有时候可能遇到提示内存不足的情况,为什么会提示内存不足呢?其实dx只是一个脚本,它调用的还是java编写的dx.jar库。默认情况下分配给dx的内存是1024m。

    • dexOptions

      1. public interface DexOptions {
      2. boolean getIncreamental()
      3. boolean getPreDexLibraries()
      4. boolean getJumboMode()
      5. String getJavaMaxHeapSize()
      6. Integer getThreadCount()
      7. }

      dexOptions共有5个属性

      1. getIncreamental

        用来配制是否开启dex的增量模式,默认为false。增量模式虽然速度更快一些,但是可能会不工作,要慎用。

      2. javaMaxHeapSize

        执行dx命令分配的最大堆内存,例如可以设置为2g

        1. dexOptions {
        2. javaMaxHeapSize "2g"
        3. }
      3. JumboMode

        用来配制是否开启Jumbo模式,有时候我们的工程量比较大,方法超过了65535个,需要开启jumbo模式才能构建成功。

      4. preDexLibraries

        用来配制是否预执行dex Libraries库工程,开启后会大大提高增量构建的速度,不过可能会影响clean构建的速度。默认值为true。有时候我们需要使用dx的–multi-dex选项生成多个dex,这导致和库工程有冲突的时候,需要将该选项设置为false。

      5. threadCount

        用来配制android gradle运行dx命令时使用的线程数,适当的线程数量可以提高dx的效率。

    9.11 突破65535方法数限制

    为什么有这个限制?

    java文件都被打包成一个dex文件,这个文件是被优化过的,dalvik虚拟机的可执行文件,dalvik虚拟机在执行dex文件的时候,使用了short这个类型来索引dex文件中的方法,这就意味着单个dex文件最多可以被定义的方法为65535个。

    9.12 自动清理未使用的资源

    gradle为我们提供了shrinkResources,它是一种在构建时,在打包apk之前,会检测所有的资源,看看是否被引用,如果没有,那么这些资源就不会被打包到apk中,因为在这个过程中(构建时),android gradle构建系统会拿到所有的资源,不管是自己的还是第三方的,都一视同仁处理。resource shrinking要结合minifyEnabled使用,目的是减缩代码。

    • build.gradle

      1. release {
      2. //混淆
      3. minifyEnabled true
      4. // 移除无用的resource文件
      5. shrinkResources true
      6. }

      Task :app:shrinkDevelopDebugRes

    自动清理未使用的资源这个功能虽然好,但是也会有问题,比如用反射获取的资源文件,静态检测是检测不出来的,这时候就需要用android gradle提供的keep方法来配制哪些资源不被清理。

    • res/raw/keep.xml

      1. "1.0" encoding="utf-8"?>
      2. <resources xmlns:tools="http://schemas.android.com/tools"
      3. tools:keep="@string/ik_eid_prefix, @string/ik_szlm_proxy" />

      keep方法的使用,我们需要新建一个xml文件来配制,文件目录是res/raw/keep.xml,然后通过tools:keep来设置,以逗号分隔多个属性。此外,对于这个keep.xml文件,android gradle构建系统最终打包的时候会清理它,不会把它打包到apk中,除非你在代码里通过R.raw.keep引用了它。

    除了shrinkResource之外,android gradle还为我们提供了一个resConfigs,它属于ProductFlavor的一个方法,可让我们配置哪些资源才被打包进apk中,比如只有中文的资源,只有hdpi格式的图片等。这是非常重要的,比如support library和google play service这两个主要的大库,因为国际化的问题,都支持了几十种语言,但对于app来说,我们不需要那么多语言。

    • resConfigs

      1. defaultConfig {
      2. // 只保留中文资源
      3. resConfigs "zh"
      4. }
    10. Android Gradle 多项目构建

    Android的多项目和其他基于gradle构建的多项目差不多,比如java多项目,groovy多项目,它们本身都是gradle多项目构建,唯一的区别是项目本身属性,比如这个项目是java库,那个是android app项目等。

    10.1 Android 项目区别

    android项目一般分为库项目、应用项目、测试项目,android gradle根据这些项目分别对应有3种插件,com.android.library、com.android.application、com.android.test。

    库项目一般和java库非常相似,它比java多的是一些android特有的资源配置。一般一些具有公共特性的类,可以抽象成一个库工程,这样它们就可以被其他项目使用。

    应用项目,一般只有一个,可以打包成我们可以发布的apk包,如果工程太复杂,它会引用很多库项目,以便组成一个最终的app发布。

    10.2 Android 多项目设置

    多个项目的设置和gradle的多项目是一样的,android也是基于gradle的,所以项目其实是gradle的概念,项目本身的特性才是每个领域的细分和定义,如android项目、java项目等。

    • setting.gradle

      1. include ':app'
      2. include ':base-lib'

      这样有三个项目,一个app、一个基础库base-lib,一个根project

    10.3 库项目引用和配置
    • app/build.gradle

      1. dependencies {
      2. implementation project(":lib-center")
      3. }

      android app也可以引用jar包,java lib打出来的包是jar包,android lib打出来的包是aar包。

    10.4 库项目单独发布

    项目直接依赖一般适用于关联比较紧密,不可复用的项目,对于这类项目我们可以直接基于源代码项目的依赖。有时候一些项目,可以被其他项目复用,比如公共组件库,可以单独发布出去,被其他项目使用,创建工程时选择Java Library

    先在本地搭建nexus私服

    1. wget http://sonatype-download.global.ssl.fastly.net/nexus/3/nexus-3.6.0-02-unix.tar.gz

    2. nexus/nexus-3.6.0-02-unix/nexus-3.6.0-02/bin下

      nexus的注释指向jdk路径,INSTALL4J_JAVA_HOME_OVERRIDE=/usr/local/java/jdk1.8.0_321

    3. ./nexus start

    4. 访问:http://localhost:8081/,通了即成功。

    5. 创建Java Library工程

    6. 项目目录为

      1. ├── custom-plugin
      2. │   ├── build.gradle
      3. │   ├── libs
      4. │   └── src
      5. │   └── main
      6. │   ├── java
      7. │   │   └── com
      8. │   │   └── example
      9. │   │   └── custom_plugin
      10. │   │   └── MyPlugin.kt
      11. │   └── resources
      12. │   └── META-INF
      13. │   └── gradle-plugins
      14. │   └── my-plugin.properties
    7. build.gradle

      1. dependencies {
      2. // 应用gradleApi(),才能使用Plugin
      3. implementation gradleApi()
      4. }
    8. resources/META-INF/gradle-plugins/my-plugin.properties

      1. /*
      2. * 对应com/example/custom_plugin/MyPlugin.kt
      3. */
      4. implementation-class=com.example.custom_plugin.MyPlugin

      其中文件名my-plugin即是发布插件的id

    9. MyPlugin.kt

      1. package com.example.custom_plugin
      2. import org.gradle.api.Plugin
      3. import org.gradle.api.Project
      4. class MyPlugin : Plugin<Project>{
      5. override fun apply(project: Project) {
      6. println("myPlugin被应用")
      7. val customTask = project.tasks.create("customTask")
      8. customTask.doLast {
      9. println("Custom Task do Last")
      10. }
      11. }
      12. }
    10. build.gradle

      1. plugins {
      2. id 'java-library'
      3. id 'org.jetbrains.kotlin.jvm'
      4. id 'maven-publish'
      5. }
      6. java {
      7. sourceCompatibility = JavaVersion.VERSION_1_7
      8. targetCompatibility = JavaVersion.VERSION_1_7
      9. }
      10. dependencies {
      11. implementation gradleApi()
      12. }
      13. group 'com.example.plugin'
      14. version '1.0.5'
      15. publishing {
      16. publications {
      17. myPlugin(MavenPublication) {
      18. artifactId = "wzp"
      19. from components.java
      20. groupId = group
      21. version = version
      22. }
      23. }
      24. repositories {
      25. maven {
      26. allowInsecureProtocol true
      27. name = "nexus"
      28. url = "http://localhost:8081/repository/maven-releases/"
      29. credentials {
      30. username "admin"
      31. password "admin123"
      32. }
      33. }
      34. }
      35. }

      sync成功后,运行publish任务,即可发布到私服。

    • 引用刚发布到私库的插件

    • 根目录build.gradle

      1. buildscript {
      2. repositories {
      3. maven {
      4. allowInsecureProtocol = true
      5. url 'http://localhost:8081/repository/maven-releases/'
      6. }
      7. }
      8. dependencies {
      9. classpath 'com.example.plugin:wzp:1.0.5'
      10. }
      11. }
    • app/build.gradle

      apply plugin : 'my-plugin'
      

      Configure project :app
      myPlugin被应用

      并且能够找到插件创建的customTask

    11. Android Gradle多渠道构建
    11.1 多渠道构建的基本原理

    在Android Gradle中,定义了一个叫Build Variant的概念,直译是构建变体 —— 构建apk的产物。一个Build Variant = BuildType + Product Flavor,BuilType是构建类型,比如release和debug;Product Flavor是构建的渠道,比如Baidu、Google等,它们加起来就是baiduRelease、baiduDebug、googleRelease、googleDebug,公有这几种组合构建产出。Product Flavor也是多渠道构建的基础。

    再或者Product Flavor可以定义为develop、publish,因此就有developDebug、publishDebug、developRelease、publishRelease这几种组合构建产出。

    • 新增一个Product Flavor

      1. productFlavors {
      2. google {
      3. }
      4. baidu {
      5. }
      6. }

      android gradle 为我们提供了productFlavors方法来添加不同的渠道,它接受域对象类型的ProductFlavor闭包作为其参数。每一个都是一个ProductFlavor类型的渠道。在NamedDomainObjectContainer中的名字就是渠道名。

      以上的发布渠道配置之后,Android Gradle会生成很多task,基本上都属于BuildType + Product Flavor的方式生成的。比如assembleBaidu运行之后会生成Baidu渠道的release包和debug包,assembleRelease运行后会生成所有渠道的release包,而assembleBaiduRelease运行之后只生成Baidu的release包。除了生成task之外,每个ProductFlavor还可以有自己的SrouceSet,还可以有自己的Dependencis依赖,这意味着我们可以为每个渠道定义它们自己的资源、代码以及依赖的第三方库,这为我们自定义每个渠道提供了很大的灵活性。

    11.3 多渠道构建定制

    多渠道的定制,其实就是对anroid gradle 插件的 ProductFlavor的配置,通过配置ProductFlavor达到灵活控制每一个渠道的目的。

    11.3.1 applicationId

    它是ProductFlavor的属性,用于设置该渠道的包名。

    • build.gradle

      1. productFlavors {
      2. google {
      3. flavorDimensions "1.0.1"
      4. applicationId "org.example.google.test"
      5. }
      6. baidu {
      7. flavorDimensions "1.0.0"
      8. applicationId "org.example.baidu.test"
      9. }
      10. }

      这样就可以让不同渠道的apk包有不同的包名

    11.3.2 consumerProguardFiles

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

    • build.gradle

      1. android {
      2. productFlavors {
      3. google {
      4. consumerProguardFiles 'proguard-rules.pro', 'proguard-android.txt'
      5. }
      6. }
      7. }

      还有一种是属性设置,属性设置的方式每次都是新的混淆文件列表,以前的会被清空。

    11.3.13 dimension

    有时候,我们想基于不同的标准来构建App,比如免费版还是收费版,x86版还是arm版等。在不考虑BuildType的情况下,这里有4个组合;x86免费、x86收费、arm免费、arm收费版。对于这种情况,我们有两种方式来构建,第一种通俗的用法,就是配置4个ProductFlavor,分别是x86free、x86paid、armfree、armpaid,然后针对这4个ProductFlavor配置,满足我们的需求即可。这种方式比较通俗易懂,但比较冗余,比如free是可以抽象出来的,可以通过多维度控制。

    dimension是ProductFlavor的一个属性,接受一个字符串,作为ProductFlavor的维度。可以理解为是对ProductFlavor的分组,比如free和paid可以认为是属于版本(version),而x86和arm属于架构,这样就分成了两组。

    • build.gradle

      1. // 先定义dimensions
      2. // 最后生成的variant会被如下几个ProductFlavor对象配置
      3. // 1. android里的defaultConfig配置,也是一个ProductFlavor
      4. // 2. abi维度的ProductFlavor,被dimension配置标记为abi的ProductFlavor
      5. // 3. version维度的ProductFlavor,被dimension配置标记为version的ProductFlavor
      6. // 维度的优先级非常重要,因为高优先级的flavor会替换掉低优先级的资源、代码、配置等。
      7. // 这里优先级为abi > version > defaultConfig
      8. flavorDimensions "abi", "version"
      9. productFlavors {
      10. free {
      11. dimension 'version'
      12. }
      13. paid {
      14. dimension 'version'
      15. }
      16. x86 {
      17. dimension 'abi'
      18. }
      19. arm {
      20. dimension 'abi'
      21. }
      22. }

      这样gradle就会根据abi和version的组合,帮我们生成一下variant

      1. ArmFreeDebug
      2. ArmFreeRelease
      3. ArmPaidDebug
      4. ArmPaidRelease
      5. x86FreeDebug
      6. x86FreeRelease
      7. x86PaidDebug
      8. x86PaidRelease
    11.4 提高多渠道构建的效率
  • 相关阅读:
    【Python 逆向滑块】(实战五)逆向滑块,并实现用Python+Node.js 生成滑块、识别滑块、验证滑块、发送短信
    Jenkins: 配合docker来部署项目
    谷歌收录网页最快需要多久?
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    springboot载入配置文件
    如何规划私域流量体系搭建社群运营sop推广方案计划表格
    CSS基础学习--5 background背景
    常用的设计模式以及操作Redis、MySQL数据库、各种MQ、数据类型转换的方法
    偏振相关二元谐振光栅
    注册电气工程师(供配电)专业考试大纲纯干货分享
  • 原文地址:https://blog.csdn.net/xiaopangcame/article/details/133944993