补齐Android技能树 - 玩转Gradle(一) _ 小册免费学

本文介绍了APK打包过程中的关键技术,如使用AAPT、aidl、javac等命令,以及为何需要使用脚本自动化打包以提高效率。重点讨论了Ant、Maven和Gradle的区别,强调了Gradle的灵活性和自动化特性,以及如何配置GradleWrapper和使用构建脚本来简化构建流程。
摘要由CSDN通过智能技术生成
  • APK打包器将dex和编译后的资源整合成单个apk;
  • 打包器使用zipalign工具对应用进行优化,为apk签名;

当然,这是高度抽象后的流程,实际的打包过程可要复杂得多:

  • aapt命令 → 生成R.java文件;
  • aidl命令 → 生成aidl对应的java文件;
  • javac命令 → 编译java源文件生成class文件;
  • dx.bat → class转换成class.dex文件
  • …等

想想如果每次打包apk,都要手工用各种命令、工具按顺序来打包,效率得有多低,碰上要打十几个渠道包的,打包仔直接原地去世!

Tips:不同应用市场可能有不同的统计需求,需要为每个应用市场发布一个安装包,这就是渠道包。

人是容易犯错的,特别是这种 手动介入的重复任务,把渠道标识弄乱打错包这类事情时有发生。

可以把这种流水线式重复构建的活写到一个脚本中(构建脚本),每次打包,执行下这个脚本 自动化 打包即可,使得让开发仔可以心无旁骛地编写功能代码,以此提高开发效率。

构建脚本的内容就是按照构建流程,依次执行命令、调用工具,最后将生成的可执行文件输出到特定目录。

说到脚本,有些童鞋立马上头,准备Python、Bash一把梭,实际上大可不必。

开源的自动化构建工具 就很香,没必要重复造轮子,还得自己踩波坑~


2. Ant、Maven、Gradle的区别

Android 早期使用Eclipse作为IDE时,用的自动化构建工具 → Apache Ant,Java编写、平台无关、基于任务链思想,采用XML作为构建脚本,文件默认是build.xml,基础配置模板如下:

<?xml version="1.0" encoding="UTF-8" ?>

可以清晰地看到脚本中定义了五个Target,分别是:init、compile、build、test、clean,Target。

它们之间还通过depends定义依赖关系,以此形成了执行的先后顺序,执行build Target前会执行compile,而执行compile前又要init。

Ant 还支持自定义Target,但存在一个问题:没办法管理依赖,项目依赖到第三方库,需要自己手动将 正确版本 的包拷贝到lib目录下,早期到处找各种jar包的场景依旧历历在目。

所以,带依赖库管理的 Apache Maven 来了,只需在工程管理文件中(pom.xml)标明需要的包及版本,构建时Maven会自动打包到工程中,示例如下:

<?xml version="1.0" encoding="utf-8"?>

<project …xmlns…>
cn.coderpig
Test
1.0.0-SNAPSHOT

com.squareup.okhttp3 okhttp 4.9.1

定义了自己的包:cn.coderpig:Test:1.0.0-SNAPSHOT,工程依赖了:com.squareup.okhttp3:okhttp:4.9.1,Maven会自动把okhttp包打进来,没有的话还会自动到网上下载(远程仓库,可通过repository标签指定)。

Maven抛弃了Ant中通过Target定义任务的做法,采用 约定优于配置的思想,抽象出一套满足大部分项目的 构建生命周期:clean → default → site,要求用户 在给定的生命周期中 使用插件的方式去完成构建工作。

规范化好是好,但也带来一个问题:自定义构建需求实现麻烦,没办法像Ant那样灵活。

还有一点,Ant和Maven都使用XML定义构建脚本,可读性及扩展性较差,在任务执行上较弱(不支持循环,条件判断麻烦等)。

所以,集Ant和Maven所长,功能更强大的 Gradle 来了:

  • ① 抛弃繁琐的XML,采用Grovvy DSL或Kotlin DSL来编写构建脚本;
  • ② 可以像Maven一样自动下jar包,即依赖管理;
  • ③ 有默认标准的构建生命周期,也支持灵活的自定义任务等;
  • ④ 构建级别支持从Ant、Maven的逐步迁移;

