今天,我会经过这篇文章一步一步的编写gradle文件,从项目的建立,到gradle的配置。相信有了这篇文章,你将对gradle的内部运行将有一个全新的认识。html
在讲gradle以前,咱们还需明白一点,gradle语法是基于groovy的。因此咱们先来了解一些groovy的知识,这有助于咱们以后的理解。固然若是你已经有groovy的基础你能够直接跳过,没有的也不用慌,由于只要你懂java就不是什么难题。java
下面我将经过code的形式,列出几点react
def printAge(String name, int age) {
print("$name is $age years old")
}
def printEmptyLine() {
println()
}
def callClosure(Closure closure) {
closure()
}
printAge "John", 24 //输出John is 24 years old
printEmptyLine() //输出空行
callClosure { println("From closure") } //输出From closure
复制代码
def callWithParam(String param, Closure<String> closure) {
closure(param)
}
callWithParam("param", { println it }) //输出param
callWithParam("param") { println it } //输出param
callWithParam "param", { println it } //输出param
复制代码
def printPersonInfo(Map<String, Object> person) {
println("${person.name} is ${person.age} years old")
}
def printJobInfo(Map<String, Object> job, String employeeName) {
println("${employeeName} works as ${job.name} at ${job.company}")
}
printPersonInfo name: "Jake", age: 29
printJobInfo "Payne", name: "Android Engineer", company: "Google"
复制代码
你会发现他们的调用都不须要括号,同时printJobInfo的调用参数的顺序不受影响。android
在gradle中你会发现许多闭包,因此咱们须要对闭包有必定的了解。若是你熟悉kotlin,它与Function literals with receiver相似。git
在groovy中咱们能够将Closures当作成lambdas,因此它能够直接当作代码块执行,能够有参数,也能够有返回值。可是不一样的是它能够改变其自身的代理。例如:github
class DelegateOne {
def callContent(String content) {
println "From delegateOne: $content"
}
}
class DelegateTow {
def callContent(String content) {
println "From delegateTwo: $content"
}
}
def callClosure = {
callContent "I am bird"
}
callClosure.delegate = new DelegateOne()
callClosure() //输出From delegateOne: I am bird
callClosure.delegate = new DelegateTow()
callClosure() //输出From delegateTow: I am bird
复制代码
经过改变callClosure的delegate,让其调用不一样的callContent。 若是你想了解更多,能够直接阅读groovy文档api
在上篇文章中已经提到有关gradle的脚步相关的知识,这里就再也不累赘。 下面咱们来一步一步构建gradle。bash
首先咱们新建一个文件夹example,cd进入该文件夹,在该目录下执行gradle projects,你会发现它已是一个gradle项目了微信
$ gradle projects
> Task :projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'example'
No sub-projects
To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :tasks
BUILD SUCCESSFUL in 5s
复制代码
由于这里不是在Android Studio中建立的项目,因此若是你本地没有安装与配置gradle环境,将不会有gradle命令。因此这一点要注意一下。闭包
每个android项目在它的root project下都须要配置一个settings.gradle,它表明着项目的全局配置。同时使用void include(String[] projectPaths)方法来添加子项目,例如咱们为example添加app子项目
$ echo "include ':app'" > settings.gradle
$ gradle projects
> Task :projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'example'
\--- Project ':app'
To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :app:tasks
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
复制代码
:app中的:表明的是路径的分隔符,同时在settings.gradle中默认root project是该文件的文件夹名称,也能够经过rootProject.name = name来进行修改。
如今须要作的是将子项目app构建成Android项目,因此咱们须要配置app的build.gradle。由于gradle只是构建工具,它是根据不一样的插件来构建不一样的项目,因此为了符合Android的构建,须要申明应用的插件。这里经过apply方法,它有如下三种类型
void apply(Closure closure)
void apply(Map<String, ?> options)
void apply(Action<? super ObjectConfigurationAction> action)
复制代码
这里咱们使用的是第二种,它的map参数须要与ObjectConfigurationAction中的方法名相匹配,而它的方法名有如下三种
由于咱们要使用android插件,因此须要使用apply(plugin: 'com.android.application'),又因为groovy的语法特性,能够将括号省略,因此最终在build.gradle中的表现能够以下:
$ echo "apply plugin: 'com.android.application'" > app/build.gradle
复制代码
添加完之后,再来执行一下
$ gradle app:tasks
FAILURE: Build failed with an exception.
* Where:
Build file '/Users/idisfkj/example/app/build.gradle' line: 1
* What went wrong:
A problem occurred evaluating project ':app'.
> Plugin with id 'com.android.application' not found.
* 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 6s
复制代码
发现报错了,显示com.android.application的插件id找不到。这正常,由于咱们尚未声明它。因此下面咱们要在project下的build.gradle中声明它。为何不直接到app下的build.gradle声明呢?是由于咱们是android项目,project能够有多个sub-project,因此为了防止在子项目中重复声明,统一到主项目中声明。
project的build.gradle声明插件须要在buildscript中,而buildscript会经过ScriptHandler来执行,以致于sub-project也可以使用。因此最终的申明以下:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
}
}
复制代码
上面的buildscript、repositories与dependencies方法都是以Closure做为参数,而后再经过delegate进行调用
相应的google()与jcenter()会在RepositoryHandler执行,classpaht(String)会在DependencyHandler(*)执行。
若是你想更详细的了解能够查看文档
让咱们再一次执行gradle projects
$ gradle projects
FAILURE: Build failed with an exception.
* What went wrong:
A problem occurred configuring project ':app'.
> compileSdkVersion is not specified.
* 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 1s
复制代码
发现报没有指定compileSdkVersion,由于咱们尚未对app进行相关的配置,只是引用了android插件。因此咱们如今来进行基本配置,在app/build.gradle中添加
android {
buildToolsVersion "28.0.1"
compileSdkVersion 28
}
复制代码
咱们在android中进行声明,android方法会加入到project实例中。buildToolsVersion与compileSdkVersion将经过Closure对象进行delegate。
android方法会是如何与project进行关联的?在咱们声明的Android插件中,会注册一个AppExtension类,这个extension将会与android命名。因此gradle可以调用android方法,而在AppExtension中已经声明了各类方法属性,例如buildTypes、defaultConfig与signingConfigs等。这也就是为何咱们可以在android方法中调用它们的缘由。下面是extension的建立部分源码
@Override
void apply(Project project) {
super.apply(project)
// This is for testing.
if (pluginHolder != null) {
pluginHolder.plugin = this;
}
def buildTypeContainer = project.container(DefaultBuildType,
new BuildTypeFactory(instantiator, project.fileResolver))
def productFlavorContainer = project.container(GroupableProductFlavorDsl,
new GroupableProductFlavorFactory(instantiator, project.fileResolver))
def signingConfigContainer = project.container(SigningConfig,
new SigningConfigFactory(instantiator))
extension = project.extensions.create('android', AppExtension,
this, (ProjectInternal) project, instantiator,
buildTypeContainer, productFlavorContainer, signingConfigContainer)
setBaseExtension(extension)
...
}
复制代码
android方法下面就是dependencies,下面咱们再来看dependencies
dependencies {
implementation 'io.reactivex.rxjava2:rxjava:2.0.4'
testImplementation 'junit:junit:4.12'
annotationProcessor 'org.parceler:parceler:1.1.6'
}
复制代码
有了上面的基础,应该会容易理解。dependencies是会被delegate给DependencyHandler,不过若是你到DependencyHandler中去查找,会发现找不到上面的implementation、testImplementation等方法。那它们有究竟是怎么来的呢?亦或者若是咱们添加了dev flavor,那么我又可使用devImplementation。这里就涉及到了groovy的methodMissing方法。它可以在runtime(*)中捕获到没有定义的方法。
至于(*)是gradle的methodMissing中的一个抽象感念,它申明在MethodMixIn中。
对于DependencyHandler的实现规则是: 在DependencyHandler中若是咱们回调了一个没有定义的方法,且它有相应的参数;同时它的方法名在configuration(*)中;那么将会根据方法名与参数类型来调用doAdd的相应方法。
对于configuration(*),每个plugin都有他们本身的配置,例如java插件定义了compile、compileClassPath、testCompile等。而对于Android插件在这基础上还会定义annotationProcessor,(variant)Implementation、(variant)TestImplementation等。对于variant则是基于你设置的buildTypes与flavors。
另外一方面,因为doAdd()是私用的方法,但add()是公用的方法,因此在dependencies中咱们能够直接使用add
dependencies {
add('implementation', 'io.reactivex.rxjava2:rxjava:2.0.4')
add('testImplementation', 'junit:junit:4.12')
add('annotationProcessor', 'org.parceler:parceler:1.1.6')
}
复制代码
注意,这种写法并不推荐,这里只是为了更好的理解它的原理。
gradle的知识点还有不少,这只是对有关Android的一部分进行分析。当咱们进行gradle配置的时,不至于对gradle的语法感到魔幻,或者对它的一些操做感到不解。
我在github上建了一个仓库Android精华录,收集Android相关的文章,若是有须要的能够去看一下,有好的文章能够加我微信fan331100推荐给我。