Jetpack Compose’da Optimizasyon

Sumeyyesaricam
Ford Otosan
Published in
5 min readDec 19, 2023
Photo by Peter Kaltenborn on Unsplash

Selamlar, bu yazımda Jetpack Compose’da performans iyileştirmelerini aktaracağım. Şimdiden keyifli okumalar.

Konuyu 4 ana başlık altında inceleyeceğiz. Bunlar;

  1. Compose Phases
  2. Layout Inspector
  3. Stability
  4. Compose Compiler Reports

1. Compose Phases

Compose’da UI render işlemi 3 ayrı aşamadan oluşur.

  • Composition aşamasında; uygulamanın UI’ını oluşturan Composable fonksiyonlar belirlenir ve çalıştırılır. Bu fonksiyonlar, uygulamanın görsel arayüzünü temsil eden component tree’yi oluşturur. (Şekil 1.0)
Şekil 1.0
  • Layout aşamasında; UI’daki her bir component’in boyutu ölçülür. Cihazın boyutlarına göre, belirtilen x ve y koordinatlarına göre konumlandırılır. Bu aşama, UI’ın düzenini ve component’ler arasındaki ilişkileri belirler. (Şekil 1.1)
Şekil 1.1
  • Drawing aşamasında; önceki aşamalarda hesaplanan boyut ve koordinatlara göre her bir component Canvas kullanılarak çizilir. Bu aşama UI’ın fiziksel görünümünü oluşturur. (Şekil 1.2)
Sekil 1.2

Component tree’de bulunan düğüm sayısı, UI çizme süresi ile doğru orantılıdır. Düğüm sayısı arttıkça çizim süresi de artacaktır. Bu nedenle, UI’ınızı tasarlarken sadece gerekli olan bileşenleri kullanmaya özen gösterelim.

Herhangi bir state değişikliğinde compose bu değişikliği algılayacak ve UI’ın yeniden çizilmesi sürecini yönetecektir. Bu süreç recomposition olarak adlandırılır.

Compose, herhangi bir aşamaya ihtiyaç duymadan sadece gerekli olan aşamayı çalıştırarak uygulamanın performansını artırabilecek yapıdadır. Örneğin; sadece drawing aşamasında bir değişiklik varsa Layout ve Composition aşamalarını tekrar çalıştırmadan sadece Drawing aşamasını güncelleyebilir.

Örneklendirirsek:

Elimizde bir Box component’i olsun. Component’e her tıkladığımızda rengi değişsin istiyoruz. Bunun için de boxColor adında bir state değişkeni tanımladık. Componente her tıklandığında da state’in değerini değiştiriyoruz. (Aşağıdaki kodlar referans alınabilir.)

val boxColor = remember {
mutableStateOf(Color.Red)
}
Box(
modifier = Modifier
.wrapContentSize()
.width(200.dp)
.height(200.dp)
.background(boxColor.value)
.clickable {
boxColor.value = Color.Yellow
}
)
Box(
modifier = Modifier
.wrapContentSize()
.width(200.dp)
.height(200.dp)
.drawBehind { //DrawScope
drawRect(boxColor.value)
}
.clickable {
boxColor.value = Color.Yellow
}
)

boxColor state’inin okunduğu yeri iki farklı yöntemle ele alalım. İlk yöntemde background fonksiyonunda state değeri okunurken diğerinde drawBehind içinde okunuyor. Her iki işlemle de background rengini değiştirebiliyoruz. drawBehind fonksiyonu (Canvas(), Modifier.drawWithContent.. gibi) diğer aşamaları çalıştırmadan sadece draw aşamasını çalıştırmamı sağlar. Böylece her tıklamada gereksiz yere recompose olmasının önüne geçmiş oluruz.

2. Layout Inspector

Performans sorunlarının kaynağını bulmak zor olabilir. Bu gibi sorunların ana kaynağı Recomposition sürecinin gereksiz yere tekrardan tetiklenmesidir. Bu sorunları tespit edebilmek için Layout Inspector tool’undan faydalanabiliriz.

Layout Inspector, geliştirdiğimiz ekranın component tree yapısını ve recomposition gerçekleştiğinde hangi component’lerin etkilendiğini gösterir. Örneğin, Şekil 1.3 deki görselde sol tarafta component tree yapısını ve bir Switch component’inin 9 kez recompose olduğunu ve StudentInfo component’ininde 9 kez skip edildiğini (recompose işlemine dahil edilmediğini) görebiliyoruz.

Buradaki önemli nokta, recomposition’ın sadece ilgili component’te gerçekleşmesi (gerçekten state’i değişen component’in recompose olması) ve diğer component’leri etkilememesi gerektiğidir.