TipsDSL(Domain Specific Language),特定领域语言,专注于 特定领域问题 的计算机语言,如SQL仅支持数据库相关操作,正则表达式只支持字符串的检索替换。以简洁的形式进行表达,直观易懂、使得读、调代码的成本得以降低,就是求精不求广。

简单小结下三者区别:

Ant支持自动化打包逻辑,Maven比它多了自动下jar包,规范了打包逻辑,反而不好定制。Gradle既能自动下jar包,又能自己写脚本,而且脚本写起来比Ant爽。


3. Gradle的下载配置

Gradle基于JVM,需要Java环境(1.8及以上版本),在 Gradle官网 下载对应版本Gradle,此处以6.1.1为例,有四种类型的包可供下载:

下载bin和all都可以,我习惯下后者,有时需要看下源码和文档,下载zip包后解压,配置下环境变量 GRADLE_HOME,Windows环境配置示例如下:

Path环境变量新增:

配置完后打开终端,键入:gradle -v 验证是否生效。


4. Gradle Wrapper

Android Studio默认使用 Gradle Wrapper 而不是直接使用 Gradle,命令也是使用 gradlew 而不是 gradle

这个Gradle Wrapper就是对Gradle的一层封装,使得开发者无需关心项目Gradle的版本变化。

新建一个目录,键入下述命令:

gradle wrapper

生成文件目录结构如下:

├── .gradle
├── gradle
│ └── wrapperwrapper
| └── gradle-wrapper.jar // 用于下载所需Gradle;
| └── gradle-wrapper.properties // 配置文件;
├── gradlew //Linux下的可执行脚本;
└── gradlew.bat //Windows下的可执行脚本;

接着键入:gradlew build 编译,检查到配置文件中对应版本的Gradle本地没有时,会启动 wrapper进程 下载配置Gradle,完事后此进程会自动关闭。

gradle-wrapper.properties 配置文件内容如下:

Windows下指向:C:\Users\用户名.gradle 目录,打开可以看到下载各个版本的gradle:

封装一层还有个好处:在没有安装Gradle的机器上也可以使用Gradle构建项目,但有一点要注意:

每个Gradle版本对应一个Daemon进程,基本512M起步,电脑配置不佳 的情况下,应尽量避免多个版本的Gradle同时运行。建议:自己管理Gradle,即使用本地创建Gradle环境,AS的配置方法如下:


5. 构建脚本初体验

键入下述命令新建一个build.gradle文件然后编译:

touch build.gradle
echo println(“Hello Gradle!”); >> build.gradle
gradlew

输出内容如下:

执行 gradle 时会从当前目录查找名为 build.gradlebuild.gradle.kts 的文件并执行其中的内容。

除手动创建的方式外,还可以通过 gradle init 自动初始化不同类型的Project:

此处以Kotlin application工程为例,打开目录查看创建的文件:

可以看到build.gradle中添加了一些依赖,键入 gradlew build 编译下项目:


6. 包都下到哪里去了

问题来了:下载的第三方依赖库都放哪里去了

答:~/.gradle/cache/dodules-2/files-2.1/包名/库名/版本号/hash字符串/,示例如下:

如果你不想将gradle相关的下到~/.gradle下,可自行添加环境变量 GRADLE_USER_HOME,如:

后续,gradle下载的东西就会放到这个目录下了:

上述改动,在Android Studio不一定会生效哦,有时还需自行配置:


0x2、Gradle的执行架构

当我想删除上面这个C:\Test的目录时,却发现删除不了:

就是有进程在占用这个文件夹,那是什么进程呢?答:daemon进程,可以键入下述命令 gradle --status 查看一波:

进程id为10276 → 进程处于空闲状态(BUSY表示正在构建任务) → 附加信息:6.1.1,打开任务管理器可以定位到此进程:

我们都知道java代码编译成class字节码后运行在JVM上,那就用jdk自带的 jvisualvm.exe 查看一波具体信息:

行吧,就是 daemon 守护进程,进程名为GradleDaemon,所以为啥要让一个守护进程常驻后台呢?

这得先提一提Maven了:

Maven在构建时,会启动一个Maven的JVM进程,构建结束后会关闭此进程,每使用一次Maven构建都要启动一次,其中load所需的jar文件是一个相当耗时的过程。

