前言
最近的更新频率很低,虽然有很大一部分原因是我在偷懒,另外是因为想从工作中的一些奇怪的问题出发,写一些可能对大家有帮助的内容。
最近从我们编译的均值数据上发现了编译时间有劣化的现象,然后我们在buildscan
排查了下全量编译的情况下任务的耗时。发现个别模块的compileKotlin
耗时明显变长了很多,这个奇怪的现象引起了我们的注意。另外这个仓库可能最近最大的改动点就是引入了compose
,然后开发了个新的页面。所以我们初步怀疑可能就是由于compose
导致的该问题。
验证环节
我们找到了这个模块的一个切片节点,接入compose
之前和接入compose
之后。因为担心单次编译对其产生的误差影响,我们用相同环境进行了10次的打包验证。
未开启compose
情况下编译情况
开启compose
情况下编译情况
我们对比下均值数据,可以明显发现开启compose
前后的编译时长发生了明显的变化。所以足以得出结论compose
会导致编译速度变慢,而且非常大也非常明显。而且平均耗时增加了1min30s左右。
kcp
KCP
即 Kotlin Compiler Plugin(Kotlin编译器插件)
,在 kotlinc 过程中提供 hook 时机,在此期间可以生成代码、修改字节码等。
其中我们很熟悉的kotlin-android-extensions
就是一KCP
插件,虽然他现在也已经废弃了。
Compose
编译情况不同于别的的ksp
,它需要深度的参与本次编译,然后修改当前kotlin类的编译产物。将dsl等等的语法信息进行转化。
所以当我们打开gradle内的compose的时候,其实也就相当于给kcp
添加了个额外的编译插件。然后在kotlinCompiler
的过程中修改当前我们写的compose相关的代码。
buildFeatures {
compose true
}
复制代码
所以从逻辑上来说,只要我们将当前模块开启compose之后就肯定会让这个模块的编译时间拉长。以下是我找了一个demo工程从0-1完成项目打包,然后对比了下kt代码和实际的class产物。
代码语言:javascript复制Sample工程地址 我用了一个大佬的
@Composable
fun SplashPage(onNextPage: () -> Unit) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppTheme.colors.themeUi), contentAlignment = Alignment.TopCenter
) {
LaunchedEffect(Unit) {
delay(500)
onNextPage.invoke()
}
Text(
text = "Wan Android",
fontSize = 32.sp,
color = white,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(0.dp, 150.dp, 0.dp, 0.dp)
)
}
}
复制代码
代码语言:javascript复制/* compiled from: SplashPage.kt */
@Metadata(d1 = {"u0000u0010nu0000nu0002u0010u0002nu0000nu0002u0018u0002nu0002bu0002u001au001bu0010u0000u001au00020u00012fu0010u0002u001abu0012u0004u0012u00020u00010u0003Hu0007¢u0006u0002u0010u0004¨u0006u0005"}, d2 = {"SplashPage", "", "onNextPage", "Lkotlin/Function0;", "(Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V", "app_debug"}, k = 2, mv = {1, 5, 1}, xi = 48)
/* loaded from: classes7.dex */
public final class SplashPageKt {
public static final void SplashPage(Function0<Unit> onNextPage, Composer $composer, int $changed) {
Object value$iv$iv;
Intrinsics.checkNotNullParameter(onNextPage, "onNextPage");
Composer $composer2 = $composer.startRestartGroup(1819087201);
ComposerKt.sourceInformation($composer2, "C(SplashPage)23@817L6,20@724L481:SplashPage.kt#dcpdb2");
int $dirty = $changed;
if (($changed & 14) == 0) {
$dirty |= $composer2.changed(onNextPage) ? 4 : 2;
}
if ((($dirty & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
Modifier modifier$iv = BackgroundKt.m110backgroundbw27NRU$default(SizeKt.fillMaxSize$default(Modifier.Companion, 0.0f, 1, null), AppTheme.INSTANCE.getColors($composer2, 0).m4383getThemeUi0d7_KjU(), null, 2, null);
Alignment contentAlignment$iv = Alignment.Companion.getTopCenter();
$composer2.startReplaceableGroup(-1990474327);
ComposerKt.sourceInformation($composer2, "C(Box)P(2,1,3)70@3267L67,71@3339L130:Box.kt#2w3rfo");
MeasurePolicy measurePolicy$iv = BoxKt.rememberBoxMeasurePolicy(contentAlignment$iv, false, $composer2, ((0 >> 3) & 14) | ((0 >> 3) & 112));
int $changed$iv$iv = (0 << 3) & 112;
$composer2.startReplaceableGroup(1376089335);
ComposerKt.sourceInformation($composer2, "C(Layout)P(!1,2)71@2788L7,72@2843L7,73@2855L389:Layout.kt#80mrfh");
ComposerKt.sourceInformationMarkerStart($composer2, 103361330, "C:CompositionLocal.kt#9igjgp");
Object consume = $composer2.consume(CompositionLocalsKt.getLocalDensity());
ComposerKt.sourceInformationMarkerEnd($composer2);
Density density$iv$iv = (Density) consume;
ComposerKt.sourceInformationMarkerStart($composer2, 103361330, "C:CompositionLocal.kt#9igjgp");
Object consume2 = $composer2.consume(CompositionLocalsKt.getLocalLayoutDirection());
ComposerKt.sourceInformationMarkerEnd($composer2);
LayoutDirection layoutDirection$iv$iv = (LayoutDirection) consume2;
Function0 factory$iv$iv$iv = ComposeUiNode.Companion.getConstructor();
Function3 skippableUpdate$iv$iv$iv = LayoutKt.materializerOf(modifier$iv);
int $changed$iv$iv$iv = ($changed$iv$iv << 9) & 7168;
if (!($composer2.getApplier() instanceof Applier)) {
ComposablesKt.invalidApplier();
}
$composer2.startReusableNode();
if ($composer2.getInserting()) {
$composer2.createNode(factory$iv$iv$iv);
} else {
$composer2.useNode();
}
$composer2.disableReusing();
Composer $this$Layout_u24lambda_u2d0$iv$iv = Updater.m898constructorimpl($composer2);
Updater.m905setimpl($this$Layout_u24lambda_u2d0$iv$iv, measurePolicy$iv, ComposeUiNode.Companion.getSetMeasurePolicy());
Updater.m905setimpl($this$Layout_u24lambda_u2d0$iv$iv, density$iv$iv, ComposeUiNode.Companion.getSetDensity());
Updater.m905setimpl($this$Layout_u24lambda_u2d0$iv$iv, layoutDirection$iv$iv, ComposeUiNode.Companion.getSetLayoutDirection());
$composer2.enableReusing();
skippableUpdate$iv$iv$iv.invoke(SkippableUpdater.m889boximpl(SkippableUpdater.m890constructorimpl($composer2)), $composer2, Integer.valueOf(($changed$iv$iv$iv >> 3) & 112));
$composer2.startReplaceableGroup(2058660585);
int $changed$iv = ($changed$iv$iv$iv >> 9) & 14;
$composer2.startReplaceableGroup(-1253629305);
ComposerKt.sourceInformation($composer2, "C72@3384L9:Box.kt#2w3rfo");
if ((($changed$iv & 11) ^ 2) != 0 || !$composer2.getSkipping()) {
BoxScopeInstance boxScopeInstance = BoxScopeInstance.INSTANCE;
$composer2.startReplaceableGroup(1601293652);
ComposerKt.sourceInformation($composer2, "C25@910L66,25@889L87,29@985L214:SplashPage.kt#dcpdb2");
if ((((((0 >> 6) & 112) | 6) & 81) ^ 16) != 0 || !$composer2.getSkipping()) {
Unit unit = Unit.INSTANCE;
int i = $dirty & 14;
$composer2.startReplaceableGroup(-3686930);
ComposerKt.sourceInformation($composer2, "C(remember)P(1):Composables.kt#9igjgp");
boolean invalid$iv$iv = $composer2.changed(onNextPage);
Object it$iv$iv = $composer2.rememberedValue();
if (!invalid$iv$iv && it$iv$iv != Composer.Companion.getEmpty()) {
value$iv$iv = it$iv$iv;
$composer2.endReplaceableGroup();
EffectsKt.LaunchedEffect(unit, (Function2) value$iv$iv, $composer2, 0);
long sp = TextUnitKt.getSp(LiveLiterals$SplashPageKt.INSTANCE.m4958x266b4559());
long white = ColorKt.getWhite();
FontWeight bold = FontWeight.Companion.getBold();
int $this$dp$iv = LiveLiterals$SplashPageKt.INSTANCE.m4954x579b3509();
float f = Dp.m2966constructorimpl($this$dp$iv);
int $this$dp$iv2 = LiveLiterals$SplashPageKt.INSTANCE.m4955x709c86a8();
float f2 = Dp.m2966constructorimpl($this$dp$iv2);
int $this$dp$iv3 = LiveLiterals$SplashPageKt.INSTANCE.m4956x899dd847();
float f3 = Dp.m2966constructorimpl($this$dp$iv3);
int $this$dp$iv4 = LiveLiterals$SplashPageKt.INSTANCE.m4957xa29f29e6();
TextKt.m868TextfLXpl1I(LiveLiterals$SplashPageKt.INSTANCE.m4960x3de3a5a7(), PaddingKt.m283paddingqDBjuR0(Modifier.Companion, f, f2, f3, Dp.m2966constructorimpl($this$dp$iv4)), white, sp, null, bold, null, 0L, null, null, 0L, 0, false, 0, null, null, $composer2, 384, 64, 65488);
}
value$iv$iv = (Function2) new SplashPageKt$SplashPage$1$1$1(onNextPage, null);
$composer2.updateRememberedValue(value$iv$iv);
$composer2.endReplaceableGroup();
EffectsKt.LaunchedEffect(unit, (Function2) value$iv$iv, $composer2, 0);
long sp2 = TextUnitKt.getSp(LiveLiterals$SplashPageKt.INSTANCE.m4958x266b4559());
long white2 = ColorKt.getWhite();
FontWeight bold2 = FontWeight.Companion.getBold();
int $this$dp$iv5 = LiveLiterals$SplashPageKt.INSTANCE.m4954x579b3509();
float f4 = Dp.m2966constructorimpl($this$dp$iv5);
int $this$dp$iv22 = LiveLiterals$SplashPageKt.INSTANCE.m4955x709c86a8();
float f22 = Dp.m2966constructorimpl($this$dp$iv22);
int $this$dp$iv32 = LiveLiterals$SplashPageKt.INSTANCE.m4956x899dd847();
float f32 = Dp.m2966constructorimpl($this$dp$iv32);
int $this$dp$iv42 = LiveLiterals$SplashPageKt.INSTANCE.m4957xa29f29e6();
TextKt.m868TextfLXpl1I(LiveLiterals$SplashPageKt.INSTANCE.m4960x3de3a5a7(), PaddingKt.m283paddingqDBjuR0(Modifier.Companion, f4, f22, f32, Dp.m2966constructorimpl($this$dp$iv42)), white2, sp2, null, bold2, null, 0L, null, null, 0L, 0, false, 0, null, null, $composer2, 384, 64, 65488);
} else {
$composer2.skipToGroupEnd();
}
$composer2.endReplaceableGroup();
} else {
$composer2.skipToGroupEnd();
}
$composer2.endReplaceableGroup();
$composer2.endReplaceableGroup();
$composer2.endNode();
$composer2.endReplaceableGroup();
$composer2.endReplaceableGroup();
} else {
$composer2.skipToGroupEnd();
}
ScopeUpdateScope endRestartGroup = $composer2.endRestartGroup();
if (endRestartGroup == null) {
return;
}
endRestartGroup.updateScope(new SplashPageKt$SplashPage$2(onNextPage, $changed));
}
}
复制代码
从上述可以发现,虽然是一段非常简单的compose
写的逻辑ui,但是实际上编译出来的代码其实远比想象中的多得多,而且同样也会对调用点进行修改。因此导致编译速度变慢也就合情合理。
编译方面的抉择
我们定位到问题之后,就是如何选择优化了。如果各位大佬让我优化compose
的编译速度的话,那么在下肯定无能为力了。
但是如果在我们之前的快编框架体系内的话,我们会选择将新增的compose
相关的逻辑抽取出来,写成一个新的模块,然后将新旧代码进行隔离。因为对我们来说compose
只是试水而已,他的改动可能并不会这么频繁.如果直接在原来的业务模块内添加compose
的代码,因为本身模块都已经比较大了,然后又需要增加kotlinCompiler
的时间,则会导致开发体验直线下降,大家都会说ci团队做的不好,导致编译速度变慢了。
一点点小看法
项目原先对于编译相关的监控投入的其实也并不多,大部分都要依靠与buildscan
。我们也在尝试记录这部分相关的数据。和之前介绍过的一样,我们这次也是从BuildOperationNotificationListener
的ExecuteTaskBuildOperationDetails
去获取编译任务耗时相关的数据。
这次没有考虑使用TaskExecutionListener
,则还是因为复合构建的问题,由于复合构建每个gradle实例都是独立的,需要全部注册TaskExecutionListener
,还有需要实例的同步,所以就有非常多的问题。
另外还写了个很挫的Top3耗时Project分析,哈哈哈。
代码语言:javascript复制if (notification.notificationOperationDetails is ExecuteTaskBuildOperationDetails) {
try {
val d: ExecuteTaskBuildOperationDetails =
notification.notificationOperationDetails as ExecuteTaskBuildOperationDetails
val result: ExecuteTaskBuildOperationType.Result =
notification.notificationOperationResult as ExecuteTaskBuildOperationType.Result
map[notification.notificationOperationId]?.also { get ->
val stringBuffer = StringBuffer()
val exTime =
notification.notificationOperationFinishedTimestamp - get.notificationOperationStartedTimestamp
if (result.originExecutionTime != null) {
result.originExecutionTime?.apply {
if (exTime > this) {
stringBuffer.append("!!!比上次cache任务多执行 " TimeUtils.toDec(exTime - this))
} else {
stringBuffer.append("比上次cache任务减少执行 " TimeUtils.toDec(this - exTime))
}
}
stringBuffer.append("n")
}
val taskBuildData = d.create(exTime, stringBuffer.toString())
val taskKey = taskBuildData.getKey()
if (!values.containsKey(taskKey)) {
values[taskKey] = ModuleTasksData(taskKey)
}
values[taskKey]?.addTaskBuildData(taskBuildData)
}
} catch (t: Throwable) {
t.printStackTrace()
}
}
复制代码
monitor 工程传送门
代码基本如下,因为在monitor下执行,就可以监控到所有工程的编译耗时了。
总结
我个人看法虽然compose
的接入会对编译组产生一定的挑战,但是整体来说还是非常值得我们去尝试和挑战的一个技术。
加油各位打工人。