移动开发最新Gradle插件从入门到进阶,2024年最新字节面试官不想录取的暗示

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

进阶学习视频

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

@OutputDirectory
File destDir;

@TaskAction
void sayHello() {
println “Hello $username ! age is $age”
}

}

task test(type: SayHelloTask) {
age = 18
username = “hjy”
destDir = file(“$buildDir/test”)
}

Property

ext命名空间

Gradle中很多模型类都提供了特别的属性支持,比如Project.在gradle内部,这些属性会以键值对的形式存储。使用ext命名空间,我们可以方便的添加属性。下面的方式都是支持的:

//在project中添加一个名为groupId的属性
project.ext.groupId=“tech.easily”
// 使用ext块添加属性
ext{
artifactId=‘EasyDependency’
config=[
key:‘value’
]
}

值得注意的是,只有在声明属性的时候我们需要使用ext命名空间,在使用属性的时候,ext命名空间是可以省略的。

属性文件

正如我们经常在Android项目中看到的,我们可以在项目的根目录下新建一个gradle.properties文件,并在文件中定义简单的键值对形式的属性。这些属性能够被项目中的gradle脚本所访问。如下所示:

gradle.properties

注意文件的注释是以#开头的

groupId=tech.easily
artifactId=EasyDependency
复制代码

有的时候,我们可能需要在代码中动态的创建属性文件并读取文件中的属性(比如自定义插件的时候),我们可以使用java.util.Properties类。比如:

void createPropertyFile() {
def localPropFile = new File(it.projectDir.absolutePath + “/local.properties”)
def defaultProps = new Properties()
if (!localPropFile.exists()) {
localPropFile.createNewFile()
defaultProps.setProperty(“debuggable”, ‘true’)
defaultProps.setProperty(“groupId”, GROUP)
defaultProps.setProperty(“artifactId”, project.name)
defaultProps.setProperty(“versionName”, VERSION_NAME)
defaultProps.store(new FileWriter(localPropFile), “properties auto generated for resolve dependencies”)
} else {
localPropFile.withInputStream { stream ->
defaultProps.load(stream)
}
}
}

关于属性很重要的一点是属性是可以继承的。在一个项目中定义的属性会自动的被其子项目继承,不管我们是用以上哪种方式添加属性都是适用的。

ExtensionContainer

Extension简介

就是 Gradle 的 Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。

一般我们通过ExtensionContainer来创建Extension,这个类跟TaskContainer命名有点类似。TaskContainer是用来创建并管理Task的,而ExtensionContainer则是用来创建并管理Extension的,通过Project的以下API可以获取到ExtensionContainer对象

ExtensionContainer getExtensions()

简单的Extension

/先定义一个普通的java类,包含2个属性
class Foo {
int age
String username
String toString() {
return “name = ${username}, age = ${age}”
}
}
//创建一个名为 foo 的Extension
getExtensions().create(“foo”, Foo)
//配置Extension
foo {
age = 30
username = “hjy”
}
task testExt.doLast {
//能直接通过 project 获取到自定义的 Extension
println project.foo
}

foo 就是我们自定义的 Extension 了,它里面能配置的属性与类 Foo 中的字段是一致的,在 build.gradle 中可以直接通过 project.foo 来访问。每个 Extension 实际上与某个类是相关联的,在 build.gradle 中通过 DSL 来定义,Gradle 会识别解析并生成一个对象实例,通过该类可以获取我们所配置的信息。
Project 有个扩展属性是通过 ext 命名空间配置的,可以看到 ext 与这里是类似的,不同的是 ext 可以配置任何键值对的属性值,而这里只能识别我们定义的 Java 类里的属性值。

ExtensionContainer主要api及用法

T create(String name, Class type, Object… constructionArguments)
T create(Class publicType, String name, Class<? extends T> instanceType, Object… constructionArguments)

先来看看后面这个 API 所有参数的含义。

  • publicType:创建的 Extension 实例暴露出来的类类型;
  • name:要创建的Extension的名字,可以是任意符合命名规则的字符串,不能与已有的重复,否则会抛异常;
  • instanceType:该Extension的类类型;
  • constructionArguments:类的构造函数参数值

