Android最新大厂面试真题总结,深度探索 Gradle 自动化构建技术(1),拥有百万粉丝的大牛讲述学Android的历程

repositories {
google()
jcenter()
mavenCentral()
maven {
url “https://jitpack.io”
}
maven { url “https://plugins.gradle.org/m2/” }
}
}

在 allprojects 中我们一般用来配置一些通用的配置,比如上面最常见的全局仓库配置。

7、subprojects

subprojects 可以 统一配置当前 project 下的所有子 project,示例代码如下所示:

/**

  • 7、subprojects 使用示例:
  • 给所有的子工程引入 将 aar 文件上传置 Maven 服务器的配置脚本
    */
    subprojects {
    if (project.plugins.hasPlugin(“com.android.library”)) {
    apply from: ‘…/publishToMaven.gradle’
    }
    }

在上述示例代码中,我们会先判断当前 project 旗下的子 project 是不是库,如果是库才有必要引入 publishToMaven 脚本。

3、project 属性

目前,在 project 接口里,仅仅预先定义了 七个 属性,其源码如下所示:

public interface Project extends Comparable, ExtensionAware, PluginAware {
/**

  • 默认的工程构建文件名称
    */
    String DEFAULT_BUILD_FILE = “build.gradle”;

/**

  • 区分开 project 名字与 task 名字的符号
    */
    String PATH_SEPARATOR = “:”;

/**

  • 默认的构建目录名称
    */
    String DEFAULT_BUILD_DIR_NAME = “build”;

String GRADLE_PROPERTIES = “gradle.properties”;

String SYSTEM_PROP_PREFIX = “systemProp”;

String DEFAULT_VERSION = “unspecified”;

String DEFAULT_STATUS = “release”;


}

幸运的是,Gradle 提供了 ext 关键字让我们有能力去定义自身所需要的扩展属性。有了它便可以对我们工程中的依赖进行全局配置。下面,我们先从配置的远古时代讲起,以便让我们对 gradle 的 全局依赖配置有更深入的理解。

ext 扩展属性

1、远古时代

在 AS 刚出现的时候,我们的依赖配置代码是这样的:

android {
compileSdkVersion 27
buildToolsVersion “28.0.3”

}

2、刀耕火种

但是这种直接写值的方式显示是不规范的,因此,后面我们使用了这种方式:

def mCompileSdkVersion = 27
def mBuildToolsVersion = “28.0.3”

android {
compileSdkVersion mCompileSdkVersion
buildToolsVersion mBuildToolsVersion

}

3、铁犁牛耕

如果每一个子 project 都需要配置相同的 Version,我们就需要多写很多的重复代码,因此,我们可以利用上面我们学过的 subproject 和 ext 来进行简化:

// 在根目录下的 build.gradle 中
subprojects {
ext {
compileSdkVersion = 27
buildToolsVersion = “28.0.3”
}
}

// 在 app moudle 下的 build.gradle 中
android {
compileSdkVersion this.compileSdkVersion
buildToolsVersion this.buildToolsVersion

}

4、工业时代

使用 subprojects 方法来定义通用的扩展属性还是存在着很严重的问题,它跟之前的方式一样,还是会在每一个子 project 去定义这些被扩展的属性,此时,我们可以将 subprojects 去除,直接使用 ext 进行全局定义即可:

// 在根目录下的 build.gradle 中
ext {
compileSdkVersion = 27
buildToolsVersion = “28.0.3”
}

5、电器时代

当项目越来越大的时候,在根项目下定义的 ext 扩展属性越来越多,因此,我们可以将这一套全局属性配置在另一个 gradle 脚本中进行定义,这里我们通常会将其命名为 config.gradle,通用的模板如下所示:

ext {

android = [
compileSdkVersion : 27,
buildToolsVersion : “28.0.3”,

]

version = [
supportLibraryVersion : “28.0.0”,

]

dependencies = [
// base
“appcompat-v7” : “com.android.support:appcompat-v7:${version[“supportLibraryVersion”]}”,

]

annotationProcessor = [
“glide_compiler” : “com.github.bumptech.glide:compiler:${version[“glideVersion”]}”,

]

apiFileDependencies = [
“launchstarter” : “libs/launchstarter-release-1.0.0.aar”,

]

debugImplementationDependencies = [
“MethodTraceMan” : “com.github.zhengcx:MethodTraceMan:1.0.7”
]

releaseImplementationDependencies = [
“MethodTraceMan” : “com.github.zhengcx:MethodTraceMan:1.0.5-noop”
]


}

