百度T6团队实力打造!Gradle 与 Android 构建入门 系统教程,我先收藏了

本文主要是介绍百度T6团队实力打造!Gradle 与 Android 构建入门 系统教程,我先收藏了,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


##声明:

原文发布:百度APP技术 【原创: xuduokai】
链接:https://juejin.im/post/5e9059bf6fb9a03c621679cf

前言

无论是否意识到 Gradle 的存在,每位 Android 程序员都会直接或间接的与 Gradle 打交道。每当通过 Android Studio 新建一个工程时,AS 都会自动创建一个通用的目录结构,然后就可以进行开发,在 app 的 build.gradle 中添加一些依赖,点击右上角的 Sync Now,编写代码,点击绿色小箭头 Run 运行代码,一切都这么美好,除了偶尔Sync Now 会失败变为 Try Again,如果经过几次 Try Again,问题还没有解决,打开浏览器,粘贴对应报错,查找解决方案,复制粘贴解决方案,点击 Try Again,问题解决,生活还是那么美好。

如果你想要了解问题为什么会发生,为什么这样做会解决问题,那么你需要脱离 AS 那几个常见的按钮,探索它背后的秘密—Gradle,这一自动化构建工具。

本文将介绍:

  1. 为什么需要自动化构建工具?
  2. 默认创建的 Android 工程都有什么
  3. 依赖管理
  4. 打包流程

通过阅读本文,可以大致了解 Gradle 是如何工作,可以有针对性搜索相关内容,更加快速的解决常见编译错误

为什么需要自动化构建工具?

以下命令只是示例,方便说明问题,具体使用方式请查找相关命令手册用法

我们知道,一个 APK 包其实是一个 zip 包,包含代码和资源。那么我们可以编写一个Shell 脚本,命名为 assemble.sh,任何人只要通过执行这个脚本就可以得到 apk 包,完美:

  1. 将 .java 文件转换为 .class 文件,执行命令: javac xxx.java
  2. Android 还会将 .class 文件转换为 .dex 文件: dx xxx.class
  3. 打包成 apk: zip xxx.apk [需要打包的代码和资源]

在 Android 中代码对资源是通过 R.java 文件引用,于是需要继续添加命令,并要求这个命令在 javac 命令前执行。在实际开发中我们不可能所有功能都自己实现,有可能会依赖优秀的开源库,修改后的伪代码如下:

  1. 生成 R.java: aapt [资源文件]
  2. 将 .java 文件转换为 .class 文件,执行命令: javac xxx.java R.java -classpath xxx.jar
  3. Android 还会将 .class 文件转换为 .dex 文件: dx xxx.class R.class xxx.jar
  4. 打包成 apk: zip xxx.apk [需要打包的代码和资源]

一切似乎都尽在掌握之中,真的吗?让我们看看 Android APK 实际打包的流程是什么样的:

想想实现如此复杂流程的 Shell 脚本是不是有些头大?别急,实现后还会遇到下面这些问题:

  • 对于多个工程,每个工程都需要拷贝上述 Shell 脚本
  • 对于单个工程,每次添加一个功能都需要在原有流程中插入一段代码,随着需求增加,脚本难以维护
  • 如何管理引入的外部依赖?如何打 debug、release 包?如何打多渠道包?

此时我们需要一个简化上述过程的工具,通过一些约定,如将代码、资源等放在指定目录,再辅以构建脚本就可以快速得到最终的构建产物,这就是自动化构建工具,而 Gradle 就是其中一个。

对照刚刚那个简单的例子,每一个工程在 Gradle 中叫做一个 Project,每一个需要执行的任务,如生成 R 文件、编译 java 文件等,在 Gradle 中叫做一个 Task。通过 TaskA.dependsOn(TaskB)可以实现先执行 TaskB 再执行 TaskA 的效果。同时 Gradle 也提供 doFirst、doLast 允许在每个 Task 前和后执行一些代码。

至此,我们知道为什么需要自动化构建工具:

  • 防止手动介入构建
  • 创建可重复的构建
  • 以及最重要的:提升编程效率,将精力集中在需求开发上

默认创建的 Android 工程都有什么

每当通过 Android Studio 新建一个工程时,AS 都会自动创建如上图所示的目录结构,图片中简单介绍了各个目录是干什么的,接下来为大家详细介绍每一个目录或者文件的含义:

.gradle 与 .idea

