- 根据官方文档以及前一篇文章中可以知道,如果想给 task 添加操作,可以添加在
doLast {}/doFirst {}
等闭包中,例如:
task myTask {
doFirst {
println ‘myTask 最先执行的内容’
}
doLast {
println ‘myTask 最后执行的内容’
}
// warning
// println ‘Configuration 阶段和 Execution 阶段皆会执行’
}
切记大部分的内容是写在
doLast{}
或doFirst{}
闭包中,因为写在如果写在 task 闭包中的话,会在Configuration
阶段也被执行。
- 根据官方文档可知,为了提高 task 复用性,Gradle 还支持 Task 类的书写——
2.1 将下述代码写在 build.gradle
中,并用 @TaskAction
标记想要执行的方法。
class GreetingTask extends DefaultTask {
String greeting = ‘hello from GreetingTask’
@TaskAction
def greet() {
println greeting
}
}
2.2 在 build.gradle
中撰写 task 调用 GreetingTask 类:
// Use the default greeting
task (name: hello , type: GreetingTask)
// Customize the greeting
task (name: greeting , type: GreetingTask) {
greeting = ‘greetings from GreetingTask’
}
2.3 调用该 task——
./gradlew hello
Task :app:hello
hello from GreetingTask
./gradlew greeting
Task :app:greeting
greetings from GreetingTask
所以看到这里应该不仅能够理解 Task 类的书写,并且应该能够大致明白 type
这个参数的含义了。
不知道会不会和笔者一样事儿逼的读者此时会疑惑 @TaskAction
修饰的方法和 doLast {}
以及 doFirst {}
闭包的执行顺序是怎样的?
task (name: hello, type: GreetingTask) {
doFirst {
def list = getActions()
for (int i = 0; i < list.size(); i++) {
println list.get(i).displayName
}
}
doLast {
}
}
首先声明 doFirst {}
和 doLast {}
闭包;然后戳进 DefaultTask 源码并追踪到顶级父类 AbstractTask 中可以看到内部通过使用 actions
存储所有执行的 Action,并通过 getAction()
暴露;actions
是 List 类型,内部的元素类型是 ContextAwareTaskAction,该接口又实现了 Describable,Describable 仅声明了一个 getDisplayName()
方法,所以可以直接通过 displayName 获取该 Action 的名称。
理解以上三步即可完成上述 task 撰写,在命令行中试试——
./gradlew hello
Task :app:hello
Execute doFirst {} action
Execute greet
Execute doLast {} action
Gradle 内部将会自动为变量设置 setter、getter 方法,所以当一个 Gradle 有
getXxx()
方法时,可以直接使用 xxx 变量。如果不清楚这个细节,建议回顾上一篇文章的附录。
task 依赖关系
开发者常使用 dependsOn
来指定依赖关系(另外两种是指定 task 执行顺序,详见文档 Task Dependencies and Task Ordering),如下:
task a {
doLast {
println ‘a’
}
}
task b {
dependsOn(‘a’)
doFirst {
println ‘b’
}
}
不妨将以上代码写在 app build.gradle
文件下,当执行 task b 的时候,会输出如下信息:
./gradlew task app:b
> Task :app:a
a
> Task :app:b
b
可以看到,由于 task b 需要依赖 task a,所以 task b 执行的时候会先执行 task a。
有经验的开发者如果在命令行中试过
assembleDebug
等命令会发现,它们的执行将会依赖于许多其他 task。所以不妨在命令行中试试./gradlew assembleDebug
观察输出结果。
task 实战
install && launch apk
com.android.application
自带 installDebug
task,开发者可以使用 installDebug
安装当前项目 apk:
./gradlew installDebug
> Task :app:installDebug
Installing APK ‘app-debug.apk’ on ‘xxxxx’ for app:debug Installed on 1 device.
但是似乎看起来有些不尽人意的地方,例如开发者希望安装的时候能够顺带能够启动该 app。那么该如何做呢?
首先从问题的可行性上来进行分析,开发者的直觉告诉我们是可以通过 gradle 实现的——命令行可以安装、启动 apk——adb install -r app-debug.apk
和 adb shell am start -n 包名/首 Activity
。所以关键点就是如何通过 gradle 调用命令行代码以及如何获取到 包名/首 Activity
信息。
-
开发者的直觉同样告诉我们 Gradle 开发文档)中有关于命令行调用的信息,只需要使用
exec {}
闭包就好了。 -
如何获取
包名/首 Activity
信息?可以通过AndroidManifest.xml
来获取。部分经验丰富的开发者知道——打入 apk 中的AndroidManifest.xml
文件并不是我们平常写的AndroidManifest.xml
,而是 apk 编译后位于Project/app/build/intermediates/manifests/full/debug/
包下的AndroidManifest.xml
(当然,如果是 Release 包的话,应该是Project/app/build/intermediates/manifests/full/release/
包下)。
- 包名就是
android
闭包下的defaultConfig
闭包下的applicationId
。
- 目标 Activity 则是包含 action 为
android.intent.action.MAIN
的 Activity。
理解了以上内容,便不难理解下面的内容:
task installAndRun(dependsOn: ‘assembleDebug’) {
doFirst {
exec {
workingDir “KaTeX parse error: Expected 'EOF', got '}' at position 83: …app-debug.apk' }̲ exec { def pat…{buildDir}/intermediates/manifests/full/debug/AndroidManifest.xml”
// xml 解析
def parser = new XmlParser(false, false).parse(new File(path))
// application 下的每一个 activity 结点
parser.application.activity.each { activity ->
// activity 下的每一个 intent-filter 结点
activity.‘intent-filter’.each { filter ->
// intent-filter 下的 action 结点中的 @android:name 包含 android.intent.action.MAIN
if (filter.action.@“android:name”.contains(“android.intent.action.MAIN”)) {
def targetActivity = activity.@“android:name”
commandLine ‘adb’, ‘shell’, ‘am’, ‘start’, ‘-n’,
“
a
n
d
r
o
i
d
.
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
/
{android.defaultConfig.applicationId}/
android.defaultConfig.applicationId/{targetActivity}”
}
}
}
}
}
}
- install apk 的前提必须是得有一个 apk,所以势必需要依赖
assembleDebug
task。
实际上
installDebug
task 也是依赖assembleDebug
task 的,不妨可以试试——
task showInstallDepends {
doFirst {
println project.tasks.findByName(“installDebug”).dependsOn
}
}
./gradlew showInstallDepends
> Configure project :app
[task ‘installDebug’ input files, assembleDebug]
exec
闭包中的几个参数提及下——
2.1 workingDir
:工作环境,参数为 File 格式)。默认为当前 project 目录。
2.2 commandLine
:需要命令行执行的命令,参数为 List 格式。
- 前一篇文章中提到 ——
说白了它们其实就是一些闭包、一些固定格式,正是因为它们的格式是固定的,task 才能够读取到相应的数据完成相应的事情。
在第二个 exec
闭包的第八行就很好的体现了这一点,通过 {android.defaultConfig.applicationId}
直接获取到 Gradle 文件中 android
闭包下的 defaultConfig
闭包下的 applicationId
的值。由此就获得了当前应用的包名。
当然,除了 Gradle 能够调用命令行以外,实际上 groovy 也是可以调用命令行的,但在此就不做扩展了。
-
至于最先启动的 Activity,肯定是 action 为
android.intent.action.MAIN
的 Activity,那么问题就是变成如何在AndroidManifest.xml
中寻找到该 Activity 的事了——作为一个合格的老司机,应该能够想到 groovy 一定会提供相应的 xml 解析 API 的,至于具体的使用笔者就不在此扩展了,留给各位读者去源码中探索成长。 -
除去上面的信息以外,还需要什么?还需要知道一些 gradle 构建的信息——例如 debug 包会最终出现在
${buildDir}/outputs/apk/debug
;例如 debug 包中的AndroidManifest.xml
并不是日常开发中写的那个AndroidManifest.xml
(虽然可能它俩基本没什么差异),而是${buildDir}/intermediates/manifests/full/debug
下的AndroidManifest.xml
。所以一是希望各位读者日常多去翻翻 build 文件夹,二是要知道${buildDir}
(build 文件夹)有多么重要,因为 Gradle 构建 apk 的过程中,但凡有输出文件那么基本都会存在这个文件夹中,所以多去翻一翻。
由此之后,可以在命令行输入以下命令:
./gradlew installAndRun
> Task :app:installAndRun
[ 4%] /data/local/tmp/app-debug.apk
[ 8%] /data/local/tmp/app-debug.apk
[ 12%] /data/local/tmp/app-debug.apk …
Success
Starting: Intent {com.test.Test/TestActivity}
至此便完成了一个安装并启动 apk 的 task 撰写了。
hook assets
上面的 task 看起来似乎和 Android 的构建过程并无多大关系,没错,那么接下来不妨深层次接触试试——通过 hook 原生 task 实现更改打包中的文件——在打包过程中向 assets
插入一张图片。
尽管这看起来丝毫没卵用
在打包流程中,有一个 task 名为 packageDebug
,该 task 是打包文件生成 apk 的——
接着,不妨在命令行键入以下命令:
./gradlew help --task “packageDebug”
Type
PackageApplication (com.android.build.gradle.tasks.PackageApplication)
可以看到,该 task 的 type 是 PackageApplication
——
不妨再看看它的父类 PackageAndroidArtifact
:
看到关键信息,该 task 中有一个类型为 FileCollection assets
字段,这便是最终打入 apk 中的那个 assets 了。所以不难写出以下代码——
task hookAssets {
afterEvaluate {
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
884704192)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!