官方文档里还说明了一个特性,创建的 Extension 对象都默认实现了 ExtensionAware 接口,并注明出处。

示例

//父类
class Animal {

String username
int legs

Animal(String name) {
username = name
}

void setLegs(int c) {
legs = c
}

String toString() {
return “This animal is $username, it has ${legs} legs.”
}
}

//子类
class Pig extends Animal {

int age
String owner

Pig(int age, String owner) {
super(“Pig”)
this.age = age
this.owner = owner
}

String toString() {
return super.toString() + " Its age is $age, its owner is $owner."
}

}

//创建的Extension是 暴露出来Animal 类型,创建extension名称是name,该extension的类型是Pig,后面2个是参数
Animal aAnimal = getExtensions().create(Animal, “animal”, Pig, 3, “hjy”)
//创建的Extension是 Pig 类型
Pig aPig = getExtensions().create(“pig”, Pig, 5, “kobe”)

animal {
legs = 4 //配置属性
}

pig {
setLegs 2 //这个是方法调用,也就是 setLegs(2)
}

task testExt << {
println aAnimal
println aPig
//验证 aPig 对象是 ExtensionAware 类型的
println “aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}”
}

增加Extension

  • create() 方法会创建并返回一个 Extension 对象,
  • add() 方法,唯一的差别是它并不会返回一个 Extension 对象

基于前面的这个实例,我们可以换一种写法如下:

getExtensions().add(Pig, “mypig”, new Pig(5, “kobe”))
mypig {
username = “MyPig”
legs = 4
age = 1
}
task testExt << {
def aPig = project.getExtensions().getByName(“mypig”)
println aPig
}

查找Extension

Object findByName(String name)
T findByType(Class type)
Object getByName(String name) //找不到会抛异常
T getByType(Class type) //找不到会抛异常

嵌套Extension 方式一

类似下面这样的配置应该随处可见:

outer {

outerName “outer”
msg “this is a outer message.”
inner {
innerName “inner”
msg “This is a inner message.”
}

}

可以通过下面的方式来创建

class OuterExt {

String outerName
String msg
InnerExt innerExt = new InnerExt()

void outerName(String name) {
outerName = name
}

void msg(String msg) {
this.msg = msg
}

//创建内部Extension,名称为方法名 inner
void inner(Action action) {
action.execute(inner)
}

//创建内部Extension,名称为方法名 inner
void inner(Closure c) {
org.gradle.util.ConfigureUtil.configure(c, innerExt)
}

String toString() {
return "OuterExt[ name = ${outerName}, msg = ${msg}] " + innerExt
}

}

class InnerExt {

String innerName
String msg

void innerName(String name) {
innerName = name
}

void msg(String msg) {
this.msg = msg
}

String toString() {
return “InnerExt[ name = ${innerName}, msg = ${msg}]”
}

}

def outExt = getExtensions().create(“outer”, OuterExt)

outer {

outerName “outer”
msg “this is a outer message.”

inner {
innerName “inner”
msg “This is a inner message.”
}

}

task testExt doLast {
println outExt
}

关键在以下下面的方法

void inner(Action action)
void inner(Closure c)

定义在outer内部的inner,Gradle 解析时本质上会调用 outer.inner(……)方法,该方法的参数是一个闭包(Script Block) 所以在类OuterExt中必须定义inner方法

嵌套Extension方式二(NamedDomainObjectContainer)

使用场景

Gradle Extension 的时候,说到名为 android 的 Extension 是由 BaseExtension 这个类来实现的,里面对 buildTypes 是这样定义的:

private final NamedDomainObjectContainer buildTypes;

buildTypes 就是 NamedDomainObjectContainer 类型的,先来看看 buildTypes 在 Android 中是怎么使用的,下面这段代码应该都很熟悉了,它定义了 debug、relase 两种打包模式:

android {
buildTypes {
release {
// 是否开启混淆
minifyEnabled true
// 开启ZipAlign优化
zipAlignEnabled true
//去掉不用资源
shrinkResources true
// 混淆文件位置
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
// 使用release签名
signingConfig signingConfigs.hmiou
}
debug {
signingConfig signingConfigs.hmiou
}
}
}