.gradle 与 .idea 存放 Gradle 和 AS 对于当前工程的缓存。

最常见的一个应用就是点击 sync 后,AS 会在每个工程下生成 .iml 文件,他们与 .gradle、.idea 配合为我们提供了代码提示等常见功能。所以如果你的代码飘红而你确认依赖没有问题,可以尝试下面步骤清除 AS 缓存:

  • 删.idea 删.gradle 文件
  • 命令行执行 ./gradlew clean
  • 选择 File -> invalidate caches/restart
  • Sync

gradle/wrapper 与 gradlew gradlew.bat

当我们初次配置 Android 环境时,需要安装 Java,安装 AS,但并不需要安装 Gradle,这其中就是 gradle/wrapper 的功劳。

当执行 gradlew 脚本时,它可以保证每个项目都使用自己期望的 Gradle 版本,而其中的奥秘就在 gradlew 的这段代码中

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

gradlew 并没有直接启动 Gradle 而是启动 gradle-wrapper.jar,它会判断如果没有 Gradle 环境,从 gradle-wrapper.properties 中的 distributionUrl 中下载相应环境,并启动 Gradle。

因为 Gradle 允许命令行启动时附加参数来自定义 Gradle 的运行环境,所以百度app通过自定义 gradle-wrapper.jar,实现通过配置文件为不同内存大小的电脑、debug/release 包指定不同 gradle 运行内存,提升大家编译速度。

setting.gradle

setting.gradle 中最关键的就是其提供的 include 方法,通过这个方法可以指定哪些工程需要参与编译,每一个参与编译的工程 Gradle 会为它创建一个 Project 对象

根目录 build.gradle

首先是 buildscript 代码块: gradle 默认是自顶向下执行,无论 buildscript 代码块在哪,它都会第一个执行

接下来是 repositories 和 dependencies: repositories 表示 dependencies 声明的依赖去哪些仓库找,google、jcenter、mavenCentral 都是第三方 Maven 仓库。同时,也可以通过 maven 方法添加自己的 Maven 仓库。需要注意的是,不应该假设组件一定会从特定仓库拉取,如果 Gradle 请求一个仓库超时,它会自动请求其他仓库。

dependencies:代表 Gradle 执行需要哪些依赖。比如需要 Android Gradle Plugin 插件为我们打包 apk 包,就需要添加: classpath ‘com.android.tools.build:gradle:3.4.0’

最后是 allprojects 和 repositories: 在 allprojects 中的配置会对所有工程生效而里面的 repositories 则表示工程声明的 dependencies 去哪些仓库查找

app build.gradle

首先可以看到 apply plugin: ‘com.android.application’,当应用这个插件后,它会为我们创建一系列 Task,比如 assembleDebug、assembleRelease,执行这些 Task,就会得到最终的 APK。

android 代码块是插件为我们提供的 API允许我们修改 Task 的行为。

dependencies 代码块的内容决定当前 Project 依赖哪些组件,而不同的依赖声明会有不同的结果,具体内容我们在下一节分析。

依赖管理

依赖配置

在 Android Gradle Plugin 3.0 时代,Google 使用 implementation 和 api 选项取代过去的 compile 选项。既然接口都变了,Google 索性将其他的配置项也进行了改名,方便大家理解其配置的含义。需要注意的是,老版本的接口没有被立刻删除,但是在下一个主要版本中会被删除。下面是各个配置项的官方中文解释:

举个例子: 假设 A 依赖 B,B 依赖 C。
如果 B 对 C 使用 implementation 依赖,则 A 无法调用 C 的代码
如果 B 对 C 使用 api 依赖,则 A 可以调用 C 的代码
如果 B 对 C 使用 compileOnly 依赖,则 A 无法调用 C 的代码,且 C 的代码不会被打包到 APK 中
如果 B 对 C 使用 runtimeOnly 依赖,则 A、B 无法调用 C 的代码,但 C 的代码会被打包到 APK 中

实际上每一个组件都有自己的 compileClasspath 和 runtimeClasspath
当一个组件参与编译时,Gradle 就会将其放在 compileClasspath 中
当一个组件参与打包时,Gradle 就会将其放在 runtimeClasspath 中

不同的依赖配置项,其实就是将声明的依赖放入不同组件的不同的 classpath 中,回到上面的例子 对于 implementation ,其实就是将 C 放入 B 的 compileClasspath 和 runtimeClasspath,放入 A 的 runtimeClasspath 中,从而实现 A 如果调用 C 的代码,在 A 的编译阶段 javac 报错,但最终 C 会被打包到 APK 包中

