慢慢深入发现之前安卓gradle相关的的迷惑慢慢都解开了,慢慢的对gradle有所了解啦!!!
知识点
一、深入了解 Project
1、Project介绍
1、类似安卓的activity,gradle程序的入口,十分重要。
2、gradle中工程下的module其实也是project,为根工程的子project(执行命令gradle projects可以看出)
3、每个peoject必对应一个build.gradle文件,删除这个文件后,project就是普通的文件夹了。
2、模型:
project的模型就是树结构模型,一般只有两层。(一般不建议在子project中再建立project)
3、project作用
1、根project 唯一的作用管理子project(通过一系列api)
2、子project作用就是输出相应文件。
- 比如安卓模块的app project就是输出apk的(apply plugin: ‘com.android.application’)
- 安卓模块的library常用来输出aar(com.android.library)
- java library 就是输出jar包的
注意:输出的内容由相应module下的build.gradle 来控制,每个peoject必对应一个build.gradle文件。
二、Project涉及知识点简介
1、project相关api
提供根project操作子project,子project操作根project的能力。
2、Task相关api
让project可以新增Task、使用已有Task的能力。
3、 属性api
gradle提供弄了一些属性,为project添加额外属性的能力。
4、file相关api
操作当前project下的文件
5、其他api
project加依赖、配置等。
三、详解:project相关api
科普
1、每个groovy脚本都会被编译器编译为script字节码
2、 每一个build.gradle 都会被编译为project的字节码。(build.gradle 就对应一个project)
3、build.gradle中写的逻辑其实就是相当于在project类中编写逻辑。
4、所有的build.gradle 的内容(方法、代码) 都是在执行在gradle的==配置阶段(Configuration)==的。
5、相应的build.gradle文件下的this就代表当前的Project对象。
6、project对象有个name属性,可以获得project的值
1、根project子project对象的获取
随便找个build.gradle 文件写逻辑即可。写完逻辑随便执行个简单的clean 任务。我们的build.gradle文件一定会被执行的。
//1 Set<Project> 获得当前工程下的所有project对象
this.getAllprojects().each {
Project->project
println(project.name)
}
//2 Set<Project> 获得当前工程下的所有子project
this.getSubprojects()
// 3、Project 获得父project,没有父时返回null
this.getParent()
//4、 Project 获得root 根project
this.getRootProject() // 永远不会返回null的
2、父project操作子project
1、传统思路:
父project中遍历获得子project,再操作子project。
2、使用Project类构造:
直接使用提供的构造 ,Project project(String path, Closure configureClosure)等构造操作。(本节使用方式)
(1)Project project(String path, Closure configureClosure)
project("app") { Project project ->
// 这个安卓project指定输出类型 (安卓中application为输出apk,library为library库一般对应aar输出)
apply plugins: "com.android.application"
group "sunnyDay" // 指定分组
version "1.0.1" // 版本
dependencies {
// 指定依赖闭包
}
android{
//指定安卓闭包 安卓项目当然有安卓闭包
}
}
参数1:project的路径(name),参数2:闭包,闭包代表指定路径的project
ps: 所以实际上完全可以在根工程中完成子工程中所有的配置,但是不建议这样做。模块化解耦好。
(2) void allprojects(Closure configureClosure)
allprojects {// 闭包参数默认为每个Project
// 子类相同的配置可以在这里抽取配置
}
参数闭包,配置当前节点project和当前节点下所有子project
(3)void subprojects(Closure configureClosure)
subprojects {
Project project->
if (project.plugins.hasPlugin("com.android.library")){ // 如果子工程为library工程 则做。。。。。
// apply from "xx/xx/build.gradle" // 引用某一project工程,这样就可以使用这个工程下的各种文件。
}
}
1、当前节点下所有子节点的同一配置
2、闭包参数默认为每个子Project
(4)小结:上述三个方法的区别
范围不同:
1、第一个指定工程名获得相应工程对象。
2、第二个获得所有工程对象
3、第三个获得工程的子工程对象。
3、小收获
1、apply plugins: "xxx"// 指定输出类型(例如xxx为com.android.application)
2、apply from "xx/xx/build.gradle" // 引用某一project工程,这样就可以使用这个工程下的各种文件。
四、详解:属性相关API
1、常见属性值
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
/**
* The default project build file name.
*/
String DEFAULT_BUILD_FILE = "build.gradle";
/**
* The hierarchy separator for project and task path names.
*/
String PATH_SEPARATOR = ":";
/**
* The default build directory name.
*/
String DEFAULT_BUILD_DIR_NAME = "build";
String GRADLE_PROPERTIES = "gradle.properties";
String SYSTEM_PROP_PREFIX = "systemProp";
String DEFAULT_VERSION = "unspecified";
String DEFAULT_STATUS = "release";
......
}
可以看到Project类中默认了一些属性值的。常见属性值的意义如下:
1、String DEFAULT_BUILD_FILE = “build.gradle”:project 就是默认从这个文件读取配置,对自己初始化的。
2、String PATH_SEPARATOR = “:”:文件分隔符,文件中使用反斜杠,Gradle中使用冒号
3、String DEFAULT_BUILD_DIR_NAME = “build”:输出文件夹,存放工程的产出物。
4、String GRADLE_PROPERTIES = “gradle.properties”: 配置文件
2 、扩展属性
这里就有一个安卓工程为例子,因为安卓工程下,默认有个app子工程,我们不用在创建module了,直接拿来使用。
一般默认情况下app的build.gradle如下。是不是很熟悉,哈哈哈!
apply plugin: 'com.android.application'
android {
compileSdkVersion = 29
buildToolsVersion "29.0.1"
defaultConfig {
applicationId "com.sunnyday.gradleproject"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
implementation 'com.google.android.material:material:1.0.0-beta01'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
}
问题:
可以看出,这个project内部的变量值都是写死的,比如compileSdkVersion = 29、applicationId "com.sunnyday.gradleproject"等等。我们每次修改都要修改其值,不太方便,那么能不能统一配置定义变量?这样我们需要更改时只需更改变量值即可。其实是可行的。解决如下。
(1)变量定义方式
定义变量,抽取,下次只需要修改变量值即可。
如下图,其实我们完全可以吧变量都抽取出来(此处为了方便抽了两个)
(2)使用扩展属性方式(其实也是变量的定义)
扩展属性语法:
ext{
// 变量定义赋值
}
(3)扩展属性和直接定义变量的区别
其实写在一个build.gradle中 扩展属性和定义变量方式是没啥区别的,但是一个工程存在多个模块时。通用抽取时,扩展属性的优势就出来了。
3、扩展属性的优点应用
1、扩展属性优点:抽取到父节点
2、如上使用扩展属性简单的抽取了三条。接下来我们就以这三条为例子。进行抽取。
(1)菜鸡做法:直接抽取,多次定义。
在跟project的build.gradle为每个子工程定义扩展属性,子工程的build.gradle 就可直接使用(如下),编译通过。
(2)中级写法:定义一次多处使用
分析上面写法:
1、其实你为每个子工程指定了扩展属性,Project内部帮我们在每个子类中生成扩展属性。每个都生成还是耗性能的。
2、其实我们可以吧扩展属性直接定义在根工程,然后子工程获得根工程对象再直接复用。
// 根工程build.gradle中定义
ext {
mCompileSdkVersion =29
mVersion = 1
mApplicationId = 'com.sunnyday.gradleproject'
}
// 子工程的build.gradle
android {
// 子工程中获得根工程对象,直接使用
compileSdkVersion = this.rootProject.mCompileSdkVersion
buildToolsVersion "29.0.1"
defaultConfig {
// 子工程中获得根工程对象,直接使用
applicationId this.rootProject.mApplicationId
minSdkVersion 21
targetSdkVersion 29
// 子工程中获得根工程对象,直接使用
versionCode this.rootProject.mVersion
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
其实不用this.rootProject调用,直接compileSdkVersion =mCompileSdkVersion也是可以的,因为每个子project都会继承根project的所有属性,拿来直接使用。
(3)最终优化:自定义个build.gradle 文件
面对多模块,最终抽取演变而来的最终优化。
创建个build.gradle 文件
根工程引入文件
子工程使用
4、根工程的gradle.properties相关属性
其实以点properties结尾的文件都是配置文件,而且这种文件的内部都是以键值对的格式书写
(1)栗子:定义个sdk编译版本,子模块就可以直接通过键访问到这个值。
# 这里定义的键值对所有的工程都可以直接使用,这里都是键值对。键key,值object(使用注意使用方法转换toXXX())
mCompileSdkVersion=29
(2)是否使用某工程
# 是否引入某个工程,在这里配置true 或者false
isLoadXxxProject=false
settiing.gradle 中配置下。如上安卓项目中,直接使用了app这个子project。
如果我们有个工程名字为XxxProject,gradle.properties 配置了false,则这里判断为不使用XxxProject,则XxxProject就为普通的文件夹(参看此工程上的小绿点消失)
五、详解:文件属性相关api
前面groovy的基础语法,我们也了解了gradle文件操作,这里主要是了解Project对象对文件的操作。在project下更加方便操作文件。
1、要点图
2、路径相关的api
主要是获得工程的一些重要目录。
(1)常用api
1、File getRootDir 获得根工程文件
2、File getBuildDir 获得当前工程的build文件
3、File getProjectDir 获得当前工程文件
(2)栗子
println getRootDir().name
println getBuildDir().name
println getProjectDir().name
优点:可以使用getRootDir获得当前工程绝对路径,然后在此绝对路径下,创建相对路径啦。
2、文件相关api
(1)文件定位
1、File file(String filename) 默认路径为当前工程路径
2、FileCollection files(Object… paths) 指定多个文件,返回文件对象集合
ps:以上是直接使用project对象的方法。这时传递的参数(file方法的)默认为当前project的路径,如果你使用new File的方式传递的是绝对路径。注意区别。
// 读取app目录下的build.gradle
getFileContext("./app/build.gradle")
/**
* 读取文件内容
* */
def getFileContext(String path) {
try {
def file = file(path)
println file.text
} catch (GradleException e) {
println("file not found...")
}
}
(2)文件拷贝
copy{
from file // 要拷贝的文件
into file // 拷贝到
exclude{} // 排除不拷贝的文件或者文件夹
rename{}// 重命名
}
例如:app目录下的a.txt 拷贝到根工程下
如下我们在app的build.gradle 中写代码(为了方便指定路径)
ps:file 可为文件对象,或者文件夹
copy{
from file("a.txt")
into getRootDir()
}
(3)文件树的遍历
fileTree(String file){
FileTree tree ->
}
闭包参数 FileTree 对象
ps:Gradle会把文件映射成文件树
fileTree("./src/main/res"){
FileTree tree ->tree.visit {// 使用FileTree 的visit 方法遍历文件
FileTreeElement element->
// 闭包参数文件树节点
println(element.file.name)
}
}
须知:
1、Gradle会把文件映射成文件树
2、fileTree(){}方法闭包参数为文件树对象
3、文件树对象的visit 方法可以遍历文件节点
4、文件节点的file属性可获得相应的文件对象
3、小结
以上方法不支持跨跟工程操作。
六、详解:其他api
1、知识图
2、依赖相关api
(1)主要方法
buildscript {
// 1、配置工程的仓库地址
repositories {}
// 2、配置工程的“插件”依赖地址(注意这里的插件二字)
dependencies {}
}
(2)repositories 的栗子
buildscript { ScriptHandler scriptHandler ->
// 1、配置工程的仓库地址
scriptHandler.repositories {
// 能调用哪些方法继续看方法源码的参数类即可
RepositoryHandler repositoryHandle ->
repositoryHandle.jcenter()
repositoryHandle.google()
repositoryHandle.mavenCentral()
repositoryHandle.maven {
//指定特定的maven地址(一般为公司内部的私有服务器地址)
maven { url 'https://xxx/xxx' }
name "name"// 还可以起个名字,一般为公司名,这里可以不写此句代码
credentials {// 请求这个地址时密码要验证才可(为url请求添加密码限制)
username: "admin"
password: "admin123"
}
}
}
// 2、配置工程的“插件”依赖地址(注意这里的插件二字)
scriptHandler.dependencies {
// 能调用哪些方法继续看方法源码的参数类即可
}
}
1、project的方法
2、查看源码发现参数为闭包,闭包变量为ScriptHandler类型(阅读注释)
3、ScriptHandler可以有哪些方法,继续看ScriptHandler类的源码即可
4、以下为 build script的完整写法(简单写法参看安卓根工程的build.script文件栗子)
(3)dependencies 栗子
1、dependencies :配置工程的插件依赖地址(注意这里的插件二字)
2、注意这个dependencies 在buildscript 闭包内,注意和工程下的dependencies 闭包区别区别:
1、buildscript 闭包内的dependencies 依赖引入的是供gradle使用的
2、工程下的dependencies 依赖引入供我们的应用程序使用
ps:安卓栗子如下(安卓工程中)
// 根工程:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
// app 工程:
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
开发过安卓的看着是不是很熟悉!!!
(4)工程下的dependencies 补充
implementation xxx,xxx可为的类型有哪些?
1、fileTree,file,files。根据要添加的本地文件/文件夹数目选择
2、第三方jar、aar
3、library库工程(使用implementation prooject(“libraryname”))
ps:常见问题:依赖冲突
(5)常见依赖冲突解决方式
dependencies {
implementation 'com.jakewharton:butterknife:10.2.0'{
// 方式1:排除某一模块
exclude{
module:"support-v4"
}
// 方式2:排除某一组下的全部库
exclude{
group:"com.android.support"
}
transitive false // 传递依赖
}
}
(5)传递依赖
本工程是否可以使用远程依赖包里依赖的依赖
dependencies {
implementation 'com.jakewharton:butterknife:10.2.0'{
//本工程是否可以使用远程依赖包里依赖的依赖
transitive false // 传递依赖
}
}
一般不建议使用,默认为false不使用。
因为在工程B的升级过程中可能会废弃对工程C的依赖,这样A就出现Bug了。
3、外部命令执行
跨工程操作文件,使用linux等cmd命令
// 跨工程操作文件
task(name: 'apkcopy') {// 指定任务名
doLast {// gradle的指定阶段去执行
def sourcePath = this.buildDir.path + "outPut/apk"
def destinationPath = "硬盘位置(自己随便定义)"
def command = "mv f ${sourcePath} ${destinationPath}"
exec {// 执行外部命令(linux命令)
try {
} catch (GradleException e) {
println("exec failed")
}
executable "bash" // 代表执行外部命令
args "-c", command
println("exec success")
}
}
}
1、task简单了解,参数指明任务名字
2、doLast闭包指明task在执行阶段执行
3、exec用于执行外部命令
4、一般exec内的语法固定不变,只需改变外部命令即可
The end
下节:Task相关知识点敬请期待!!!