Android Gradle权威指南读书笔记
第一章 Gradle入门
- 生成Gradle Wrapper
命令:gradle wrapper --gradle-version 版本号 - 自定义Gradle Wrapper
task wrapper(type : Wrapper) {
gradleVersion = '2.4'
archiveBase = 'GRADLE USER HOME'
archivePath = 'wrapper/dists'
distributionBase = 'GRADLE USER HOME'
distributionPath = 'wrapper/dists'
distributionUrl = 'http\://services.gradle.org/distributions/gradle-2.4-all.zip'
- 打印日志
print或者loggerprint定向为QUIET级别日志
logger.quiet('quiet日志信息')
logger.error('error日志信息’)
logger.warn('warn日志信息')
logger.lifecycle(`lifecycle日志信息')
logger.info('info 日志信息.')
logger.debug('debug 日志信息.')
- 命令行
- 查看帮助:
./gradlew tasks,./gradlew -h,/gradlew help --task [task] - 强制刷新依赖:
./gradlew --refresh-dependencies assemble
第二章 Groovy语法
Groovy完全兼容 Java ,这就意味着你可以在 build 本文件里写任何的 Java 代码。
字符串
- 单引号和双引号都可以定义字符串常量
- 单引号不支持字符串表达运算,但双引号支持
task printStringVar <{
def name 张三
println '单引号的变量计算:${name}'
println "双引号的变量计算:${name}"
}
./gradlew printStringVar 运行后输出:
单引号的变量计算: ${name}
双引号的变量计算:张三
双引号可以直接进行表达式计算的这个能力非常好用,我们可以用这种方式进行字符串连接运算,再也不用 Java 中烦琐的“+”号了。记住这个嵌套的规则,一个美元符号紧跟着一对花括号,花括号里放表达式,比如${name}、${1+1}等,只有一个变量的时候可以省略花括号,如$name.
集合
- List
task printList {def numList =[1,2,3,4,5,6]; println numList. getClass().name //ArrayListprintln numList[1]//访问第二个元素println numList[-1]//访问最后一个元素println numList[-2]//访问倒数第二个元素println numList[1..3]//访问第二个到第四个元素numList.each { //遍历println it //it 变量就是正在迭代的元素}
}
- Map
task printlnMap << {def mapl =[ 'width': 1024 ,'height': 768]println mapl.getClass() . name //LinkedHashMapprintln mapl ['width' ] println mapl.heightmapl.each { println "Key:${it . key),Value:${it.value}"}
}
- 方法
3.1 括号可以省略
task invokeMethod << {method1(1,2) method1 1,2
}def method1(int a,int b) { println a+b
}
3.2 return 是可以不写的
当没有return时,Groovy 会把方法执行过程中的最后一句代码作为其返回值:
task printMethodReturn << {def add1 = method2 1 , 2 def add2 = method2 5 , 3 println "addl1:${add1},add2:${add2 }"
} def method2{int a,int b) { if (a>b){a }else{ b}
}
3.3 代码块可作为参数传递
以集合的each方法为例:
//呆板的写法其实是这样
numList.each({println it})
//我们格式化一下,是不是好看一些
numList.each({ println it
})
//好看一些, Groovy 规定,如果方法的最后一个参数是闭包 ,可以放到方法外面
numList.each() { println it
}
//然后方法可以省略括号,就变成我们经常看到的样式。这种写法其实是个方法调用
numList.each { println it
}
JavaBean
并不是一定要定义成员变量才能作为类的属性访问,直接用getter/setter方法,也一样可以当作属性访问:
task helloJavaBean <{Person p = new Person()println "名字是:${p.name}"p.name = "张三"println "名字 ${p.name}"println "年龄是${p.age}"
}class Person { private String namepublic int getAge() { 12}
}
上面的例子发现,有定会getAge()方法时,一样可以使用点运算把getter方法当成属性运算。但是不能修改age的值,因为我们并没有定义setter。
闭包
闭包,其实就是一个代码块。
task helloClosure <<{customEach{//调用闭包print it//it关键字代表传入的参数}
}def customEach(closure){for(int i in 1..10){closure(i)//闭包的调用,传入闭包需要接收的参数。如果只有一个参数,那么就是我们的it变量了}
}
- 向闭包传递参数
task helloClosure << {//多个参数eachMap {k,v -> //"->"符号用于把闭包的参数和主体区分开来。println "${k} is ${v}"}
}def eachMap(closure) { def map1 = ["name": "张三", "age":18]map1.each{ closure(it.key,it.value)}
}
- 闭包委托
Groovy闭包的强大之处在于它支持闭包方法的委托Groovy的闭包有thisObject,owner,delegate三个属性当你在闭包内调用方法时,由它们来确定使用哪个对象来处理。默认情况下delegate,owner是相等的,但是delegate是可以被修改的,这个功能是非常强大的,Gradle中的闭包的很多功能都是通过修改delegate实现的:
task helloDelegate << {new Delegate().test { println "this0bject:${thisObject. getClass()} "println "owner:${owner.getClass ()}"println "delegate:${delegate.getClass() }"method1()it.method()}
}def method1() { println "Context this:${this.getClass()) in root" println "method1 in root"
}class Delegate { def method1() { println "Delegate this:${this.getClass()) in Delegate"println "methodl in Delegate" }def test(Closure<Delegate> closure) {closure(this)}
}
输出结果:
thisObject:class build_e27c427w88bo0afju9niqltzf
owner:class build_e27c427w88bo0afju9niqltzf$_run_closure2
delegate : class build_e27c427w88bo0afju9niqltzf$_ run_closure2
Context this : class build_e27c427w88bo0afju9niqltzf in root
method1 in root
Delegate this:class Delegate in Delegate
method1 in Delegate
通过上面的例子我们发现,thisObject的优先级最高,默认情况下,优先使用 thisObect来处理闭包中调用 的方法,如果有则执行从输出中我们也可以看到,这个thisObject 其实就是这个构建脚本的上下文,它和脚本中this对象是相等的。
从例子中也证明了 delegate和owner 是相等的,它们两个的优先级是: owner 要比 delegate高。所以闭包内方法的处理顺序是 thisObject > owner> delegate。
在DSL (Domain Specific Language,领域特定语言)中,比如 Gradle ,我们一般会指定delegate为当前的 it ,这样我们在闭包内就可以对该进行配置,或者调用其方法:
task configClosure << {person { personName = "张三"personAge = 20 dumpPerson () }
}class Person {String personName int personAge def dumpPerson() { println " name is ${personName} age is ${personAge}"}
}def person(Closure<Person> closure) { Person p =new Person() ; closure.delegate = p //委托模式优先closure.setResolveStrategy(Closure.DELEGATE_FIRST); closure(p)
}
例子中我们设置了委托对象为当前创建的 Person 实例,并且设置了委托模式优先,所以,我们在使用 person方法创建一个 Person 的实例时,可以在闭包里直接对该 Person 实例配置有没有发现和我们在 Gradle中使用 task 创建一个 Task 的用 法很像,其实在 Gradle 中有很多类似的用法,在 Gradle 中也基本上都是使用 delegate 的方式使用闭包进行配置等操作。
第三章 Gradle构建脚本基础
Settings文件
一个设置文件,用于初始化以及工程树的配置。
rootProject.name = 'android-gradle-book-code'
include ':example02'
project(':example02').projectDir = new File(rootDir, 'chapter01/example02')
Projects和tasks
一个Project 包含很多Task,也就是说每个 Project 是由多个 Task组成的。而Task 就是个操作,一个原子性的操作。task其实是Project的一个函数:
/*** <p>Creates a {@link Task} with the given name and adds it to this project. Calling this method is equivalent to* calling {@link #task(java.util.Map, String)} with an empty options map.</p>** <p>After the task is added to the project, it is made available as a property of the project, so that you can* reference the task by name in your build file. See <a href="#properties">here</a> for more details</p>** <p>If a task with the given name already exists in this project, an exception is thrown.</p>** @param name The name of the task to be created* @return The newly created task object* @throws InvalidUserDataException If a task with the given name already exists in this project.*/Task task(String name) throws InvalidUserDataException;
创建task有以下两种方式,一个是task,一个是tasks方法创建TaskContainer:
task customTask1 {doFirst {println 'customTask1:doFirst'}doLast {println 'customTask1:doLast'}
}tasks.create("customTask2") {doFirst {println 'customTask2:doFirst'}doLast {println 'customTask2:doLast'}
}
任务依赖
通过dependsOn方法。dependsOn是Task类的一个方法,可以接受多个依赖的任务作为参数
task ex35Hello << {//<<符号是doLast方法的缩写println 'hello'
}task ex35World << {println 'world'
}task ex35Main(dependsOn: ex35Hello) {doLast {println 'main'}
}task ex35MultiTask {dependsOn ex35Hello,ex35WorlddoLast {println 'multiTask'}
}
任务间通过API控制和交互
创建一个任务和我们定义一个变量是一样的,变量名就是我们定义的任务名,类型Task。所以我们可以通过任务名,使用 Task API 访问它的方法属性或者对任务重新配置等。
对于直接通过任务名操纵任务的原理是:Project 在创建该任务的时候,同时把该任务对应的任务名注册Project 个属性,类型是 Task。
task ex36Hello << {println 'dowLast1'
}ex36Hello.doFirst {println 'dowFirst'
}ex36Hello.doLast {println project.hasProperty('ex36Hello')//trueprintln 'dowLast2'
}
自定义属性
Project和Task 都允许用户添加额外的自定义属性,要添加额外的属性,通过 ext 属性即可实现。相比局部变量,自定义属性有更为广泛的作用域,你可以跨 Project,跨 Task访问这些自定义属性。只要你能访问这些属性所属的对象,那么这些属性都可以被访问到。
apply plugin: "java"//自定义一个Project的属性
ext.age = 18//通过代码块同时自定义多个属性
ext {phone = 1334512address = ''
}sourceSets.all {ext.resourcesDir = null
}sourceSets {main {resourcesDir = 'main/res'}test {resourcesDir = 'test/res'}
}task ex37CustomProperty << {println "年龄是:${age}"println "电话是:${phone}"println "地址是:${address}"sourceSets.each {println "${it.name}的resourcesDir是:${it.resourcesDir}"}
}
第四章Gradle任务
创建Task
- 方法一,调用
Project的task(string)方法
def Task ex41CreateTask1 = task(ex41CreateTask1)
ex41CreateTask1.doLast {println "创建方法原型为:Task task(String name) throws InvalidUserDataException"
}
- 方法二,调用
Project的task(name,map),以一个任务名字+一个对该任务配置的 Map 对象。Map可配置项有限(type,overwrite,dependsOn,action,description,group),参考TaskContainer#Task create(Map<String, ?> options)。
| 配置项 | 描述 | 默认值 |
|---|---|---|
| type | 基于一个存在 Task 来创建,和我们类继承 不多 | DefaultTask |
| overwrite | 是否替换存在 Task ,这个和 type 合起来用 | false |
| dependsOn | 用于配直任务的依赖 | [] |
| action | 添加到任务中的 Actio 或者一个闭包 | null |
| description | 用于配置任务的描述 | null |
| group | 用于配置任务的分组 | null |
def Task ex41CreateTask2 = task(ex41CreateTask2,group:BasePlugin.BUILD_GROUP)ex41CreateTask2.doLast {println "创建方法原型为:Task task(Map<String, ?> args, String name) throws InvalidUserDataException"println "任务分组:${ex41CreateTask2.group}"
}
- 方法三,
Project的task(Closure)。闭包里的委托对象就是Task ,所以你可以使用Task对象的任何方法、属性等信息进行配置。
task ex41CreateTask3 {description '演示任务创建'doLast {println "创建方法原型为:Task task(String name, Closure configureClosure)"println "任务描述:${description}"}
}# 核心都是调用TaskContainer 对象中的 create 方法。
tasks.create('ex41CreateTask4') {description '演示任务创建'doLast {println "创建方法原型为:Task create(String name, Closure configureClosure) throws InvalidUserDataException"println "任务描述:${description}"}
}
访问Task
- 我们创建的任务都会作为项目(
Project)的一个属性,属性名就是任务名,所以我们可以直接通过该任务名称访问和操纵该任务:
task ex42AccessTask1
ex42AccessTask1.doLast {println 'ex42AccessTask1.doLast'
}
- 在
Project中我们可以通过tasks属性访问TaskContainer,以访问集合元素的方式访问我们创建的任务:
task ex42AccessTask2
tasks['ex42AccessTask2'].doLast {println 'ex42AccessTask2.doLast'
}
访问时,任务名就是 Key(关键索引)。 其实这里说 Key 不恰当,因为 tasks 并不是Map 。这里再顺便扩展一下 Groovy 的知识,[]在 Groovy中是操作符,我们知道 Groovy的操作符有对应方法让我们重载, a[b]对应的是a.getAt(b )这个方法,对应的例tasks[''42AccessTask2'] 其实就是调用 tasks.getAt('ex42AccessTask2')这个方法 如果我们查看Gradle代码的源码最后发现调用 findByName(String name)实现的。
- 通过路径访问。
get找不到任务就会抛出UnknownTaskException异常 ,而find找不到该任务的时候会返null:
task ex42AccessTask3
tasks['ex42AccessTask3'].doLast {println tasks.findByPath(':example42:ex42AccessTask3')println tasks.getByPath(':example42:ex42AccessTask3')println tasks.findByPath(':example42:asdfasdfasdf')
}
- 通过名称访问。
get和find区别同上。值得强调的是,通过路径访问的时候, 参数值可以是任务路径也可以是任务的名字。但通过名字访问,参数值只能是任务的名称,不能为路径。
task ex42AccessTask4
tasks['ex42AccessTask4'].doLast {println tasks.findByName('ex42AccessTask4')println tasks.getByName('ex42AccessTask4')}
任务分组和描述
任务是可以分组和添加描述的,任务的分组其实就是对任务的分类,便于我们对任务进行归类整理,这样清晰明了 任务的描述就是说明这个任务有什么作用,是这个任务的大概说明。这在使用tasks命令行或Android Studio工具查看时会更直观。
def Task myTask = task ex43GroupTask
myTask.group = BasePlugin.BUILD_GROUP
myTask.description = '这是一个构建的引导任务'myTask.doLast {println "group:${group},description:${description}"
}
<< 操作符
<<操作符在 Gradle Task 上是 doLast 方法的短标记形式,也就是说<<可以代替doLast。<< 对应的是 a.leftShift(b)。
/*** <p>Adds the given closure to the end of this task's action list. The closure is passed this task as a parameter* when executed. You can call this method from your build script using the << left shift operator.</p>** @param action The action closure to execute.* @return This task.** @deprecated Use {@link #doLast(Closure action)}*/@DeprecatedTask leftShift(Closure action);
任务执行顺序
def Task myTask = task ex45CustomTask(type: CustomTask)
myTask.doFirst{println 'Task执行之前执行 in doFirst'
}
myTask.doLast{println 'Task执行之后执行 in doLast'
}class CustomTask extends DefaultTask {@TaskActiondef doSelf() {println 'Task自己本身在执行 in doSelf'}}
Gradle会解析其带有TaskAction 注解的方法作为 Task要执行的 Action。然后通过 Task的prependParallelSafeAction方法将 Action 加到 actionsList中。而doFirst和doLast的原理其实就是将Action插入到List的最前面和最后面。
任务排序
通过Task的属性,shouldRunAfter和mustRunAfter干预任务的执行顺序而并非我们理解的任务排序。
task ex46OrderTask1 << {println 'ex46OrderTask1'
}task ex46OrderTask2 << {println 'ex46OrderTask2'
}ex46OrderTask1.mustRunAfter ex46OrderTask2
任务的启用和禁用
使用Task的enable和disable属性
task ex47DisenabledTask << {println 'ex47DisenabledTask'
}ex47DisenabledTask.enabled =false
任务的onlyIf断言
断言就是一个条件表达式。 Task 一个onlyIf方法,它接受一个闭包作为参数如果该闭包返回 true该任务执行,否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。
final String BUILD_APPS_ALL="all";
final String BUILD_APPS_SHOUFA="shoufa";
final String BUILD_APPS_EXCLUDE_SHOUFA="exclude_shoufa";task ex48QQRelease << {println "打应用宝的包"
}
task ex48BaiduRelease << {println "打百度的包"
}
task ex48HuaweiRelease << {println "打华为的包"
}task ex48MiuiRelease << {println "打Miui的包"
}task build {group BasePlugin.BUILD_GROUPdescription "打渠道包"
}build.dependsOn ex48QQRelease,ex48BaiduRelease,ex48HuaweiRelease,ex48MiuiReleaseex48QQRelease.onlyIf {def execute = false;if(project.hasProperty("build_apps")){Object buildApps = project.property("build_apps")if(BUILD_APPS_SHOUFA.equals(buildApps)|| BUILD_APPS_ALL.equals(buildApps)){execute = true}else{execute = false}}else{execute = true}execute
}ex48BaiduRelease.onlyIf {def execute = false;if(project.hasProperty("build_apps")){Object buildApps = project.property("build_apps")if(BUILD_APPS_SHOUFA.equals(buildApps)|| BUILD_APPS_ALL.equals(buildApps)){execute = true}else{execute = false}}else{execute = true}execute
}ex48HuaweiRelease.onlyIf {def execute = false;if(project.hasProperty("build_apps")){Object buildApps = project.property("build_apps")if(BUILD_APPS_EXCLUDE_SHOUFA.equals(buildApps)|| BUILD_APPS_ALL.equals(buildApps)){execute = true}else{execute = false}}else{execute = true}execute
}ex48MiuiRelease.onlyIf {def execute = false;if(project.hasProperty("build_apps")){Object buildApps = project.property("build_apps")if(BUILD_APPS_EXCLUDE_SHOUFA.equals(buildApps)|| BUILD_APPS_ALL.equals(buildApps)){execute = true}else{execute = false}}else{execute = true}execute
}
打包时,只需要在命令行(或者新建task设置property,调用Task#setProperty(name,value)):
#打所有渠道包
. /gradlew :example48:build
. /gradlew -Pbuild_apps=all :example48 : build
#打首发包
. /gradlew -Pbuild_apps=shoufa :example48 : build
#打非首发包
./gradlew -Pbuild_apps=exclude shoufa :example48:build
其中,命令行中-P意思是为Project指定K-V格式的属性键值对,使用格式为-PK=V。(命令行task -h可查看用法)
任务规则
通过TaskContainer的addRule()方法添加规则。
Rule addRule(Rule var1);
Rule addRule(String var1, Closure var2);
当我们执行、依赖一个不存在的任务时, Gradle会执行失败,失败信息是任务不存在。我们使用规则对其进行改进,当执行、依赖不存在的任务时,不会执行失败,而是打印提示信息,提示该任务不存在:
tasks.addRule("对该规则的一个描述,便于调试、查看等") { String taskName ->task(taskName) << {println "该${taskName}任务不存在,请查证后再执行"}
}task ex49RuleTask {dependsOn missTask//此任务不存在,会打印上面的提示
}
第五章 Gradle插件
插件作用
- 可以添加任务到你的项目中,帮你完成一些事情,比如测试、编译、打包
- 可以添加依赖配置到你的项目中,我们可以通过它们配置我们项目在构建过程中需要的依赖,如我们编译的时候依赖的第三方库等。
- 可以向项目中现有的对象类型添加新的扩展属性、方法等,让你可以使用它们帮助我们配置、优化构建,比如
android {}这个配置块就是Android Gradle插件为Project对象添加的 个扩展。 - 可以对项目进行一些约定,比如应用
Java件之后,约定src/main/java目录下是我们的源代码存放位置,在编译的时候也是编译这个目录下的Java源代码文件。
使用插件
- 二进制插件
二进制插件就是实现了org.gradle.api.Plugin接口的插件。
apply plugin:'java'
apply plugin:org.gradle.api.plugins.JavaPlugin
apply plugin:JavaPlugin
上面三种写法是等价的。对于Gradle内置的二进制插件,都有一个容易记的短名,成为plugin id,如上面的java。非内置的需要使用全路径名导入,就是第二种写法。而包 org.gradle.api.plugins又是Gradle默认导入的,所以可以省去,也就是第三种写法。
- 脚本插件
apply from:'version.gradle'task ex52PrintlnTask << {println "App版本是:${versionName},版本号是:${versionCode}"
}
其实这不能算一个插件,它只是一个脚本。应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它使用的是from关键字,后面紧跟的是 个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用 HTTP URL。
虽然它不是一个真正的插件,但是不能忽视它的作用,它是脚本文件模块化的基础,我们可以把庞大的脚本文件,进行分块、分段整理,拆分成一个个共用、职责分明的文件,然后使apply from 来引用它们,比如我们可以把常用的函数放在一个 utils.gradle 脚本里,供其他脚本文件引用。
- 第三方插件
第三方插件需要先使用buildscript{}进行配置。更多的第三方插件可以通过https://plugins.gradle.org/查找。
自定义插件
如果仅在自身项目中使用,可以在build文件中继承Plugin类实现apply()接口。
apply plugin: Ex53CustomPluginclass Ex53CustomPlugin implements Plugin<Project> {void apply(Project project) {project.task('ex53CustomTask') << {//创建了一个任务供导入方使用println "这是一个通过自定义插件方式创建的任务"}}
}
现在我们就可以用也gradlew :example53 :ex53CustomTask 来执行这个任务,这个任务是我们通过自定义插件创建的。
如果我们想开发个独立的插件给所有想用的人怎么做呢?这就需要我们单独创建一个Groovy 工程作为开发自定义插件的工程了。
- 创建 Groovy 工程,然后配置我们插件开发所需的依赖:
apply plugin: 'groovy'dependencies {compile gradleApi()compile localGroovy()
}
- 实现插件类
# Ex53CustomPlugin.groovy
package com.github.rujews.pluginsimport org.gradle.api.Plugin
import org.gradle.api.Projectclass Ex53CustomPlugin implements Plugin<Project>{@Overridevoid apply(Project target) {target.task('ex53CustomTask') << {println "这是一个通过自定义插件方式创建的任务"}}
}
- 因为每个插件都有一个唯一的
plugin id,所以需要在META-INFO文件夹里的properties文件来定义对应插件实现类。在src/main/resources/META-INFO/gradle-plugins目录下新建[plugin_id].properties文件,其中,文件名就是plugin id。在文件中写入:
implementation-class=com.github.rujews.plugins.Ex53CustomPlugin
key为implementation class 固定不变, value 就是我们自定义的插件的实现类,上面的例子中就是com.github.rujews.plugins.Ex53CustomPlugin 。现在都配置好了,我们就可以生成一个 jar 包分发给其他人使用我们的插件了.
buildscript { dependencies { classpath files (’ libs/example53 . jar’) }
}
apply plugin:'com.github.rujews.plugins.ex53customplugin'
第六章 Java Gradle插件
构建Java项目
执行build这个任务,输出如下:
main
test
:example64:compileJava
:example64:processResources
:example64:classes
:example64:jar
:example64:assemble
:example64:compileTestJava UP-TO-DATE
:example64:processTestResources UP-TO-DATE
:example64:testClasses UP-TO-DATE
:example64:test UP-TO-DATE
:example64:check UP-TO-DATE
:example64:build
从上面可以看出这个任务在执行过程中都做了什么,最后在build/libs生成jar包。clean任务则是删除build目录以及其他构建生成的文件。assemble 任务,该任务不会执行单元测试,只会编译和打包。这个任务在 Android里也有,执行它可以打 apk 包,所以它不止会打 jar 包,其实它算是一个引导类的任务,根据不同的项目类型打出不同的包。check 任务,它只 执行单元测试,有时候还会做一些质量检查,不会打 jar 包,也是个引导任务。
源码集合(SourceSet)
Java插件在Project下为我们提供了一个sourceSets属性以及 sourceSets{}闭包来访问和配置源集。sourceSets{}闭包配置的都是 SourceSet对象。
apply plugin:'java'sourceSets {main {//这里对main SourceSet配置}
}task ex65SourceSetTask {sourceSets.all{println name}
}

常用源集属性

Java 插件添加的通用任务

源集任务
运行任务的时候,列表中的任务名称中 sourceSet 要换成你的源集的名称,比如 main源集的名称就是compileMainJava。
Java插件添加的属性
Java 插件添加了很多常用的属性,这些属性都被添加到Project 中,我们可以直接使用比如前面己经用到的 sourceSets:

Jav 插件添加的源集属性
多项目构建
多项目构建,其实就是多个 Gradle 项目一起构建。

目录结构
如上图目录结构,同一个模块下也可以在setting.gradle中进行配置以达到模块化的作用,供外部依赖使用。
include ':example68app'
project(':example68app').projectDir = new File(rootDir, 'chapter06/example68/app')
include ':example68base'
project(':example68base').projectDir = new File(rootDir, 'chapter06/example68/base')
这样,我们可以在app项目中使用base项目了:
apply plugin:'java'dependencies {compile project(':example68base')
}
发布构建
Gradle 构建的产物,我们称之为构件。一个构件可以是一个 Jar ,也可以是一个 Zip或者 WAR等。
- 定义发布的构件
apply plugin:'java'task publishJar(type: Jar)
artifacts {archives publishJar //通过一个 Task 来为我们发布提供构件
}
也可以发布文件:
def publishFile = file ( ’ build/buildile ’)
artifacts { archives publishFile
}
- 发布构件,也就是上传构件到指定的地方。可以是一个指定的目录,Maven库等。
apply plugin:'java'
apply plugin:'maven'task publishJar(type: Jar)group 'org.flysnow.androidgradlebook.ex69'
version '1.0.0'artifacts {archives publishJar
}uploadArchives {repositories {flatDir {name 'libs'dirs "$projectDir/libs" //发布到本地libs目录}mavenLocal()//发布到本地Maven库,也就是用户目录的.m2/repository文件夹mavenDeployer {repository(url: "http://repo.mycompany.com/nexus/content/repositories/releases") {authentication(userName: "usrname", password: "pwd")}snapshotRepository(url: "http://repo.mycompany.com/nexus/content/repositories/snapshots") {authentication(userName: "usrname", password: "pwd")}}}
}
第七章 Android Gradle插件
Android Gradle插件的分类
App 插件 id: com.android.applicationLibrary 插件 id: com.android.libraryTest 插件 id: com.android.test
Android Gradle 工程
- Android Gradle 工程的配置,都是在
android{}中,这个是唯一的入口。其具体实现是在com.android build.gradle.AppExtension,是一个Project的扩展。参考源码,以及Doc文档。
其创建原型:
extension = project.extensions.create ('android', getExtensionClass() ,
(Projectlnternal) project, instantiator, androidBuilder, sdkHandler,
buildTypeContainer, productFlavorContainer, signingConfigContainer,
extraModelinfo, isLibrary())
defaultConfig。defaultConfig是默认的配置,它是一个ProductFlavor:
/*** The default configuration, inherited by all build flavors (if any are defined).
*/
public void defaultConfig(Action<ProductFlavor> action) {checkWritability();action.execute(defaultConfig);
buildTypes。buildTypes是一个NamedDomainObjectContainer类型,是一个域对象。这个和SourceSet一样。buildTypes里面有release debug等。我们可以在buildTypes{}里新增任意多个我们需要构建的类型,比如debug,Gradle 会帮我们自动创建一个对应BuildType,名字就是我们定义的名字。
proguardFiles 方法可以接受一个可变参数。所以我们可以同时配置多个配置文件。
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
getDefaultProguardFile是Android 扩展的方法,它可以获取你的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{}配置中applicationld,minSdkVersion,versionCode,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 文件中,我们可以指定要使用的库:
<uses-library
android:name=” com.google.android.maps”
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里。需要使用下面的方法:
android {useLibrary 'org.apache.http.legacy'
}
批量修改apk名
applicationVariants.all { variant ->variant.outputs.each { output ->if (output.outputFile != null && output.outputFile.name.endsWith('.apk')&&'release'.equals(variant.buildType.name)) {def flavorName = variant.flavorName.startsWith("_") ? variant.flavorName.substring(1) : variant.flavorNamedef apkFile = new File(output.outputFile.getParent(),"Example92_${flavorName}_v${variant.versionName}_${buildTime()}.apk")output.outputFile = apkFile}}}
Android对象为我们提供了 个属性:applicationVariants(仅仅适用于 Android应用 Gradle 插件) , libraryVariants( 仅仅适用于 Android库Gradle插件),testVariants(以上两种Gradle插件都适用)。
ps:all 是 gradle 中 DomainObjectCollection 接口的方法。each 是 groovy 中List、Map 等集合类的方法。
它们的区别:
1.all 会对集合内现有的元素和之后加入的元素,都执行给定的闭包操作。each 只会对集合内现有的元素执行给定的闭包操作。
2.all接收的闭包,可以直接访问集合内元素的属性和方法。each接收的闭包,不能直接访问集合内元素的属性和方法。
原因:all 接收的闭包是一个配置闭包,集合内元素既会作为参数传给这个闭包,也会被设为闭包的委托对象,这样闭包就可以直接访问委托对象(集合内元素)的属性和方法。each 接收的是一个普通闭包,集合内元素只会作为参数传给这个闭包。
从git获取apk的版本信息
/*** 以git tag的数量作为其版本号* @return tag的数量*/
def getAppVersionCode(){def stdout = new ByteArrayOutputStream()exec {commandLine 'git','tag','--list'standardOutput = stdout}return stdout.toString().split("\n").size()
}/*** 从git tag中获取应用的版本名称* @return git tag的名称*/
def getAppVersionName(){def stdout = new ByteArrayOutputStream()exec {//执行shell命令commandLine 'git','describe','--abbrev=0','--tags'standardOutput = stdout}return stdout.toString().replaceAll("\n","")
}
动态配置manifest
productFlavors.all { flavor ->manifestPlaceholders.put("UMENG_CHANNEL",name)}
自定义BuildConfig
productFlavors {google {buildConfigField 'String','WEB_URL','"http://www.google.com"'}baidu {buildConfigField 'String','WEB_URL','"http://www.baidu.com"'}}
动态添加自定义资源
productFlavors {google {resValue 'string','channel_tips','google渠道欢迎你'}baidu {resValue 'string','channel_tips','baidu渠道欢迎你'resValue 'color','main_bg','#567896'resValue 'integer-array','months','<item>1</item>'}}
其效果和在 res/values 文件中定义一个资源是等价的。最终生成的值可以在build/generated/res/resValues/[productFlavor]/[buildType]/ values/generated.xml中找到。
Java编译选项
compileOptions { encoding = ’ utf-8 ’ sourceCompatibility = JavaVersion.VERSION_1_6targetCompatibility = JavaVersion.VERSION_1_6
}
adb 操作选项配置
adbOptions { timeOutinMs = 5*1000//秒installOptions '-r','-s'
}
adb install有l,r,t,s,d,g六个选项。-l 锁定该应用程序。-r:替换己存在的应用程序,也就是我们说的强制安装 ;-t :允许测试包; -s: 也把应用程序安装到SD卡上。-d: 允许进行降级安装,也就是安装的程序比于机上带的版本低。-g: 为该应用授予所有运行时的权限。
自动清理未使用的资源
- 手动清除
Android Lint工具Resource Shrinking,需要配合Code Shrinking一起使用。
release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile ('proguard-android.txt '),'proguard�rules.pro'
}
如果想看详细日志,想知道哪些资源被自动清理了,可以使--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接受一个以逗号(,)分割的配置资源列表,并且支持星号(*)通配符。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools http://schemas.android.com/tools” tools:keep= @layout/used*_c,@layout/l_used_a,@layout/l_used_b* />
resConfigs方法
defaultConf { ...resConfigs 'zh'
}
第十章 Android Gradle多项目构建
库项目的引用和配置
默认情况下, Android 库项目发布出来的包都是release版本的,当然可以通过配置来改变它,比如改成默认发布的,这就是 debug 版本的:
android{defaultPublishConfig "flavor1Debug"//flavor+buildtype
}
如果要针对不同的版本,引用不同的发布的 aar 。
android{publishNonDefault true
}
在引用时使用以下方法:
dependencies { flavor1Compile project(path:':lib1', configuration:'flavorlRelease') flavor2Compile project(path :':lib1', configuration:'flavor2Release')
}
库项目发布到Nexus
应用过Maven插件之后,我们需要配置 Maven构建的 要素,它们分别是 group:artifact:version。使用 group和version 比较方便,直接指定即可。应用 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 版本,正式发布。
uploadArchives {repositories.mavenDeployer {name = 'mavenCentralReleaseDeployer' //名称固定repository(url: "http://xxx/nexus/repository/releases/") { //仓库地址authentication(userName: "admin", password: "admin") //仓库用户名和密码}snapshotRepository (url: "http://xxx/nexus/repository/snapshots/") { //仓库地址authentication(userName: "admin", password: "admin") //仓库用户名和密码}pom.version = '3.5.23-dev' //版本号pom.artifactId = "test" //aar名称pom.groupId = "com.example.abc" //包名pom.name = "sdk"pom.packaging = 'aar' //打包格式,aar固定}
}
然后配置好仓库的地址告知Gradle:
allprojects {repositories {...maven {url "http://xxx/nexus/content/groups/xxx/"}}
第十一章 Android多渠道构建
多渠道构建定制
consumerProguardFiles。既是一个属性,也有一个同名的方法,它只对Android库项目有用。它使得混淆文件列表也会被打包到aar里一起发布 ,这样 当应用项目 引用这个aar包, 并且启用混淆的时候,会自动使用aar包里的混淆文件对aar里的代码进行混淆 ,这样我们就不用对该aar包进行混淆配置了。
android { productFlavors { google { consumerProguardFiles 'proguard-rules.pro','proguard-rules.txt'}}
}
flavorDimensions。ProductFlavor的一个属性,接受一个字符串,作为该ProductFlavor的维度。
flavorDimensions "abi", "version"productFlavors {free {dimension 'version'}paid {dimension 'version'}x86 {dimension 'abi'}arm {dimension 'abi'}}
提高多渠道构建的效率
参考美团技术的方法:利用了在 apk的META-INF 目录下添加空文件不用重新签名的原理,非常高效,其大概就是:
- 利用
Android Gradle打一个基本包(母包); - 然后基于该包复制一个,文件名要能区分产品 、打包时间、版本、渠道等;
- 然后对复制出来的 apk 文件进行修改,在其
META-INF目录下新增空文件,但是空文件的文件名要有意义,必须包含能区分渠道的名字 ,如mtchannel _google; - 重复步骤2 、步骤3生成我们所需的所有渠道包
apk,这个可以使用Python这类脚本来做; - 这样就生成了我们所有发布渠道的
apk包了。
相关文章:
Android Gradle权威指南读书笔记
第一章 Gradle入门 生成Gradle Wrapper 命令:gradle wrapper --gradle-version 版本号自定义Gradle Wrapper task wrapper(type : Wrapper) { gradleVersion 2.4 archiveBase GRADLE USER HOME archivePath wrapper/dists distributionBase GRADLE USER HOME …...
顺子日期(蓝桥杯)
文章目录 顺子日期问题描述答案:14字符串解题CC语言指针C语言函数 数组解题 顺子日期 问题描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 小明特别喜欢顺子。顺子指的就是连续的三个数字:123、…...
攻防世界web篇-unserialize3
得出php代码残篇 将代码补全后再在线php运行工具中进行运行 在浏览器输入后得到下面的界面 这里需要将O:4:“xctf”:1:{s:4:“flag”;s:3:“111”;} 改为 O:4:“xctf”:2:{s:4:“flag”;s:3:“111”;}...
微信小程序 onLoad和onShow的区别
在微信小程序中,onLoad() 和 onShow() 是两个常用的生命周期函数,用于监听页面的加载和显示事件。这两个函数的区别如下: 触发时机 onLoad() 函数只会在页面加载时触发一次,而 onShow() 函数每次页面显示时都会被触发。因此&#…...
elementui select组件下拉框底部增加自定义按钮
elementui select组件下拉框底部增加自定义按钮 el-select组件的visible-change 事件(下拉框出现/隐藏时触发) <el-selectref"select":value"value"placeholder"请选择"visible-change"visibleChange">&…...
深入理解闭包:原理、应用与最佳实践
1、 什么是闭包? 如果一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量,那么内部函数就形成了一个闭包。 def outer_function(x):# 外部函数接受一个参数 x 是自由变量# seed 也是一个自由变量seed 10def inner_function(y…...
[NSSCTF 2nd]Math
原题py: from secret import flag from Crypto.Util.number import * import gmpy2length len(flag) flag1 flag[:length//2] flag2 flag[length//2:] e 65537m1 bytes_to_long(flag1) p getPrime(512) q getPrime(512) n p*q phi (p-1)*(q-1) d gmpy2.i…...
uml知识点学习
https://zhuanlan.zhihu.com/p/659911315https://zhuanlan.zhihu.com/p/659911315软件工程分析设计图库目录 - 知乎一、结构化绘图1. 结构化——数据流图Chilan Yuk:1. 结构化——数据流图2. 结构化——数据字典Chilan Yuk:2. 结构化——数据字典3. 结构…...
JAVA学习日记1——JAVA简介及第一个java程序
简单记忆 JAVA SE :标准版,核心基础 JAVA EE:企业版,进阶 JDK:Java Development Kit,Java开发工具包,包含JRE JRE:Java Runtime Environment,Java运行时环境ÿ…...
Linux命令(102)之less
linux命令之less 1.less介绍 linux命令less是一个文本文件查看工具,它以一种交互的方式,逐页地显示文本文件的内容,并且可以在文件中进行搜索等定位 2.less用法 less [参数] filename less参数 参数说明-N显示每行的行号-i忽略搜索时的大…...
vue多条件查询
<template><div><input type"text" v-model"keyword" placeholder"关键字"><select v-model"category"><option value"">所有分类</option><option v-for"cat in categories&q…...
c 语言基础:L1-038 新世界
这道超级简单的题目没有任何输入。 你只需要在第一行中输出程序员钦定名言“Hello World”,并且在第二行中输出更新版的“Hello New World”就可以了。 输入样例: 无输出样例: Hello World Hello New World 程序源码: #incl…...
计算机算法分析与设计(13)---贪心算法(多机调度问题)
文章目录 一、问题概述1.1 思路分析1.2 实例分析 二、代码编写 一、问题概述 1.1 思路分析 1. 设有 n n n 个独立的作业 1 , 2 , … , n {1, 2, …, n} 1,2,…,n,由 m m m 台相同的机器 M 1 , M 2 , … , M m {M_1, M_2, …, M_m} M1,M2,…,Mm 进行加工处…...
小程序canvas层级过高真机遮挡组件的解决办法
文章目录 问题发现真机调试问题分析问题解决改造代码效果展示 问题发现 在小程序开发中需要上传图片进行裁剪,在实际真机调试中发现canvas层遮挡住了生成图片的按钮。 问题代码 <import src"../we-cropper/we-cropper.wxml"></import> <…...
番外8.1 配置+管理文件系统
Task01: Linux 文件系统结构; 可以进行Linux操作系统的文件权限管理与方式切换,可以应用磁盘与文件权限管理工具; 01:常见文件系统类型(Ext4[rhel6默认文件管理系统], 存储容量1 EB1073741824 GB; XFS[rhel 7/8默认的文…...
互联网Java工程师面试题·Java 总结篇·第八弹
目录 72、用 Java 的套接字编程实现一个多线程的回显(echo)服务器。 73、XML 文档定义有几种形式?它们之间有何本质区别?解析XML 文档有哪几种方式? 74、你在项目中哪些地方用到了 XML? 72、用 Java 的套…...
VSCode修改扩展和用户文件夹目录位置(Windows)
VSCode修改扩展和用户文件夹目录位置(Windows) 前言:方法前期准备:方法1(强推荐)方法2(不太推荐)方法3(好麻烦,不太推荐) 前言: VSCod…...
Spring 事务
文章目录 实现CURD(没加入事务前)1.加入依赖2.创建jdbc.properties3.配置Spring的配置文件4.数据库与测试表 基于注解的声明式事务准备工作测试模拟场景 加入事务①添加事务配置 Transactional注解标识的位置只读事务属性:超时事务属性&#…...
无法访问 github ,解决办法
一、使用代理(首选) 这种办法只需要更改github.com为代理的域名即可,使用方式与GitHub除了域名不同其他都一样,速度挺快,可登陆,可提交。 1、查看当前的代理: git config --global --get htt…...
SD卡与emmc的异同
eMMC与SD卡的异同: 物理尺寸和接口: eMMC:eMMC是一种嵌入式存储解决方案,通常采用BGA(Ball Grid Array)封装,焊接在电路板上。它没有标准的物理尺寸,而是以芯片的形式存在。SD卡&…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