对于 api、compileOnly、runtimeOnly 原理相同

源码与二进制

当想要依赖一个源码工程时只需要这样写: implementation project(’:demo:mylibrary’)

而且我们可以明确知道 mylibrary 中的依赖都会被正确打包到 APK 中

当我们依赖二进制需要这样写: implementation ‘androidx.appcompat:appcompat:1.0.2’

当执行依赖命令(只输出 release 包的 runtimeClasspath):
./gradlew :app:dependencies --configuration releaseRuntimeClasspath > dependencies.txt

输出依赖关系图时会看到并不是仅仅依赖一个 appcompat 组件(只显示部分依赖),还包含该组件自己的依赖,以及依赖的依赖,直到组件自身没有依赖,这样的特性叫做依赖传递

releaseRuntimeClasspath - Resolved configuration for runtime for variant: release\--- androidx.appcompat:appcompat:1.0.2+--- androidx.annotation:annotation:1.0.0+--- androidx.core:core:1.0.1| +--- androidx.annotation:annotation:1.0.0| +--- androidx.collection:collection:1.0.0| | \--- androidx.annotation:annotation:1.0.0| +--- androidx.lifecycle:lifecycle-runtime:2.0.0| | +--- androidx.lifecycle:lifecycle-common:2.0.0| | | \--- androidx.annotation:annotation:1.0.0| | +--- androidx.arch.core:core-common:2.0.0| | | \--- androidx.annotation:annotation:1.0.0| | \--- androidx.annotation:annotation:1.0.0| \--- androidx.versionedparcelable:versionedparcelable:1.0.0| +--- androidx.annotation:annotation:1.0.0| \--- androidx.collection:collection:1.0.0 (*)+--- androidx.collection:collection:1.0.0 (*)+--- androidx.cursoradapter:cursoradapter:1.0.0

那么 Gradle 是怎么确定这些依赖呢?当使用Maven 规范上传组件时,不单单会上传组件的二进制,还会上传一个 pom.xml 文件,依赖信息就在这个文件当中。

因为查看公共的 Maven 服务器有可能需要翻墙,下面给大家展示百度app自己搭建的服务器的后台,方便理解被上传的二进制在服务器是以怎样的结构存放的

这个是百度app自己搭建的 Maven 服务器后台,点击一项查看详情:

有上传的二进制 aar,也有 pom 文件,还有我们在上传时自定义的文件 readme

看完远端的 POM 文件,我们在看看当二进制被下载后在本地是如何存放的

下面是一个简单的 POM 文件:

可以看到有两个 dependency,需要注意的是 scope,也会分为 runtime 和 compile,runtime 不会参与编译,但会参与打包,compile 会参与编译和打包

两个实际例子: 一:假设 A 依赖 B,B 依赖 C

B 对 C 使用 implementation 依赖
B 中有类 Foo 继承于 C中的 Bar
在 A 中使用类 Foo 时会报错找不到类 Bar,解决办法只能让 A 再依赖 C,所以应该尽量避免使用继承
二:假设 A 依赖 B,B 依赖 C
BC 是二进制, B 的 POM 中对 C 的依赖是 runtime
在 Gradle 4.4 中,A 依然可以调用 C 的代码,这个问题在 Gradle 5.0 后被修复

依赖冲突

什么是依赖冲突:

假设 ABC 是源码,D 是二进制,A 声明依赖 B,A 声明依赖 C,B 声明依赖 D 1.0 版本,C 声明依赖 D 1.1版本,这时,D 有依赖冲突,需要确定是使用 1.0 还是 1.1 版本

如何解决依赖冲突:

  1. 进行编译时,B 编译时依赖 D 的1.0版本,C 编译时依赖 D 的1.1版本,但最终打包到 apk 中 D 的版本是 1.1,因为版本号冲突默认选择最高版本
  2. Gradle 为我们提供了一系列解决依赖冲突的规则如:不允许依赖传递,exclude 移除一个依赖,替换一个组件为另一个组件,这些方法就不一一介绍了,按需百度即可
  3. 百度app在此基础上增加规则:如果最终应用的版本号高于在 version.properties 定义的版本号则报错

