Android Gradle系列-原理篇

栏目: 编程工具 · 发布时间: 7年前

内容简介:上周我们在今天,我会通过这篇文章一步一步的编写gradle文件,从项目的创建,到gradle的配置。相信有了这篇文章,你将对gradle的内部运行将有一个全新的认识。在讲gradle之前,我们还需明白一点,gradle语法是基于groovy的。所以我们先来了解一些groovy的知识,这有助于我们之后的理解。当然如果你已经有groovy的基础你可以直接跳过,没有的也不用慌,因为只要你懂java就不是什么难题。

上周我们在 Android Gradle系列-入门篇 文章中已经将gradle在项目中的结构过了一遍。对于gradle,我们许多时候都不需要修改类似与*.gradle文件,做的最多的应该是在dependencies中添加第三方依赖,或者说修改sdk版本号,亦或者每次发版本改下versionCode与versionName。即使碰到问题也是直接上google寻找答案,而并没有真正理解它为什么要这么做,或者它是如何运行的?

今天,我会通过这篇文章一步一步的编写gradle文件,从项目的创建,到gradle的配置。相信有了这篇文章,你将对gradle的内部运行将有一个全新的认识。

Groovy

在讲gradle之前,我们还需明白一点,gradle语法是基于groovy的。所以我们先来了解一些groovy的知识,这有助于我们之后的理解。当然如果你已经有groovy的基础你可以直接跳过,没有的也不用慌,因为只要你懂 java 就不是什么难题。

syntax

下面我将通过code的形式,列出几点

  • 当调用的方法有参数时,可以不用(),看下面的例子
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
  • 调用方法时可以指定参数名进行传参,有指定的会转化到Map对象中,没有的将按正常传参
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的调用参数的顺序不受影响。

Closure

在gradle中你会发现许多闭包,所以我们需要对 闭包 有一定的了解。如果你熟悉kotlin,它与 Function literals with receiver 类似。

在groovy中我们可以将Closures当做成lambdas,所以它可以直接当做代码块执行,可以有参数,也可以有返回值。但是不同的是它可以改变其自身的代理。例如:

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 文档

Gradle

在上篇文章中已经提到有关gradle的脚步相关的知识,这里就不再累赘。

下面我们来一步一步构建gradle。

搭建项目层级

首先我们新建一个文件夹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来进行修改。

搭建Android子项目

现在需要做的是将子项目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中的方法名相匹配,而它的方法名有以下三种

  • from: 应用一个脚本文件
  • plugin: 应用一个插件,通过id或者class名
  • to: 应用一个目标代理对象

因为我们要使用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进行调用

  • buildscript(Closure)在Project中调用,通过ScriptHandler来执行Closure
  • repositories(Closure)在ScriptHandler中调用,通过RepositoryHandler来执行Closure
  • dependencies(Closure)在ScriptHandler中调用,通过DependencyHandler来执行Closure

相应的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。

Extensions

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)
     ...
}

Dependencies

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推荐给我。

Android Gradle系列-原理篇


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Database Design and Implementation

Database Design and Implementation

Edward Sciore / Wiley / 2008-10-24 / 1261.00 元

* Covering the traditional database system concepts from a systems perspective, this book addresses the functionality that database systems provide as well as what algorithms and design decisions will......一起来看看 《Database Design and Implementation》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具