Closed guoguo338 closed 1 week ago
The whole process of showing an application includes:
Initialization of Compose. Happens only when we start an application.
Preparing the application. Happens every time we open a new window/dialog, or just change the content of it.
On every step we can do some optimization on the framework level, but it is not always possible to optimize it completely, and developer of the application should care of optimization of the second step (preparing the application).
If it is not possible to do all the work fast in the first frame, the application developer should schedule some work to the next frames. For desktop it can be achieved this way:
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowScope
import androidx.compose.ui.window.singleWindowApplication
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
fun main() = singleWindowApplication {
val isWindowVisible by isWindowVisibleState()
Box(Modifier.fillMaxSize().background(Color.LightGray)) {
if (!isWindowVisible) {
LightUI()
} else {
HeavyUI()
}
}
}
@Composable
private fun WindowScope.isWindowVisibleState(): State<Boolean> {
val isVisible = remember { mutableStateOf(false) }
DisposableEffect(Unit) {
val listener = object : ComponentAdapter() {
override fun componentShown(e: ComponentEvent?) {
isVisible.value = window.isVisible
}
}
window.addComponentListener(listener)
onDispose {
window.removeComponentListener(listener)
}
}
return isVisible
}
@Composable
fun LightUI() {
Row {
repeat(3) {
Box(
Modifier
.width(200.dp)
.fillMaxHeight()
.border(1.dp, Color.Gray)
)
}
}
}
@Composable
fun HeavyUI() {
Row {
repeat(3) {
Column(
Modifier
.width(200.dp)
.fillMaxHeight()
.border(1.dp, Color.Black)
.verticalScroll(rememberScrollState())
) {
repeat(50) {
var text by remember { mutableStateOf("") }
TextField(text, { text = it })
}
}
}
Column {
// loading images asynchronously
}
}
}
(maybe we should provide a similar check in Compose itself, for all platforms)
Anyway, the startup of Compose application can be very slow (2-4 seconds), so we need:
Optimize some of the steps (where we can):
Provide tools to do additional optimizations on the application level:
Provide tools to measure performance of each step (in IDE, or just in console)
Describe them in our documentation
delay the number of modifiers until 2nd frame
Good idea, but not sure that is possible in general case. This work should be done in each modifier - we should look what modifiers are slow, and postpone some work in them.
the slot-table for first frame could be captured and loaded in advanced next time
It is not possible to do on the framework level, because we can't know which part can be loaded next time, that should be decided by application developer. Also, if we talk about serialization of some part (storing it on a disk), it is also not possible on the framework level, as the slot table contain a lot of non-serializable data. But anyway, we can provide tools, so developers can easily choose parts which they decide can be postponed or/and serialized.
Related issue: https://github.com/JetBrains/compose-jb/issues/2517
Just a small hint: one thing that helps (at least on macOS) is loading fonts in parallel, if you have a main menu. Launch a coroutine on a background dispatcher even before you call application { … }
and call this magic: UIManager.getFont("Panel.font").fontName
. While Compose app is loading classes, initializing, etc, at least part of the font system would be ready.
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
Hi,
Is there any way to shrink the time for "First Meaning Paint" in compose framework side, i.e., the time for the first page rendering. For example, maybe we can delay the number of modifiers until 2nd frame? Maybe the slot-table for first frame could be captured and loaded in advanced next time?