当我们新建一个项目时候,默认会有debug和release这2个配置,那么debug、release可以修改其他名字吗,能增加其他名字来配置吗,比如想增加一个测试包配置test,还有就是release里面都能配置哪些属性呢
我来说下结果,如果不确定的,可以实际验证一下:

  • debug、release 是可以修改成其他名字的,你可以替换成你喜欢的名字;
  • 你可以增加任意不同名字的配置,比如增加一个开发版本的打包配置 dev ;
  • 可配置的属性可参考接口:com.android.builder.model.BuildType ;

可以看到它是非常灵活的,可以根据不同的场景定义不同的配置,每个不同的命名空间都会生成一个 BuildType 配置。要实现这样的功能,必须使用 NamedDomainObjectContainer 类型。

什么是NamedDomainObjectContainer

顾名思义就是命名领域对象容器,它的主要功能有:

  • 通过DSL创建指定type的对象实例
  • 指定的type必须有一个public构造函数,且必须带有一个String name的参数
  • 它是一个实现了SortedSet接口的容器,所以所有领域对象的name属性都必须是唯一的,在容器内部会用name属性来排序

named domain object container is a specialisation of NamedDomainObjectSet that adds the ability to create instances of the element type.  Note that a container is an implementation of SortedSet, which means that the container is guaranteed to only contain elements with unique names within this container. Furthermore, items are ordered by their name.

创建NamedDomainObjectContainer

NamedDomainObjectContainer 需要通过 Project.container(…) API 来创建,其定义为:

NamedDomainObjectContainer container(Class type)
NamedDomainObjectContainer container(Class type, NamedDomainObjectFactory factory)
NamedDomainObjectContainer container(java.lang.Class type, Closure factoryClosure

来看个具体的实例:

//这是领域对象类型定义
class TestDomainObj {

//必须定义一个 name 属性,并且这个属性值初始化以后不要修改
String name

String msg

//构造函数必须有一个 name 参数
public TestDomainObj(String name) {
this.name = name
}

void msg(String msg) {
this.msg = msg
}

String toString() {
return “name = ${name}, msg = ${msg}”
}
}

//创建一个扩展
class TestExtension {

//定义一个 NamedDomainObjectContainer 属性
NamedDomainObjectContainer testDomains

public TestExtension(Project project) {
//通过 project.container(…) 方法创建 NamedDomainObjectContainer
NamedDomainObjectContainer domainObjs = project.container(TestDomainObj)
testDomains = domainObjs
}

//让其支持 Gradle DSL 语法
void testDomain(Action<NamedDomainObjectContainer> action) {
action.execute(testDomains)
}

void test() {
//遍历命名领域对象容器,打印出所有的领域对象值
testDomains.all { data ->
println data
}
}
}

//创建一个名为 test 的 Extension
def testExt = getExtensions().create(“test”, TestExtension, project)

test {
testDomain {
domain2 {
msg “This is domain2”
}
domain1 {
msg “This is domain1”
}
domain3 {
msg “This is domain3”
}
}
}

task myTask doLast {
testExt.test()
}

运行结果如下:

name = domain1, msg = This is domain1
name = domain2, msg = This is domain2
name = domain3, msg = This is domain3

查找和遍历

NamedDomainObjectContainer 既然是一个容器类,与之相应的必然会有查找容器里的元素和遍历容器的方法:

//遍历
void all(Closure action)
//查找
T getByName(String name)
//查找
T findByName(String name)

还是接着前面的例子:

//通过名字查找
TestDomainObj testData = testDomains.getByName(“domain2”)
println “getByName: ${testData}”
//遍历命名领域对象容器,打印出所有的领域对象值
testDomains.all { data ->
println data
}

需要注意的是,Gradle 中有很多容器类的迭代遍历方法有 each(Closure action)、all(Closure action),但是一般我们都会用 all(…) 来进行容器的迭代。all(…) 迭代方法的特别之处是,不管是容器内已存在的元素,还是后续任何时刻加进去的元素,都会进行遍历。

Android的Extension

我们在gradle中会看到 android{}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

defaultConfig、productFlavors、signingConfigs、buildTypes 这4个内部 Extension对象是怎么定义的,通过查看源码可以找到一个叫 BaseExtension 的类,里面的相关代码如下:

private final DefaultConfig defaultConfig;
private final NamedDomainObjectContainer productFlavors;
private final NamedDomainObjectContainer buildTypes;
private final NamedDomainObjectContainer signingConfigs;
public void defaultConfig(Action action) {
this.checkWritability();
action.execute(this.defaultConfig);
}

public void buildTypes(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.buildTypes);
}
public void productFlavors(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.productFlavors);
}
public void signingConfigs(Action<? super NamedDomainObjectContainer> action) {
this.checkWritability();
action.execute(this.signingConfigs);
}