Şekil 1.3

3. Stability

Compose bize iki tip verir. stable ve unstable.

  • Stable, değişmeyen ve kararlı bir yapıya sahip olduğu bilgisini verir ve recomposition gerçekleştiğinde Compose tarafından değişiklik olup olmadığı bilgisi Compose’a iletilir. Compose fonksiyonu; değişiklik yoksa atlanır, varsa recompose olur.
  • Unstable, değişebilir ve kararsız bir yapıya sahip olduğu bilgisini verir. Compose değişkenin değerine bakılmaksızın parent componentde recompositon gerçekleşirse, compose fonksiyon da recompose olur.

Eğer uygulama içinde çok fazla unstable parametreler varsa her recomposition’da etkileneceği için performans sorunlarına neden olabilir.

Stable bir data class ile unstable bir data class’ın nasıl çalıştığına bakalım.

//Unstable Data Class
data class MutableStudent(var name: String, var count: Int)
//Stable Data Class
data class ImmutableStudent(val name: String, val age: Int)
@Composable
fun GreetingMutable() {
var isChecked by remember { mutableStateOf(false) }
val mStudent by remember {
mutableStateOf(MutableStudent("Sümeyye", 20))
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
StudentInfo(mStudent)
Switch(checked = isChecked, onCheckedChange = {
isChecked = !isChecked
})
}
}
}
@Composable
fun StudentInfo(student: MutableStudent) {
Column {
Text(text = "Name: " + student.name)
Text(text = "Age: " + student.age)
}
}
  • Buradaki kodda switch her değiştiğinde isChecked state’i değişir.
  • State değişikliği gerçekleştiğinde parent component’e bildirilir.
  • Compose; child componentlerin stable ya da unstable parametrelere sahip olup olmama durumuna bakar ve eğer;
    * stable ise eski ve yeni değerlerini kontrol ederek gerekiyorsa recompose işlemini gerçekleştirir.
    * unstable ise direkt recompose işlemini gerçekleştirir.
  • Data class’ların compose compiler çıktısına bakalım.
Şekil 1.4

Şekil 1.4'te görüldüğü gibi data class değişkenlerinin var ile tanımlandığında unstable, val ile tanımlandığında ise stable olarak işaretlendiğini görüyoruz.

Uygulamamızı unstable class ile çalıştırıp Layout Inspector’da incelediğimizde Şekil 1.5’deki gibi bir çıktı elde ederiz.

Şekil 1.5

Layout Inspector’daki görüntüye göre StudentInfo recomposition sayısı her switch’e tıklandığında (isCheck değişkeni) değişmekte. Ama unstable objeyi (student) parametre alan StudentInfo component’i de bu recomposition’dan etkilenir ve recompose olur. Bu istenen bir durum değildir.

Şimdi bir de stable class çıktısına bakalım.

Şekil 1.6

Burada ise switch component’ine her tıklandığında StudentInfo component’i ilgili state değişikliğinden etkilenmez. Bu durumu sağlayan ise StudentInfo component’i içerisinde stable parametre kullanılmasıdır. Stable parametreler kullanıldığından dolayı compose bu component’i skippable olarak işaretleyecektir.

Compose compiler çıktılarından birinde compose fonksiyonumun Şekil 1.7 deki gibi restartable ve skippable olarak işaretlendiğini görüyoruz.

Şekil 1.7
  • Skippable, Compose fonksiyona gelen parametreler eskisiyle aynı değere sahipse recompose atlanacaktır.
  • Restartable, compose recomposition gerçekleştiğinde durum değişikliklerinden haberdar ve tekrar çalıştırılabilir.

4. Compose Compiler Reports

Bir önceki bölümde compose compiler raporlarından bahsetmiştim. Compose compiler raporlarını görmek istersek aşağıdaki 2 yöntemden birini kullanarak raporları elde edebiliriz.

  1. Aşağıdaki task projeye dahil edilir.
subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
if (project.findProperty("composeCompilerReports") == "true") {
freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.buildDir.absolutePath}/compose_compiler"
)
}
if (project.findProperty("composeCompilerMetrics") == "true") {
freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.buildDir.absolutePath}/compose_compiler"
)
}
}
}
}
  • Aşağıdaki komut terminalden çalıştırılarak rapor üretilir.

./gradlew assembleRelease -PcomposeCompilerReports=true

2. Compose Compiler Reports to HTML Generator plugin’ini projemize dahil ederek raporları oluşturabiliriz.

Yazıda paylaştığım kodlara buradan erişebilirsin.

Yazımı burada sonlandırıyorum, umarım faydalı olmuştur. :)

--

--