6、更加智能化的现在

尽管有了很全面的全局依赖配置文件,但是,在我们的各个模块之中,还是不得不写一大长串的依赖代码,因此,我们可以 使用遍历的方式去进行依赖,其模板代码如下所示:

// 在各个 moulde 下的 build.gradle 脚本下
def implementationDependencies = rootProject.ext.dependencies
def processors = rootProject.ext.annotationProcessor
def apiFileDependencies = rootProject.ext.apiFileDependencies

// 在各个 moulde 下的 build.gradle 脚本的 dependencies 闭包中
// 处理所有的 aar 依赖
apiFileDependencies.each { k, v -> api files(v)}

// 处理所有的 xxximplementation 依赖
implementationDependencies.each { k, v -> implementation v }
debugImplementationDependencies.each { k, v -> debugImplementation v }

// 处理 annotationProcessor 依赖
processors.each { k, v -> annotationProcessor v }

// 处理所有包含 exclude 的依赖
debugImplementationExcludes.each { entry ->
debugImplementation(entry.key) {
entry.value.each { childEntry ->
exclude(group: childEntry.key, module: childEntry.value)
}
}
}

也许未来随着 Gradle 的不断优化会有更加简洁的方式,如果你有更好地方式,我们可以来探讨一番。

在 gradle.properties 下定义扩展属性

除了使用 ext 扩展属性定义额外的属性之外,我们也可以在 gradle.properties 下定义扩展属性,其示例代码如下所示:

// 在 gradle.properties 中
mCompileVersion = 27

// 在 app moudle 下的 build.gradle 中
compileSdkVersion mCompileVersion.toInteger()

4、文件相关 API

在 gradle 中,文件相关的 API 可以总结为如下 两大类

  • 1)、路径获取 API
  • getRootDir()
  • getProjectDir()
  • getBuildDir()
  • 2)、文件操作相关 API
  • 文件定位
  • 文件拷贝
  • 文件树遍历

1)、路径获取 API

关于路径获取的 API 常用的有 三种,其示例代码如下所示:

/**

  • 1、路径获取 API
    */
    println “the root file path is:” + getRootDir().absolutePath
    println “this build file path is:” + getBuildDir().absolutePath
    println “this Project file path is:” + getProjectDir().absolutePath

然后,我们执行 ./gradlew clean,输出结果如下所示:

Configure project :
the root file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
this build file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid/build
this Project file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
配置阶段,root project 'Awesome-WanAndroid’耗时:538ms

2)、文件操作相关 API

1、文件定位

常用的文件定位 API 有 file/files,其示例代码如下所示:

// 在 rootProject 下的 build.gradle 中

/**

  • 1、文件定位之 file
    */
    this.getContent(“config.gradle”)

def getContent(String path) {
try {
// 不同与 new file 的需要传入 绝对路径 的方式,
// file 从相对于当前的 project 工程开始查找
def mFile = file(path)
println mFile.text
} catch (GradleException e) {
println e.toString()
return null
}
}

/**

  • 1、文件定位之 files
    */
    this.getContent(“config.gradle”, “build.gradle”)

def getContent(String path1, String path2) {
try {
// 不同与 new file 的需要传入 绝对路径 的方式,
// file 从相对于当前的 project 工程开始查找
def mFiles = files(path1, path2)
println mFiles[0].text + mFiles[1].text
} catch (GradleException e) {
println e.toString()
return null
}
}

2、文件拷贝

常用的文件拷贝 API 为 copy,其示例代码如下所示:

/**

  • 2、文件拷贝
    */
    copy {
    // 既可以拷贝文件,也可以拷贝文件夹
    // 这里是将 app moudle 下生成的 apk 目录拷贝到
    // 根工程下的 build 目录
    from file(“build/outputs/apk”)
    into getRootProject().getBuildDir().path + “/apk/”
    exclude {
    // 排除不需要拷贝的文件
    }
    rename {
    // 对拷贝过来的文件进行重命名
    }
    }
3、文件树遍历