在 app 的 build.gradle 里我们通常会采用插件 apply plugin: ‘com.android.application’ ,而在 library module 中则采用插件 apply plugin: ‘com.android.library’,AppPlugin 就是插件 com.android.application 的实现类,LibraryPlugin 则是插件 com.android.library 的实现类,接着再看看 AppPlugin 里是怎样创建 Extension 的:

public class AppPlugin extends BasePlugin implements Plugin {
@Inject
public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
super(instantiator, registry);
}
protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer buildTypeContainer, NamedDomainObjectContainer productFlavorContainer, NamedDomainObjectContainer signingConfigContainer, NamedDomainObjectContainer buildOutputs, ExtraModelInfo extraModelInfo) {
return (BaseExtension)project.getExtensions().create(“android”, AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
}
public void apply(Project project) {
super.apply(project);
}
//省略…
}

在 createExtension() 方法中,可以看到创建了一个名为 android 的 Extension,该 Extension 的类型为 AppExtension,而 AppExtension 的继承结构为 AppExtension -> TestedExtension -> BaseExtension,所以它的实现逻辑大部分都是在 BaseExtension 里实现的。

在Android 工程中的build.gradle 文件中,我们配置相关信息使用 android{} 节点,从 AppPlugin 也能看出其 Extension的名称为 android ,所以获取方法如下:

  • project.extensions.getByName
  • project.extensions.getByType

def getInfo() {
//或者 直接 project.android
BaseExtension extension = project.extensions.getByName(“android”)
def android = project.extensions.getByType(AppExtension)
project.android

println “buildToolsVersion: e x t e n s i o n . b u i l d T o o l s V e r s i o n " p r i n t l n " c o m p i l e S d k V e r s i o n : {extension.buildToolsVersion}" println "compileSdkVersion: extension.buildToolsVersion"println"compileSdkVersion:{extension.getCompileSdkVersion()}”
println “applicationId: e x t e n s i o n . d e f a u l t C o n f i g . a p p l i c a t i o n I d " p r i n t l n " m i n S d k V e r s i o n : {extension.defaultConfig.applicationId}" println "minSdkVersion: extension.defaultConfig.applicationId"println"minSdkVersion:{extension.defaultConfig.minSdkVersion}”
println “targetSdkVersion: e x t e n s i o n . d e f a u l t C o n f i g . t a r g e t S d k V e r s i o n " p r i n t l n " v e r s i o n C o d e : {extension.defaultConfig.targetSdkVersion}" println "versionCode: extension.defaultConfig.targetSdkVersion"println"versionCode:{extension.defaultConfig.versionCode}”
println “versionName:${extension.defaultConfig.versionName}”
}

更详细的请参考

3、构建生命周期

三个阶段

每次构建的本质其实就是执行一系列的Task,某些Task可能依赖其他Task,那些没有依赖的Task总会被最先执行,而且每个Task只会被执行一遍,每次构建的依赖关系是在构建的配置阶段确定的,在gradle构建中,构建的生命周期主要包括以下三个阶段:

初始化(Initialization)

构建工具会根据每个build.gradle文件创建出一个Project实例,初始化阶段会执行项目根目录下的Settings.gradle文件,来分析哪些项目参与构建

