android编译是什么,聊聊Android编译流程

各位大佬,能不能随便给我的项目或者之前的文章点个star,苦兮兮。github.com/ 掘金文章

面试官:哟,大叔,又来了啊。

我:emmmmm,我今天就是来屠龙了。

面试官:勇气可嘉,那么我们聊聊Android编译流程吧。

我:吃我一招天打雷劈屠真龙。

是时候表演真正的技术了

正常情况下,编译流程都是从下图说起的。90a86d79e177723d228f56c470f6ee8b.png

通过 aapt 打包 res 资源文件,生成 R.java、resources.arsc 和 res 文件(二进制 & 非二进制如 res/raw 和 pic 保持原样);

处理 .aidl 文件,生成对应的 Java 接口文件;

通过 Java Compiler 编译 R.java、Java 接口文件、Java 源文件,生成 .class 文件;

通过 dex 命令,将 .class 文件和第三方库中的 .class 文件处理生成 classes.dex;

通过 apkbuilder 工具,将 aapt 生成的 resources.arsc 和 res 文件、assets 文件和 classes.dex 一起打包生成 apk;

通过 Jarsigner 工具,对上面的 apk 进行 debug 或 release 签名;

通过 zipalign 工具,将签名后的 apk 进行对齐处理。

看起来我们貌似已经回答出了这个问题的答案,但是今天是来屠龙的,所以我们不能就这么简单的放过这个题目。

从gradle Task看编译流程

先贴一段gradle打印task耗时的代码

项目根目录build.gradle打开

加入下面代码

import java.util.concurrent.TimeUnit

// Log timings per task.

class TimingsListener implements TaskExecutionListener, BuildListener {

private long startTime

private timings = []

@Override

void beforeExecute(Task task) {

startTime = System.nanoTime()

}

@Override

void afterExecute(Task task, TaskState taskState) {

def ms = TimeUnit.MILLISECONDS.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);

timings.add([ms, task.path])

task.project.logger.warn "${task.path} took ${ms}ms"

}

@Override

void buildFinished(BuildResult result) {

println "Task timings:"

for (timing in timings) {

if (timing[0] >= 50) {

printf "%7sms %s\n", timing

}

}

}

@Override

void buildStarted(Gradle gradle) {}

@Override

void projectsEvaluated(Gradle gradle) {}

@Override

void projectsLoaded(Gradle gradle) {}

@Override

void settingsEvaluated(Settings settings) {}

}

gradle.addListener new TimingsListener()

复制代码

当项目运行完之后会输出类似如下的日志,表示一个run执行之后gradle所执行的task的时间以及任务名。

1543ms :compiler:kaptGenerateStubsKotlin

144ms :RouterLib:packageDebugResources

1166ms :compiler:kaptKotlin

816ms :compiler:compileKotlin

401ms :compiler:compileJava

65ms :compiler:jar

122ms :app:mergeDebugResources

56ms :EmptyLoader:compileJava

170ms :app:processDebugManifest

171ms :RouterLib:parseDebugLocalResources

60ms :app:checkDebugDuplicateClasses

2416ms :RouterLib:compileDebugKotlin

122ms :RouterLib:compileDebugJavaWithJavac

124ms :secondmoudle:mergeDebugNativeLibs

1185ms :app:processDebugResources

70ms :secondmoudle:kaptGenerateStubsDebugKotlin

202ms :RouterLib:mergeDebugNativeLibs

350ms :secondmoudle:kaptDebugKotlin

158ms :secondmoudle:compileDebugJavaWithJavac

1108ms :app:kaptGenerateStubsDebugKotlin

91ms :secondmoudle:bundleLibRuntimeToJarDebug

129ms :app:mergeDebugNativeLibs

430ms :app:kaptDebugKotlin

1008ms :app:compileDebugKotlin

120ms :app:compileDebugJavaWithJavac

265ms :app:mergeDebugJavaResource

181ms :app:transformClassesAndResourcesWithAuto_registerForDebug

7262ms :app:dexBuilderDebug

1308ms :app:mergeProjectDexDebug

344ms :app:packageDebug

复制代码

从上述Task列表中可以看出,其实最上面这张图所说的编译流程其实并不完整。

kapt和apt

我上篇文章说了,javaCompiler执行之前会先执行apt,生成java代码,其任务名就是kaptGenerateStubsDebugKotlin。

compiler 混入了奇怪的东西

kotlin已经被引入了很多版本了,但是kotlin的compiler其实和java compiler是不一样的。

如果按照标准答案去回答这个问题吧,总感觉还是有所欠缺的,所以我们需要补充的一个点就是compileDebugKotlin。

当然少不了transform

当我们使用字节码插桩之后其实就增加了个transform的流程,也就是这个transformClassesAndResourcesWithAuto_registerForDebug。

那么是不是还有什么可以补充的呢?

AGP在不同版本的差异还是比较大的。特别是在3.2版本之上的版本被引入了D8编译器之后。

低版本先使用DX编译器将class转化为dex。

而高版本采用d8编译器将class转化为dex。6d2e81335e8ed60a415178a5e1d770b2.png

desugar是干嘛的?

Android Studio 为使用部分 Java 8 语言功能及利用这些功能的第三方库提供内置支持。默认工具链对 javac 编译器的输出执行字节码转换(称为 desugar),从而实现新语言功能。

语法糖香归香,但是最后.dex可是不认识你的。

那么D8的优势是什么呢???

话不多,直接上图。

f85fcb352a49be0d725330123e8f6752.png

41193685002fc7805a723e6aa4260b85.png

可以看到D8在编译速度以及编译出来的文件体积上有了明显的提升。

那么混淆呢??

看看最一开始的图,有没有发现少了混淆的流程呢!!!

在AGP3.4版本上引入了R8,也就是混淆升级版本。而且在高版本上,整体流程也其实发生了微妙的变更,将原先的流程进行了合并。

R8开启前的编译流程

622449bc95955cf100b8d3884e14cd8f.png

R8开启后的编译流程

37b690804189ba788ce04d61face9a4e.png

说句题外话,但是R8更吃内存,机器辣鸡的老哥慎重点。

关于签名

之前写的东西有点遗漏啊,谷歌官方有说明,下面是引用啊

注意:您必须在应用构建过程中的两个特定时间点之一使用 zipalign,具体在哪个时间点使用,取决于您所使用的应用签名工具:

如果您使用的是 apksigner,则只能在为 APK 文件签名之前执行 zipalign。如果您在使用 apksigner 为 APK 签名之后对 APK 做出了进一步更>改,签名便会失效。

如果您使用的是 jarsigner,则只能在为 APK 文件签名之后执行 zipalign。

那么当使用V1签名时,编译流程顺序还是6-7

而当使用的是V2的签名时,则编译流程顺序是7-6

结束

其实并没有什么想说的,只是想给各位老哥表演下倒立吹牛逼。觉得还ok 给我点个赞把。万一有哪个倒霉蛋下次再被问到这个题目,可以来这里上个香。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值