而Gradle 3.0之后,默认使用Daemon模式:

启动一个非常轻量的client JVM进程,只用于和后台的deamon JVM进程通信。构建完client进程关闭,而deamon进程仍然保留(处于IDLE空闲状态),下次需要构建时,直接启用deamon进程,减少构建的耗时等待。deamon进程默认后台保留三个小时,在此时间段没有被启动则关闭。

0x3、Gradle配置

Gradle配置的地方有三处,参数优先级依次如下:

命令行参数 > ~/.gradle/gradle.properties > 项目根目录/gradle.properties

罗列下较常用的命令行选项,大概过一下有个印象即可,用到再查(更多详细内容可参见:官方文档)

命令结构

gradle [taskName…] [–option-name…]

增量编译:同一个项目中, 同一个 task除非有必要, 否则不会被无意义的执行多次;

缓存:无论是否在同一个项目,只要Task输入没变就复用缓存结果,不必真的执行task;

Tasks执行

gradle myTask # 执行某个Task
gradle :my-subproject:taskName # 执行子项目中的Task
gradle my-subproject:taskName # 同上,不指定子项目,会执行所有子项目的此Task,如gradle clean;
gradle task1 task2 # 运行多个Task
gradle dist --exclude-task test # 将某个task排除在执行外
gradle dist -x test # 同上
gradle test --rerun-tasks # 强制执行UP-TO-DATE的Task,即不走增量编译,执行全量编译;
gradle test --continue # 默认情况下,一旦Task失败就会构建失败,通过此参数可继续执行;

常见任务(和插件间的Task约定)

gradle build
gradle run
gradle check
gradle clean # 删除构建目录

构建细节

gradle projects # 列出所有子项目
gradle tasks # 列出所有Task(分配给任务组的Task)
gradle tasks --group=“build setup” # 列出特定任务组的Task
gradle tasks --all # 列出所有Task
gradle -q help --task libs # 查看某个Task的详细信息
gradle myTask --scan # 生成可视化的编译报告
gradle dependencies # 列出项目依赖
gradle -q project:properties # 列出项目属性列表

调试选项

-?,-h,–help # 帮助信息
-v,–version # 版本信息
-s, --stacktrace # 打印出异常堆栈跟踪信息;
-S, --full-stacktrace # 比上面更完整的信息;

性能相关

–build-cache # 复用缓存
–no-build-cache # 不复用缓存,默认
–max-workers # 最大处理器数量
–parallel # 并行生成项目
–no-parallel # 不并行生成项目
–priority # Gradle启动的进程优先级
–profile # 生成性能报告

守护进程

–daemon # 使用deamon进程构建
–no-daemon # 不使用deamon进程构建
–foreground # 前台进程启动deamon进程
–status # 查看运行中和最近停止的deamon进程;
–stop # 停止所有同一版本的deamon进程;

日志选项

-q, --quiet # 只记录错误
-w, --warn
-i, --info
-d, --debug
–console=(auto,plain,rich,verbose) # 指定输出类型
–warning-mode=(all,fail,none,summary) # 指定警告级别

执行选项

–include-build # 复合构建
–offline # 离线构建
–refresh-dependencies # 强制清除依赖缓存
–dry-run # 在不实际执行Task的情况下看Task执行顺序
–no-rebuild # 不重复构建项目依赖

环境选项

-b, --build-file # 指定构建文件
-c, --settings-file # 指定设置文件
-g, --gradle-user-home # 指定默认.Gradle目录
-p, --project-dir # 指定Gradle的开始目录
–project-cache-dir # 指定缓存目录,默认.gradle
-D, --system-prop # 设置JVM系统属性
-I, --init-script # 指定初始化脚本
-P, --project-prop # 指定根项目的项目属性;

Tips:有些属性支持在gradle.properties文件中进行配置,就不用每次命令行输入了,更多可参见:System properties

0x4、Gradle基础

1. Gradle构建生命周期

Gradle构建分为三大阶段:

2. 生命周期监听(HOOK)

3. Grovvy基础语法速成

Grovvy是JVM上的 脚本语言,基于Java扩展的 动态语言,除了兼容Java外,还加入了闭包等新功能。

Gradle会把**.gradle** Groovy脚本编译成.class java字节码文件在JVM上运行。