include ‘:app’

include ‘:libraries:someProject’

配置(Configuration)

这个阶段通过执行构建脚本来为每个project创建并分配Task。配置阶段会去加载所有参与构建的项目的build.gradle文件,会将build.gradle文件实例化为一个Gradle的project对象,然后分析project之间的依赖关系,下载依赖文件,分析project下的task之间的依赖关系

执行(Execution)

这是Task真正被执行的阶段,Gradle会根据依赖关系决定哪些Task需要被执行,以及执行的先后顺序。
task是Gradle中的最小执行单元,我们所有的构建,编译,打包,debug,test等都是执行了某一个task,一个project可以有多个task,task之间可以互相依赖。例如我有两个task,taskA和taskB,指定taskA依赖taskB,然后执行taskA,这时会先去执行taskB,taskB执行完毕后在执行taskA。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在根目录和app目录下的build.gradle中会引用下面的插件

dependencies { classpath ‘com.android.tools.build:gradle:2.2.2’ }

apply plugin: ‘com.android.application’

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Android 三个文件重要的gradle

Gradle项目有3个重要的文件需要深入理解:

  • settings.gradle

settings.gradle 文件会在构建的 initialization 阶段被执行,它用于告诉构建系统哪些模块需要包含到构建过程中。对于单模块项目, settings.gradle 文件不是必需的。对于多模块项目,如果没有该文件,构建系统就不能知道该用到哪些模块。

  • 项目根目录的 build.gradle

项目根目录的 build.gradle 文件用来配置针对所有模块的一些属性。它默认包含2个代码块:buildscript{…}和allprojects{…}。前者用于配置构建脚本所用到的代码库和依赖关系,后者用于定义所有模块需要用到的一些公共属性。