我们可以 使用 fileTree 将当前目录转换为文件数的形式,然后便可以获取到每一个树元素(节点)进行相应的操作,其示例代码如下所示:

/**

  • 3、文件树遍历
    */
    fileTree(“build/outputs/apk”) { FileTree fileTree ->
    fileTree.visit { FileTreeElement fileTreeElement ->
    println “The file is $fileTreeElement.file.name”
    copy {
    from fileTreeElement.file
    into getRootProject().getBuildDir().path + “/apkTree/”
    }
    }
    }

5、其它 API

1、依赖相关 API

根项目下的 buildscript

buildscript 中 用于配置项目核心的依赖。其原始的使用示例与简化后的使用示例分别如下所示:

原始的使用示例

buildscript { ScriptHandler scriptHandler ->
// 配置我们工程的仓库地址
scriptHandler.repositories { RepositoryHandler repositoryHandler ->
repositoryHandler.google()
repositoryHandler.jcenter()
repositoryHandler.mavenCentral()
repositoryHandler.maven { url ‘https://maven.google.com’ }
repositoryHandler.maven { url “https://plugins.gradle.org/m2/” }
repositoryHandler.maven {
url uri(‘…/PAGradlePlugin/repo’)
}
// 访问本地私有 Maven 服务器
repositoryHandler.maven {
name “personal”
url “http://localhost:8081:/JsonChao/repositories”
credentials {
username = “JsonChao”
password = “123456”
}
}
}

// 配置我们工程的插件依赖
dependencies { DependencyHandler dependencyHandler ->
dependencyHandler.classpath ‘com.android.tools.build:gradle:3.1.4’


}

简化后的使用示例

buildscript {
// 配置我们工程的仓库地址
repositories {
google()
jcenter()
mavenCentral()
maven { url ‘https://maven.google.com’ }
maven { url “https://plugins.gradle.org/m2/” }
maven {
url uri(‘…/PAGradlePlugin/repo’)
}
}

// 配置我们工程的插件依赖
dependencies {
classpath ‘com.android.tools.build:gradle:3.1.4’


}

app moudle 下的 dependencies

不同于 根项目 buildscript 中的 dependencies 是用来配置我们 Gradle 工程的插件依赖的,而 app moudle 下的 dependencies 是用来为应用程序添加第三方依赖的。关于 app moudle 下的依赖使用这里我们 需要注意下 exclude 与 transitive 的使用 即可,示例代码如下所示:

implementation(rootProject.ext.dependencies.glide) {
// 排除依赖:一般用于解决资源、代码冲突相关的问题
exclude module: ‘support-v4’
// 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,
// 且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B
// 中所使用的 C 中的依赖,默认都是不打开,即 false
transitive false
}

2、外部命令执行

我们一般是 使用 Gradle 提供的 exec 来执行外部命令,下面我们就使用 exec 命令来 将当前工程下新生产的 APK 文件拷贝到 电脑下的 Downloads 目录中,示例代码如下所示:

/**

  • 使用 exec 执行外部命令
    */
    task apkMove() {
    doLast {
    // 在 gradle 的执行阶段去执行
    def sourcePath = this.buildDir.path + “/outputs/apk/speed/release/”
    def destinationPath = “/Users/quchao/Downloads/”
    def command = “mv -f $sourcePath $destinationPath”
    exec {
    try {
    executable “bash”
    args “-c”, command
    println “The command execute is success”
    } catch (GradleException e) {
    println “The command execute is failed”
    }
    }
    }
    }

四、Task

只有 Task 才可以在 Gradle 的执行阶段去执行(其实质是执行的 Task 中的一系列 Action),所以 Task 的重要性不言而喻。

1、从一个例子 🌰 出发

首先,我们可以在任意一个 build.gradle 文件中可以去定义一个 Task,下面是一个完整的示例代码:

// 1、声明一个名为 JsonChao 的 gradle task
task JsonChao
JsonChao {
// 2、在 JsonChao task 闭包内输出 hello~,
// 执行在 gradle 生命周期的第二个阶段,即配置阶段。
println(“hello~”)
// 3、给 task 附带一些 执行动作(Action),执行在
// gradle 生命周期的第三个阶段,即执行阶段。
doFirst {
println(“start”)
}
doLast {
println(“end”)
}
}
// 4、除了上述这种将声明与配置、Action 分别定义
// 的方式之外,也可以直接将它们结合起来。
// 这里我们又定义了一个 Android task,它依赖于 JsonChao
// task,也就是说,必须先执行完 JsonChao task,才能
// 去执行 Android task,由此,它们之间便组成了一个
// 有向无环图:JsonChao task => Android task
task Andorid(dependsOn:“JsonChao”) {
doLast {
println(“end?”)
}
}

首先,在注释1处,我们声明了一个名为 JsonChao 的 gradle task。接着,在注释2处,在 JsonChao task 闭包内输出了 hello~,这里的代码将会执行在 gradle 生命周期的第二个阶段,即配置阶段。然后,在注释3处,这里 给 task 附带一些了一些执行动作(Action),即 doFirst 与 doLast,它们闭包内的代码将执行在 gradle 生命周期的第三个阶段,即执行阶段

对于 doFirst 与 doLast 这两个 Action,它们的作用分别如下所示:

  • doFirst表示 task 执行最开始的时候被调用的 Action
  • doLast表示 task 将执行完的时候被调用的 Action

需要注意的是,doFirst 和 doLast 是可以被执行多次的

最后,注释4处,我们可以看到,除了注释1、2、3处这种将声明与配置、Action 分别定义的方式之外,也可以直接将它们结合起来。在这里我们又定义了一个 Android task,它依赖于 JsonChao task,也就是说,必须先执行完 JsonChao task,才能 去执行 Android task,由此,它们之间便组成了一个 有向无环图:JsonChao task => Android task

执行 Android 这个 gradle task 可以看到如下输出结果:

Task :JsonChao
start
end
执行阶段,task ':JsonChao’耗时:1ms
:JsonChao spend 4ms
Task :Andorid
end?
执行阶段,task ':Andorid’耗时:1ms
:Andorid spend 2ms
构建结束
Tasks spend time > 50ms:
执行阶段,耗时:15ms

2、Task 的定义及配置

Task 常见的定义方式有 两种,示例代码如下所示:

// Task 定义方式1:直接通过 task 函数去创建(在 “()” 可以不指定 group 与 description 属性)
task myTask1(group: “MyTask”, description: “task1”) {
println “This is myTask1”
}

// Task 定义方式2:通过 TaskContainer 去创建 task
this.tasks.create(name: “myTask2”) {
setGroup(“MyTask”)
setDescription(“task2”)
println “This is myTask2”
}

定义完上述 Task 之后再同步项目,即可看到对应的 Task Group 及其旗下的 Tasks。

Task 的属性

需要注意的是,不管是哪一种 task 的定义方式,在 “()” 内我们都可以配置它的一系列属性,如下:

project.task(‘JsonChao3’, group: “JsonChao”, description: “my tasks”,
dependsOn: [“JsonChao1”, “JsonChao2”] ).doLast {
println “execute JsonChao3 Task”
}

目前 官方所支持的属性 可以总结为如下表格:

选型描述默认值
“name”task 名字无,必须指定
“type”需要创建的 task ClassDefaultTask
“action”当 task 执行的时候,需要执行的闭包 closure 或 行为 Actionnull
“overwrite”替换一个已存在的 taskfalse
“dependsOn”该 task 所依赖的 task 集合[]
“group”该 task 所属组null
“description”task 的描述信息null
“constructorArgs”传递到 task Class 构造器中的参数null
使用 “$” 来引用另一个 task 的属性

在这里,我们可以 在当前 task 中使用 “$” 来引用另一个 task 的属性,示例代码如下所示:

task Gradle_First() {

}

task Gradle_Last() {
doLast {
println “I am not $Gradle_First.name”
}
}

使用 ext 给 task 自定义需要的属性

当然,除了使用已有的属性之外,我们也可以 使用 ext 给 task 自定义需要的属性,代码如下所示:

task Gradle_First() {
ext.good = true
}

task Gradle_Last() {
doFirst {
println Gradle_First.good
}
doLast {
println “I am not $Gradle_First.name”
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

欢迎大家一起交流讨论啊~

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-alWPBHxx-1712398277572)]

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-i37XB8lN-1712398277573)]

欢迎大家一起交流讨论啊~

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值