Kotlin 增量编译是怎么实现的?_useclasspathsnapshot,华为 面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新HarmonyOS鸿蒙全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img

img
img
htt

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

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

如果你需要这些资料,可以添加V获取:vip204888 (备注鸿蒙)
img

正文

ChangeType.REMOVED -> removed.add(it.file)
else -> Unit
}
}
modified to removed
}
.run {
ChangedFiles.Known(first, second)
}
}

// 查找改变了的classpath
private fun getClasspathChanges(inputChanges: InputChanges): ClasspathChanges = when {
!classpathSnapshotProperties.useClasspathSnapshot.get() -> ClasspathSnapshotDisabled
else -> {
when {
!inputChanges.isIncremental -> NotAvailableForNonIncrementalRun(classpathSnapshotFiles)
inputChanges.getFileChanges(classpathSnapshotProperties.classpathSnapshot).none() -> NoChanges(classpathSnapshotFiles)
!classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile.exists() -> {
NotAvailableDueToMissingClasspathSnapshot(classpathSnapshotFiles)
}
else -> ToBeComputedByIncrementalCompiler(classpathSnapshotFiles)
}
}
}
}

对于KotlinCompile,我们也可以从入参,出参,TaskAction的角度来分析

  1. classpathSnapshotProperties是个包装类型的输入,内部包括@Classpath类型的输入,使用@Classpath输入时,如果输入文件名发生变化而内容没有发生变化时,不会触发Task重新运行,这对classpath来说非常重要
  2. incrementalProps是组件后的增量编译输入参数,包括kotlin输入,java输入,classpath输入等
  3. CompileKotlinTaskAction,它最后会执行到callCompilerAsync方法,在其中通过getChangedFilesgetClasspathChanges获取改变了的输入与classpath
  4. getClasspathChanges方法通过inputChanges获取一个已经改变与删除的文件的Pair
  5. getClasspathChanges则根据增量编译是否开启,是否有文件发生更改,历史snapshotFile是否存在,返回不同的ClassPathChanges密封类

在增量编译参数拼装完成后,接下来就是跟着逻辑走,最后会走到GradleKotlinCompilerWorkcompileWithDaemmonOrFailbackImpl

private fun compileWithDaemonOrFallbackImpl(messageCollector: MessageCollector): ExitCode {
val executionStrategy = kotlinCompilerExecutionStrategy()
if (executionStrategy == DAEMON_EXECUTION_STRATEGY) {
val daemonExitCode = compileWithDaemon(messageCollector)
if (daemonExitCode != null) {
return daemonExitCode
}
}
val isGradleDaemonUsed = System.getProperty(“org.gradle.daemon”)?.let(String::toBoolean)
return if (executionStrategy == IN_PROCESS_EXECUTION_STRATEGY || isGradleDaemonUsed == false) {
compileInProcess(messageCollector)
} else {
compileOutOfProcess()
}
}

可以看出,kotlin编译有三种策略,分别是

  1. 守护进程编译:Kotlin编译的默认模式,只有这种模式才支持增量编译,可以在多个Gradle daemon进程间共享
  2. 进程内编译:Gradle daemon进程内编译
  3. 进程外编译:每次编译都是在不同的进程

compileWithDaemon 会调用到 Kotlin Compile 里执行真正的编译逻辑:

val exitCode = try {
val res = if (isIncremental) {
incrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, bufferingMessageCollector)
} else {
nonIncrementalCompilationWithDaemon(daemon, sessionId, targetPlatform, bufferingMessageCollector)
}
} catch (e: Throwable) {
null
}

到这里会执行 org.jetbrains.kotlin.daemon.CompileServiceImplcompile 方法,这样就终于调到了Kotlin编译器内部

第六步:Kotlin 编译器计算出需重编译的文件

经过这么多步骤,终于走到Kotlin编译器内部了,下面我们来看下Kotlin编译器的增量编译逻辑

