资讯专栏INFORMATION COLUMN

Android Gradle系列-原理篇

jsliang / 459人阅读

摘要:上周我们在系列入门篇文章中已经将在项目中的结构过了一遍。但是不同的是它可以改变其自身的代理。这正常,因为我们还没有声明它。与将通过对象进行。至于是的中的一个抽象感念,它申明在中。的知识点还有很多,这只是对有关的一部分进行分析。

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

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

Groovy

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

syntax

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

当调用的方法有参数时,可以不用(),看下面的例子

</>复制代码

  1. def printAge(String name, int age) {
  2. print("$name is $age years old")
  3. }
  4. def printEmptyLine() {
  5. println()
  6. }
  7. def callClosure(Closure closure) {
  8. closure()
  9. }
  10. printAge "John", 24 //输出John is 24 years old
  11. printEmptyLine() //输出空行
  12. callClosure { println("From closure") } //输出From closure

如果最后的参数是闭包,可以将它写在括号的外面

</>复制代码

  1. def callWithParam(String param, Closure closure) {
  2. closure(param)
  3. }
  4. callWithParam("param", { println it }) //输出param
  5. callWithParam("param") { println it } //输出param
  6. callWithParam "param", { println it } //输出param

调用方法时可以指定参数名进行传参,有指定的会转化到Map对象中,没有的将按正常传参

</>复制代码

  1. def printPersonInfo(Map person) {
  2. println("${person.name} is ${person.age} years old")
  3. }
  4. def printJobInfo(Map job, String employeeName) {
  5. println("${employeeName} works as ${job.name} at ${job.company}")
  6. }
  7. printPersonInfo name: "Jake", age: 29
  8. printJobInfo "Payne", name: "Android Engineer", company: "Google"

你会发现他们的调用都不需要括号,同时printJobInfo的调用参数的顺序不受影响。

Closure

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

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

</>复制代码

  1. class DelegateOne {
  2. def callContent(String content) {
  3. println "From delegateOne: $content"
  4. }
  5. }
  6. class DelegateTow {
  7. def callContent(String content) {
  8. println "From delegateTwo: $content"
  9. }
  10. }
  11. def callClosure = {
  12. callContent "I am bird"
  13. }
  14. callClosure.delegate = new DelegateOne()
  15. callClosure() //输出From delegateOne: I am bird
  16. callClosure.delegate = new DelegateTow()
  17. callClosure() //输出From delegateTow: I am bird

通过改变callClosure的delegate,让其调用不同的callContent。
如果你想了解更多,可以直接阅读groovy文档

Gradle

在上篇文章中已经提到有关gradle的脚步相关的知识,这里就不再累赘。
下面我们来一步一步构建gradle。

搭建项目层级

首先我们新建一个文件夹example,cd进入该文件夹,在该目录下执行gradle projects,你会发现它已经是一个gradle项目了

</>复制代码

  1. $ gradle projects
  2. > Task :projects
  3. ------------------------------------------------------------
  4. Root project
  5. ------------------------------------------------------------
  6. Root project "example"
  7. No sub-projects
  8. To see a list of the tasks of a project, run gradle :tasks
  9. For example, try running gradle :tasks
  10. BUILD SUCCESSFUL in 5s

因为这里不是在Android Studio中创建的项目,所以如果你本地没有安装与配置gradle环境,将不会有gradle命令。所以这一点要注意一下。

每一个android项目在它的root project下都需要配置一个settings.gradle,它代表着项目的全局配置。同时使用void include(String[] projectPaths)方法来添加子项目,例如我们为example添加app子项目

</>复制代码

  1. $ echo "include ":app"" > settings.gradle
  2. $ gradle projects
  3. > Task :projects
  4. ------------------------------------------------------------
  5. Root project
  6. ------------------------------------------------------------
  7. Root project "example"
  8. --- Project ":app"
  9. To see a list of the tasks of a project, run gradle :tasks
  10. For example, try running gradle :app:tasks
  11. BUILD SUCCESSFUL in 1s
  12. 1 actionable task: 1 executed

:app中的:代表的是路径的分隔符,同时在settings.gradle中默认root project是该文件的文件夹名称,也可以通过rootProject.name = name来进行修改。

搭建Android子项目

现在需要做的是将子项目app构建成Android项目,所以我们需要配置app的build.gradle。因为gradle只是构建工具,它是根据不同的插件来构建不同的项目,所以为了符合Android的构建,需要申明应用的插件。这里通过apply方法,它有以下三种类型

</>复制代码

  1. void apply(Closure closure)
  2. void apply(Map options)
  3. void apply(Action action)

这里我们使用的是第二种,它的map参数需要与ObjectConfigurationAction中的方法名相匹配,而它的方法名有以下三种

from: 应用一个脚本文件

plugin: 应用一个插件,通过id或者class名

to: 应用一个目标代理对象

因为我们要使用android插件,所以需要使用apply(plugin: "com.android.application"),又由于groovy的语法特性,可以将括号省略,所以最终在build.gradle中的表现可以如下:

</>复制代码

  1. $ echo "apply plugin: "com.android.application"" > app/build.gradle

添加完以后,再来执行一下

</>复制代码

  1. $ gradle app:tasks
  2. FAILURE: Build failed with an exception.
  3. * Where:
  4. Build file "/Users/idisfkj/example/app/build.gradle" line: 1
  5. * What went wrong:
  6. A problem occurred evaluating project ":app".
  7. > Plugin with id "com.android.application" not found.
  8. * Try:
  9. 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.
  10. * Get more help at https://help.gradle.org
  11. 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也能够使用。所以最终的申明如下:

</>复制代码

  1. buildscript {
  2. repositories {
  3. google()
  4. jcenter()
  5. }
  6. dependencies {
  7. classpath "com.android.tools.build:gradle:3.3.2"
  8. }
  9. }

上面的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

</>复制代码

  1. $ gradle projects
  2. FAILURE: Build failed with an exception.
  3. * What went wrong:
  4. A problem occurred configuring project ":app".
  5. > compileSdkVersion is not specified.
  6. * Try:
  7. 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.
  8. * Get more help at https://help.gradle.org
  9. BUILD FAILED in 1s

发现报没有指定compileSdkVersion,因为我们还没有对app进行相关的配置,只是引用了android插件。所以我们现在来进行基本配置,在app/build.gradle中添加

</>复制代码

  1. android {
  2. buildToolsVersion "28.0.1"
  3. compileSdkVersion 28
  4. }

我们在android中进行声明,android方法会加入到project实例中。buildToolsVersion与compileSdkVersion将通过Closure对象进行delegate。

Extensions

android方法会是如何与project进行关联的?在我们声明的Android插件中,会注册一个AppExtension类,这个extension将会与android命名。所以gradle能够调用android方法,而在AppExtension中已经声明了各种方法属性,例如buildTypes、defaultConfig与signingConfigs等。这也就是为什么我们能够在android方法中调用它们的原因。下面是extension的创建部分源码

</>复制代码

  1. @Override
  2. void apply(Project project) {
  3. super.apply(project)
  4. // This is for testing.
  5. if (pluginHolder != null) {
  6. pluginHolder.plugin = this;
  7. }
  8. def buildTypeContainer = project.container(DefaultBuildType,
  9. new BuildTypeFactory(instantiator, project.fileResolver))
  10. def productFlavorContainer = project.container(GroupableProductFlavorDsl,
  11. new GroupableProductFlavorFactory(instantiator, project.fileResolver))
  12. def signingConfigContainer = project.container(SigningConfig,
  13. new SigningConfigFactory(instantiator))
  14. extension = project.extensions.create("android", AppExtension,
  15. this, (ProjectInternal) project, instantiator,
  16. buildTypeContainer, productFlavorContainer, signingConfigContainer)
  17. setBaseExtension(extension)
  18. ...
  19. }
Dependencies

android方法下面就是dependencies,下面我们再来看dependencies

</>复制代码

  1. dependencies {
  2. implementation "io.reactivex.rxjava2:rxjava:2.0.4"
  3. testImplementation "junit:junit:4.12"
  4. annotationProcessor "org.parceler:parceler:1.1.6"
  5. }

有了上面的基础,应该会容易理解。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

</>复制代码

  1. dependencies {
  2. add("implementation", "io.reactivex.rxjava2:rxjava:2.0.4")
  3. add("testImplementation", "junit:junit:4.12")
  4. add("annotationProcessor", "org.parceler:parceler:1.1.6")
  5. }

注意,这种写法并不推荐,这里只是为了更好的理解它的原理。

gradle的知识点还有很多,这只是对有关Android的一部分进行分析。当我们进行gradle配置的时,不至于对gradle的语法感到魔幻,或者对它的一些操作感到不解。

我在github上建了一个仓库Android精华录,收集Android相关的文章,如果有需要的可以去看一下,有好的文章可以加我微信fan331100推荐给我。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74516.html

相关文章

  • Android 性能监控系列一(原理

    摘要:全称应用性能管理监控后面我会通过一系列的文章来介绍的原理框架设计与实现等等。在应用构建期间,通过修改字节码的方式来进行字节码插桩就是实现自动化的方案之一。 showImg(https://segmentfault.com/img/bVbbRX6?w=1995&h=1273); 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 一. 前言 性能问题是导致 App 用户流失的罪魁...

    yacheng 评论0 收藏0
  • Android Gradle系列-进阶

    摘要:如果你有新建一个项目的经历,那么你将看到推荐的方案在的中使用来定义版本号全局变量。例如之前的版本号就可以使用如下方式实现因为使用的是语言,所以以上都是语法例如版本控制,上面代码的意思就是将有个相关的版本依赖放到的变量中,同时放到了中。 showImg(https://segmentfault.com/img/bVbsh3m?w=2560&h=1280); 上篇文章我们已经将Gradle...

    lvzishen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<