buildscript {
repositories {
jcenter()
}
dependencies {
classpath ‘com.android.tools.build:gradle:2.3.2’
}
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

buildscript:定义了 Android 编译工具的类路径。repositories中, jCenter是一个著名的 Maven 仓库。
allprojects:中定义的属性会被应用到所有 moudle 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。

  • 模块目录的 build.gradle

模块级配置文件 build.gradle 针对每个moudle 的配置,如果这里的定义的选项和顶层 build.gradle定义的相同。它有3个重要的代码块:plugin,android 和 dependencies。

常用gradle命令

//构建
gradlew app:clean //移除所有的编译输出文件,比如apk

gradlew app:build //构建 app module ,构建任务,相当于同时执行了check任务和assemble任务

//检测
gradlew app:check //执行lint检测编译。

//打包
gradlew app:assemble //可以编译出release包和debug包,可以使用gradlew assembleRelease或者gradlew assembleDebug来单独编译一种包

gradlew app:assembleRelease //app module 打 release 包

gradlew app:assembleDebug //app module 打 debug 包

//安装,卸载

gradlew app:installDebug //安装 app 的 debug 包到手机上

gradlew app:uninstallDebug //卸载手机上 app 的 debug 包

gradlew app:uninstallRelease //卸载手机上 app 的 release 包

gradlew app:uninstallAll //卸载手机上所有 app 的包

监听生命周期

在gradle的构建过程中,gradle为我们提供了非常丰富的钩子,帮助我们针对项目的需求定制构建的逻辑,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要监听这些生命周期,主要有两种方式:

  • 添加监听器
  • 使用钩子的配置块

关于可用的钩子可以参考GradleProject中的定义,常用的钩子包括:

Project

Project提供的生命周期回调方法有

//在 Project 进行配置前调用
void beforeEvaluate(Closure closure)
//在 Project 配置结束后调用
void afterEvaluate(Closure closure)

beforeEvaluate 必须在父模块的 build.gradle 对子模块进行配置才能生效,因为在当前模块的 build.gradle 中配置,它自己本身都没配置好,所以不会监听到。

settings.gradle 代码:

include “:app”

build.gradle 代码:

//对子模块进行配置
subprojects { sub ->
sub.beforeEvaluate { proj ->
println “子项目beforeEvaluate回调…”
}
}
println “根项目配置开始—”
task rootTest {
println “根项目里任务配置—”
doLast {
println “执行根项目任务…”
}
}
println “根项目配置结束—”

app/build.gradle 代码:

println “APP子项目配置开始—”
afterEvaluate {
println “APP子项目afterEvaluate回调…”
}
task appTest {
println “APP子项目里任务配置—”
doLast {
println “执行子项目任务…”
}
}
println “APP子项目配置结束—”

在根目录执行:gradle -q,结果如下:

根项目配置开始—
根项目里任务配置—
根项目配置结束—
子项目beforeEvaluate回调…
APP子项目配置开始—
APP子项目里任务配置—
APP子项目配置结束—
APP子项目afterEvaluate回调…

project.android 获取到AppExtension:

Gradle

Gradle 提供的生命周期回调方法很多,部分与 Project 里的功能雷同:

//在project进行配置前调用,child project必须在root project中设置才会生效,root project必须在settings.gradle中设置才会生效
void beforeProject(Closure closure)
//在project配置后调用
afterProject(Closure closure)
//构建开始前调用
void buildStarted(Closure closure)
//构建结束后调用
void buildFinished(Closure closure)
//所有project配置完成后调用
void projectsEvaluated(Closure closure)
//当settings.gradle中引入的所有project都被创建好后调用,只在该文件设置才会生效
void projectsLoaded(Closure closure)
//settings.gradle配置完后调用,只对settings.gradle设置生效
void settingsEvaluated(Closure closure)

  • beforeProject()/afterProject()
    等同于Project中的beforeEvaluateafterEvaluate
  • settingsEvaluated()
    settings脚本被执行完毕,Settings对象配置完毕
  • projectsLoaded()
    所有参与构建的项目都从settings中创建完毕
  • projectsEvaluated()
    所有参与构建的项目都已经被评估完

我们修改 setting.gradle 的代码如下:

gradle.settingsEvaluated {
println “settings:执行settingsEvaluated…”
}
gradle.projectsLoaded {
println “settings:执行projectsLoaded…”
}
gradle.projectsEvaluated {
println “settings: 执行projectsEvaluated…”
}
gradle.beforeProject { proj ->
println “settings:执行KaTeX parse error: Expected 'EOF', got '}' at position 28: …beforeProject" }̲ gradle.afterPr…{proj.name} afterProject”
}
gradle.buildStarted {
println “构建开始…”
}
gradle.buildFinished {
println “构建结束…”
}
include “:app”

这个时候的执行结果如下:

settings:执行settingsEvaluated…
settings:执行projectsLoaded…
settings:执行test beforeProject
根项目配置开始—
根项目里任务配置—
根项目配置结束—
settings:执行test afterProject
settings:执行app beforeProject
子项目beforeEvaluate回调…
APP子项目配置开始—
APP子项目里任务配置—
APP子项目配置结束—
settings:执行app afterProject
APP子项目afterEvaluate回调…
settings: 执行projectsEvaluated…
构建结束…

可以看到 gradle.beforeProject 与 project.beforeEvaluate 是类似的,同样 afterProject 与 afterEvaluate 也是类似的。

除此之外,Gradle 还有一个通用的设置生命周期监听器的方法:addListener

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上面的 BuildListener、ProjectEvaluationListener 等与前面的部分 API 功能是一致的,这里不再赘述了。

TaskExecutionGraph(Task执行图)

Gradle 在配置完成后,会对所有的 task 生成一个有向无环图,这里叫做 task 执行图,他们决定了 task 的执行顺序等。同样,Gradle 可以对 task 的执行生命周期进行监听。

//任务执行前掉用

学习福利

【Android 详细知识点思维脑图(技能树)】

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Gradle 在配置完成后,会对所有的 task 生成一个有向无环图,这里叫做 task 执行图,他们决定了 task 的执行顺序等。同样,Gradle 可以对 task 的执行生命周期进行监听。

//任务执行前掉用

学习福利

【Android 详细知识点思维脑图(技能树)】

[外链图片转存中…(img-yBUxOXx6-1715449442568)]

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-xVwVIKdA-1715449442568)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值