protected inline fun <ServicesFacadeT, JpsServicesFacadeT, CompilationResultsT> compileImpl(){
//…
CompilerMode.INCREMENTAL_COMPILER -> {
when (targetPlatform) {
CompileService.TargetPlatform.JVM -> withIC(k2PlatformArgs) {
doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
execIncrementalCompiler(
k2PlatformArgs as K2JVMCompilerArguments,
gradleIncrementalArgs,
//…
)
}
}
}

如上代码,会判断输入的编译参数,如果是增量编译并且是JVM平台的话,就会执行execIncrementalCompiler方法,最后会调用到sourcesToCompile方法

private fun sourcesToCompile(
caches: CacheManager,
changedFiles: ChangedFiles,
args: Args,
messageCollector: MessageCollector,
dependenciesAbiSnapshots: Map<String, AbiSnapshot>
): CompilationMode =
when (changedFiles) {
is ChangedFiles.Known -> calculateSourcesToCompile(caches, changedFiles, args, messageCollector, dependenciesAbiSnapshots)
is ChangedFiles.Unknown -> CompilationMode.Rebuild(BuildAttribute.UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
is ChangedFiles.Dependencies -> error(“Unexpected ChangedFiles type (ChangedFiles.Dependencies)”)
}

private fun calculateSourcesToCompileImpl(
caches: IncrementalJvmCachesManager,
changedFiles: ChangedFiles.Known,
args: K2JVMCompilerArguments,
abiSnapshots: Map<String, AbiSnapshot> = HashMap(),
withAbiSnapshot: Boolean
): CompilationMode {
val dirtyFiles = DirtyFilesContainer(caches, reporter, kotlinSourceFilesExtensions)
// 初始化dirtyFiles
initDirtyFiles(dirtyFiles, changedFiles)

// 计算变化的classpath
val classpathChanges = when (classpathChanges) {
is NoChanges -> ChangesEither.Known(emptySet(), emptySet())
// classpathSnapshot可用时
is ToBeComputedByIncrementalCompiler -> reporter.measure(BuildTime.COMPUTE_CLASSPATH_CHANGES) {
computeClasspathChanges(
classpathChanges.classpathSnapshotFiles,
caches.lookupCache,
storeCurrentClasspathSnapshotForReuse,
ClasspathSnapshotBuildReporter(reporter)
).toChangesEither()
}
is NotAvailableDueToMissingClasspathSnapshot -> ChangesEither.Unknown(BuildAttribute.CLASSPATH_SNAPSHOT_NOT_FOUND)
is NotAvailableForNonIncrementalRun -> ChangesEither.Unknown(BuildAttribute.UNKNOWN_CHANGES_IN_GRADLE_INPUTS)
// classpathSnapshot不可用时
is ClasspathSnapshotDisabled -> reporter.measure(BuildTime.IC_ANALYZE_CHANGES_IN_DEPENDENCIES) {
val lastBuildInfo = BuildInfo.read(lastBuildInfoFile)
getClasspathChanges(
args.classpathAsList, changedFiles, lastBuildInfo, modulesApiHistory, reporter, abiSnapshots, withAbiSnapshot,
caches.platformCache, scopes
)
}
is NotAvailableForJSCompiler -> error(“Unexpected type for this code path: ${classpathChanges.javaClass.name}.”)
}
// 将结果添加到dirtyFiles
val unused = when (classpathChanges) {
is ChangesEither.Unknown -> {
return CompilationMode.Rebuild(classpathChanges.reason)
}
is ChangesEither.Known -> {
dirtyFiles.addByDirtySymbols(classpathChanges.lookupSymbols)
dirtyClasspathChanges = classpathChanges.fqNames
dirtyFiles.addByDirtyClasses(classpathChanges.fqNames)
}
}

// …
return CompilationMode.Incremental(dirtyFiles)
}

calculateSourcesToCompileImpl的目的就是计算Kotlin编译器应该重新编译哪些代码,主要分为以下几个步骤

  1. 初始化dirtyFiles,并将changedFiles加入dirtyFiles,因为changedFiles需要重新编译
  2. classpathSnapshot可用时,通过传入的snapshot.bin文件,与Project目录下的shrunk-classpath-snapshot.bin进行比较得出变化的classpath,以及受影响的类。在比较结束时,也会更新当前目录的shrunk-classpath-snapshot.bin,供下次比较使用
  3. 当classpathSnapshot不可用时,通过getClasspathChanges方法来判断classpath变化,这里面实际上是通过last-build.binbuild-history.bin来判断的,同时每次编译完成也会更新build-history.bin
  4. 将受classpath变化影响的类也加入dirtyFiles
  5. 返回dirtyFiles供Kotlin编译器真正开始编译

在这一步,Kotlin编译器利用输入的各种参数进行分析,将需要重新编译的文件加入dirtyFiles,供下一步使用

第七步:Kotlin编译器真正开始编译

private fun compileImpl(): ExitCode {
// …
var compilationMode = sourcesToCompile(caches, changedFiles, args, messageCollector, classpathAbiSnapshot)
when (compilationMode) {
is CompilationMode.Incremental -> {
// …
compileIncrementally(args, caches, allSourceFiles, compilationMode, messageCollector, withAbiSnapshot)
}
is CompilationMode.Rebuild -> rebuildReason = compilationMode.reason
}
// …
}

protected open fun compileIncrementally(): ExitCode {
while (dirtySources.any() || runWithNoDirtyKotlinSources(caches)) {
// …
val (sourcesToCompile, removedKotlinSources) = dirtySources.partition(File::exists)
// 真正进行编译
val compiledSources = runCompiler(
sourcesToCompile, args, caches, services, messageCollectorAdapter,
allKotlinSources, compilationMode is CompilationMode.Incremental
)
// …
}

if (exitCode == ExitCode.OK) {
// 写入last-build.bin
BuildInfo.write(currentBuildInfo, lastBuildInfoFile)
}

val dirtyData = DirtyData(buildDirtyLookupSymbols, buildDirtyFqNames)
// 写入build-history.bin
processChangesAfterBuild(compilationMode, currentBuildInfo, dirtyData)

return exitCode
}

这段代码主要做了这么几件事:

  1. 通过sourcesToCompile计算出发生改变的文件后,如果可以增量编译,则进入到compileIncrementally
  2. dirtySouces中找出需要重新编译的文件,交给runCompiler方法进行真正的编译
  3. 在编译结束之后,写入last-build.binbuild-history.bin文件,供下次编译时对比使用

到这里,增量编译的流程也就基本完成了。

总结

本文较为详细地介绍了Kotin是怎么一步步从编译入口到真正开始增量编译的,了解Kotlin增量编译原理可以帮助你定位为什么Kotlin增量编译有时会失效,也可以了解如何写出更容易命中增量编译的代码,希望对你有所帮助。
关于Kotlin增量编译还有更多的细节,本文也只是介绍了主要的流程,感兴趣的同学可直接查看KGP和Kotlin编译器的源码
参考资料
深入研究Android编译流程-Kotlin是如何编译的

作者:程序员江同学
链接:https://juejin.cn/post/7137089121989689351

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析
  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始
  • Kotlin 写 Gradle 脚本是一种什么体验?
  • Kotlin 编程的三重境界
  • Kotlin 高阶函数
  • Kotlin 泛型
  • Kotlin 扩展
  • Kotlin 委托
  • 协程“不为人知”的调试技巧
  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注鸿蒙)
img

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

在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注鸿蒙)
[外链图片转存中…(img-c4JDZFUx-1713143657265)]

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值