注意:

  1. 假设 D 发布 1.2 版本,但 B、C 都没有基于 D 1.2 版本发布新版本,则最终打包还是 D 的 1.1 版本,所以所有组件最终被打包到 APK 包中的版本都为 version.properties 中定义的版本
  2. 假设 D 的 MavenId 由 D 改成了 E,C 基于 E 发布二进制,B 还是老样子,在实际打包中会报类重复的错误,原因就是 B 的 POM 文件中依赖的还是 D,所以需要让 B 基于 D 改名后的 E重新发一个二进制

打包流程 有了前面这些铺垫,让我们实际看看在执行打包 Task 时,实际还执行了哪些 Task。环境配置如下:
Gradle 5.1.1
Android Gradle Plugin 3.1.2
org.gradle.parallel=true 开启并行编译
release 包 minifyEnabled true

执行命令可以得到如下图所示输出

# --dry-run 表示不实际执行每个 Taskgradlew assembleRelease --dry-run

Task 很多,接下来为大家介绍几个重点的 Task,其余没介绍的感兴趣的同学可以找找对应的实现类,看看它的实现。

preBuild

描述:做一些编译前的检查
一个例子:有的人可能遇到下面的错误

"Android dependency "+ display+ "is set to compileOnly/provided which is not supported"

这个的原因就是由前面说过的 compileClasspath 和 runtimeClasspath 引起的。
当一个组件因为不同的依赖配置项导致它的 compileClasspath 比如为 1.1.1版本,但他的 runtimeClasspath 是 1.1.2版本,preBuild 就会检测出这个问题并报错我们处理

compileReleaseAidl

类:AidlCompile
描述:内部使用 AidlProcessor 调用 call 方法使用 build-tool 下的 aidl 执行编译。

各类 generate和 merge

这些 Task 允许我们在整个编译工程中动态的生成一些代码,生成好的资源需要和已有的资源进行合并,并且需要注意有可能覆盖已有资源,就不再详细介绍了,

过程

第一步:我们有 app 工程下的 Java 源文件,还有 AIDL、generate、R.java 等生成的 Java 源文件,还有本身依赖,源码子工程的 jar 包、远端 aar 解压的 jar 包等一系列二进制文件,源码文件是 javac 需要编译的内容,二进制文件 .jar/.class 则是当 javac 编译遇到一个类名等符号时,如果发现在现有的源文件找不到,该去哪找的集合,对应的 javac 参数就是 -classpath

而这个参数,其实就是 compileClasspath 的一个应用,如果你源文件引用了一个类,它的 jar 包不在 compileClasspath 中,那么在编译时 javac 就会报错找不到符号了

第二步:当源文件被编译成类文件后,Google 提供了 Transform 机制允许我们对二进制文件在打包前进行修改,比如前面图片中的 :app:transformClassesWithXXXForRelease SKIPPED 就是我们自定义的 Transform。通过 :app:transformClassesAndResourcesWithProguardForRelease SKIPPED 也可以看到 Proguard 也是通过 Transform 机制实现的,这里图片中一个 .class 文件,一个是 .jar/.class 文件,第一个显然是 javac 编译后的产物,第二个则是 runtimeClasspath,就是那些需要被打包的二进制。相信大家这样就理解了 compileClasspath 和 runtimeClasspath 是如何影响打包过程

第三步:当 Transform 处理好所有的 class 文件后,接下来就是将 .class 文件转换为 .dex 文件。值得注意的是,javac 只能发现源码的问题,不会发现那些未参与编译的二进制的问题。而在 dex 转换过程中则可以发现比如类重复问题或者一个类,名字不变,但是由 Class 变成了 Interface 这类严重的代码问题。

第四步:就是将前面的和资源进行打包。对应的类是 PackageApplication,得到这个 Task 后可以对打包的内容进行自定义

结语

虽然只对常见的一些配置提供一个大纲,没有详细介绍 Gradle 和 Android Gradle Plugin 相关内容,比如 Gradle 的生命周期,插件开发,Transform 机制等等,但是如果各位看了以后能对整个编译工具链有一个大体的了解,可以在需要的时候明白该从哪个方向去解决问题,就是本文的价值所在了。

更多系列教程GitHub地址:https://github.com/Timdk857/Android-Architecture-knowledge-2-

也可以关注我的主页,干货满满,如有帮助,可否留个赞再走?谢谢~

这篇关于百度T6团队实力打造!Gradle 与 Android 构建入门 系统教程,我先收藏了的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/175384

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了