Gradle是自动化构建工具,运行在JVM上的一个程序,Groovy是基于JVM的一种语言,他两间的关系就想Android和Java一样。

Gradle中涉及Groovy的语法只是都是比较简单的,学完对Groovy感兴趣可自行移步到官网学习(groovy API)。

Android项目采用Gradle构建,默认使用 Groovy DSL 脚本构建,从Gradle 4.0开始,正式支持 Kotlin DSL 脚本构建,两者可以共存,本节基于 Groovy DSL 进行讲解。(个人感觉他两语法实在是太像了,会Kotlin迁移到Groovy无压力~)

基础规则

  • 注释与Java一致,支持:// 或 /**/
  • 不以分号结尾;
  • 单引号字符串不会对$符号转义,双引号字符串可以使用字符串模板,三引号是带格式的字符串;
  • 方法括号可省略,可不写return,默认返回最后一行代码;
  • 代码块可以作为参数传递

定义 (使用def关键字定义)

// 定义变量:Groovy支持动态类型,定义时可不指定类型,会自行推导

def a = 1 // 定义整型,Groovy编译器会将所有基本类型都包装成对象类型
def b = “字符串:${a}” // 定义字符串模板
de double c = 1.0 // 定义Double类型

声明变量

// 局部变量,仅在声明它们的范围内可见
def dest = “dest”
task copy(type: Copy) {
from “source”
into dest
}

// ext额外属性,Gradle域模型中所有增强对象都可以容纳额外的用户定义属性
ext {
springVersion = “3.1.0.RELEASE”
emailNotification = “build@master.org”
}

task printProperties {
doLast {
println springVersion
println emailNotification
}
}

// 用类型修饰符声明的变量在闭包中可见,但在方法中不可见
String localScope1 = ‘localScope1’

println localScope1

closure = {
println localScope1
}

def method() {
try {
localScope1
} catch (MissingPropertyException e) {
println ‘localScope1NotAvailable’
}
}

closure.call()
method()

// 输出结果:
// localScope1
// localScope1
// localScope1NotAvailable

函数

// 无返回值的函数需使用def关键字,最后一行代码的执行结果就是返回值
// 无参函数
def fun1() { }

// 有参函数
def fun2(def1, def2) { }

// 指定了函数返回类型,则可不加def关键字
String fun3() { return “返回值” }

// 简化下,效果同fun3
String fun4() { “返回值” }

// 函数调用可以不加括号
println fun3

循环

// i前面b,输出5个测试
for(i = 0;i < 5;i++) {
println(“测试”)
}

// 输出6个测试
for(i in 0…5) {
println(“测试”)
}

// 如果想输出5个可改成:
for(i in 0…<5)

// 循环次数,从0循环到4
4.times{
println(“测试: ${it}”)
}

三目运算符、判断

// 和Java一致,判空还可以简写成这样:
def name = ‘d’
def result = name?: “abc”

// 还有用?判空,跟Kotlin的一样,person不为空 → Data属性不为空 → 才打印name
println person?.Data?.name

// asType是类型转换
def s3 = s1.asType(Integer)

闭包

闭包的本质就是 代码块,运行时可作为变量传递的函数,并保留定义它们的变量的范围的访问。

// 定义闭包
def clouser = { String param1, int param2 -> // 箭头前面是参数定义,后面是代码
println “代码部分”
}

// 调用闭包
clouser.call()
clouser()

// 闭包没定义参数的话,隐含一个it参数,和this作用类似
def noParamClosure = { it-> true }

// 函数最后一个参数都是一个闭包,可以省略圆括号,类似于回调函数的用法
task CoderPig {
doLast ({
println “Test”
})
}

task CoderPig {
doLast {
println “Test”
}
}

// 闭包里的关键变量,没有闭包嵌套时都指向同一个,有闭包时:
// this:闭包定义处的类;
// owner,delegate:离他最近的哪个闭包对象;

最后

题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

Android开发8年,阿里、百度一面惨被吊打!我是否应该转行了?

【Android进阶学习视频】、【全套Android面试秘籍】

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展

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

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

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

限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-mufJ9Ozk-1714691021608)]

【Android进阶学习视频】、【全套Android面试秘籍】

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展

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

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

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

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值