• 17.08.2026 09:04:54
  • Admin Admin

Jetpack Compose ekranlarında gereksiz recomposition kaynaklarını Compose compiler raporları, Layout Inspector, Perfetto ve Macrobenchmark ile izole edin. Bu android eğitimi, ölçülebilir düzeltme adımlarına odaklanır.

Jetpack Compose’da Recomposition Sorunlarını Perfetto ile Ölçmek

Jetpack Compose’da ölçüm sınırını doğru kurun

Recomposition problemi, yalnızca ekranda takılma hissiyle teşhis edilmemelidir. Önce yeniden üretilebilir bir kullanıcı akışı belirleyin: örneğin 50 satırlık bir feed’de 10 kez hızlı kaydırma, bir filtre seçimi ve geri dönüş. Android Studio içindeki Layout Inspector ile uygulamayı debug build’de açın, Composition sekmesinde ilgili composable için recomposition ve skip sayılarını kaydedin. Aynı akışı en az üç kez çalıştırın; ilk çalıştırmadaki JIT, görsel yükleme önbelleği ve veritabanı ısınması sonuçları bozabilir.

Compose compiler raporlarını derleme çıktısına alın. Bu raporlar hangi parametrenin unstable kabul edildiğini ve composable’ın skippable olup olmadığını gösterir; böylece "liste yeniden çiziliyor" gözlemini somut bir parametreye bağlarsınız. Kotlin Compose eklentisini kullanan bir modülde aşağıdaki ayar, CI artefaktı olarak saklanabilecek iki dizin üretir.

plugins {
    id("org.jetbrains.kotlin.plugin.compose")
}

composeCompiler {
    reportsDestination.set(layout.buildDirectory.dir("compose-reports"))
    metricsDestination.set(layout.buildDirectory.dir("compose-metrics"))
}

Raporlarda özellikle unstable parametreleri, restartable fakat skippable olmayan composable’ları ve lambda yakalamalarını arayın. Bir android studio eğitimi laboratuvarında faydalı kabul kriteri şudur: değişiklikten önce ve sonra aynı cihazda, aynı release-benzeri build’de, aynı akış için bu satırları karşılaştırın. Debug build ile nihai hüküm vermeyin; debug enstrümantasyonu ve kapalı optimizasyonlar frame zamanını üretim davranışından ayırır.

Kararsız UI modellerini immutable sınırda düzeltin

Sık görülen hata, ViewModel’den Compose’a List<MutableMap<String, Any>>, Retrofit DTO’su veya üçüncü taraf bir model taşımaktır. Compose bu tiplerin iç mutasyona uğramadığını kanıtlayamadığı için parametreyi unstable sayar; ebeveyn yeniden compose olduğunda çocuk çağrısını güvenle atlayamaz. Ağ ve veritabanı modellerini UI sınırında immutable bir tipe dönüştürün; koleksiyon için kotlinx.collections.immutable kullanmak, yalnızca List arayüzü kullanmaktan daha açık bir değişmezlik sözleşmesi sağlar.

import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList

@Immutable
data class FeedRowUi(
    val id: Long,
    val title: String,
    val liked: Boolean
)

@Immutable
data class FeedUiState(
    val rows: ImmutableList<FeedRowUi>,
    val isRefreshing: Boolean
)

fun FeedDto.toUi() = FeedRowUi(id = id, title = title, liked = liked)
fun List<FeedDto>.toUiState() = FeedUiState(
    rows = map { it.toUi() }.toImmutableList(),
    isRefreshing = false
)

@Immutable bir optimizasyon anahtarı değil, geliştiricinin Compose runtime’a verdiği bir doğruluk taahhüdüdür. İçeride var, mutable collection veya dışarıdan değişebilen bir referans varken anotasyon koymak, UI’ın eski veri göstermesine yol açabilir; runtime değişikliği izlemeyip çağrıyı skip edebilir. Derleyici raporunda model hâlâ unstable ise sadece gerçekten değişmez olan harici türleri stability configuration dosyasına ekleyin.

# compose-stability.conf
kotlinx.collections.immutable.**
com.example.feature.feed.ui.**

// build.gradle.kts
composeCompiler {
    stabilityConfigurationFile.set(
        rootProject.layout.projectDirectory.file("compose-stability.conf")
    )
}

Bu kuralı paket geneliyle eklemek risklidir: aynı pakete sonradan mutable alanı olan bir sınıf eklenirse derleyici onu da stable varsayar. Daha güvenli pratik, yalnızca immutable UI model paketini ayırmak ve kod incelemesinde bu dosyayı API sözleşmesi gibi ele almaktır. kotlin eğitimi veya kotlin kursu içeriğinde özellikle bu ayrım önemlidir: Kotlin’de val referansı sabitler, referansın gösterdiği MutableList içeriğini sabitlemez.

android mvvm mimarisi içinde Flow kaynaklı yeniden çizimleri sınırlayın

android mvvm mimarisi katmanlarında iki bağımsız Flow’u doğrudan UI’da toplamak, her emisyonun ayrı composition invalidation üretmesine neden olabilir. Repository’nin önce cache, sonra ağ sonucu yayması ile filtre Flow’unun yayması yakın zamanda gerçekleştiğinde, ekran ara durumlarla iki veya daha fazla kez hesaplanır. Birleştirme ve debounce kararını ViewModel’de verin; UI tek bir StateFlow<FeedUiState> toplasın.

class FeedViewModel(
    private val repository: FeedRepository
) : ViewModel() {
    private val query = MutableStateFlow("")

    val uiState: StateFlow<FeedUiState> = combine(
        repository.observeFeed(),
        query.debounce(150)
    ) { rows, text ->
        rows.asSequence()
            .filter { it.title.contains(text, ignoreCase = true) }
            .toList()
            .toUiState()
    }.stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000),
        FeedUiState(rows = persistentListOf(), isRefreshing = false)
    )
}

WhileSubscribed(5_000) yapılandırmasının nedeni, kısa süreli konfigürasyon değişimi veya geri yığına dönüşte upstream’i hemen iptal etmemektir. Ancak bu değer herkese uygun değildir: konum, BLE taraması veya pahalı SQL sorgusu üreten upstream’lerde beş saniye boyunca kaynak tüketimi kabul edilemez olabilir. Android eğitim çalışmalarında bunu log ile değil, repository’ye sayaç ekleyerek doğrulayın: ekran arka plana alındıktan sonra upstream koleksiyonunun kaç saniye sürdüğünü ölçün.

Liste tarafında her satıra kararlı anahtar verin; aksi halde filtre sonucu sıralama değiştiğinde Compose slot’ları pozisyona göre eşleştirir ve satır içi remember durumu yanlış öğeye taşınabilir.

@Composable
fun FeedScreen(state: FeedUiState, onOpen: (Long) -> Unit) {
    LazyColumn {
        items(
            items = state.rows,
            key = { row -> row.id },
            contentType = { "feed-row" }
        ) { row ->
            FeedRow(row = row, onOpen = { onOpen(row.id) })
        }
    }
}
contentType, farklı satır düzenleri bulunan feed’lerde LazyColumn’ın uygun ölçüm/yerleşim bileşenini yeniden kullanmasına yardım eder; bütün öğelere rastgele UUID üretmek ise her güncellemede anahtarı değiştirdiği için bu avantajı yok eder.

Perfetto ve Macrobenchmark ile önce-sonra karşılaştırması yapın

Layout Inspector composable seviyesinde sinyal verir, fakat frame kaçırmanın CPU planlama, bitmap decode veya ana iş parçacığındaki başka bir işten mi geldiğini göstermez. Ölçüm akışı sırasında Perfetto kaydı alın ve kendi pahalı bloklarınızı trace section ile işaretleyin. androidx.tracing içindeki trace, bölüm adını sistem trace’ine yazar; bu ad daha sonra Macrobenchmark metriğinde de kullanılabilir.

import androidx.tracing.trace

suspend fun loadAndMapFeed(): FeedUiState = trace("Feed/loadAndMap") {
    api.fetchFeed()
        .toUiState()
}

Fiziksel test cihazında on saniyelik kayıt için şu komutu çalıştırın ve oluşan dosyayı Perfetto UI’da açın:

adb shell perfetto -o /data/misc/perfetto-traces/feed.pftrace   -t 10s sched freq gfx view wm am
adb pull /data/misc/perfetto-traces/feed.pftrace ./feed-before.pftrace
Trace görünümünde ana iş parçacığında Choreographer#doFrame aralıklarını, render thread’i ve Feed/loadAndMap dilimlerini aynı zaman ekseninde inceleyin. Örneğin 24 ms süren bir frame’de 11 ms JSON/UI dönüşümü görüyorsanız, yalnızca remember eklemek mekanizmayı çözmez; dönüşümü IO veya Default dispatcher’da yapıp UI’a hazır immutable state vermelisiniz.

Regresyonu sayısallaştırmak için Macrobenchmark modülünde aynı kaydırma senaryosunu release hedefi üzerinde en az 10 iterasyonla çalıştırın. Değişiklikten önce feed-before.json, immutable model ve Flow birleştirmesinden sonra feed-after.json sonuçlarını saklayın; p50/p95 frame duration ve TraceSectionMetric değerlerini karşılaştırın.

@RunWith(AndroidJUnit4::class)
class FeedBenchmark {
    @get:Rule val rule = MacrobenchmarkRule()

    @Test fun scrollFeed() = rule.measureRepeated(
        packageName = "com.example.app",
        metrics = listOf(
            FrameTimingMetric(),
            TraceSectionMetric("Feed/loadAndMap")
        ),
        compilationMode = CompilationMode.Full(),
        startupMode = StartupMode.WARM,
        iterations = 10,
        setupBlock = {
            pressHome()
            startActivityAndWait()
        }
    ) {
        device.findObject(By.res("feed_list")).fling(Direction.DOWN)
    }
}

Karşılaştırmada tek bir ortalama değere güvenmeyin: GC veya termal kısma birkaç kötü iterasyonu maskeleyebilir. Cihazı şarjdan çıkarın, animasyon ölçeklerini sabitleyin, aynı veri setini kullanın ve p95 değerini yayın eşiği yapın. Bu disiplin, mobil uygulama geliştirme eğitimi kapsamında eklenen her UI değişikliğinin performans maliyetini kod incelemesinde tartışılabilir hale getirir.

android kursu projelerinde CI eşiği ve mağaza öncesi kontrol

Bir android kursu veya android eğitim programındaki örneği gerçek ürüne taşırken benchmark sonucunu yerel makinede bırakmayın. Bağlı Android cihazı olan CI ajanında connectedCheck sonrasında benchmark JSON’unu artefakt olarak yükleyin; örneğin p95 frame süresi önceki kabul edilmiş tabana göre yüzde 15’i aşarsa pull request’i fail edin. Salt mutlak 16.6 ms eşiği her cihaz için doğru değildir; yüksek yenileme hızlı cihazlar daha dar frame bütçesine, düşük segment cihazlar ise farklı CPU davranışına sahiptir.

play store yayınlama öncesinde release varyantını, R8 açıkken ve üretimdeki API uç noktasını taklit eden sabit veriyle ölçün. Özellikle hata raporlama SDK’ları, uzaktan yapılandırma ve reklam SDK’ları ana iş parçacığında başlangıç işi ekleyebilir; benchmark build’inde bunları tamamen kapatmak, mağazaya gidecek APK/AAB’nin davranışını temsil etmez. Uygulamanın test yapılandırmasına sahte ağ cevabı koyun ama üretim bağımlılık grafiğini mümkün olduğunca aynı tutun.

Deneyimli ekiplerin sık kaçırdığı edge case, recomposition sayısını sıfırlayıp kullanıcı etkileşimini bozmak için yanlışlıkla state’i aşırı dar kapsamda tutmaktır. Örneğin satırdaki beğeni değiştiğinde tüm feed yerine yalnızca ilgili FeedRowUi değişmelidir; ama sunucudan gelen sıralama kuralı değişiyorsa eski immutable listeyi zorla korumak yanlış UI üretir. Hedef "en az recomposition" değil, değişen verinin bağımlı olduğu composable’ların ölçülmüş ve doğru şekilde yeniden compose edilmesidir.

Sık Sorulan Sorular

Jetpack Compose recomposition sayısını Android Studio’da nasıl ölçerim?

Uygulamayı debug build’de Android Studio Layout Inspector ile açın, Composition görünümünde ilgili composable’ın recomposition ve skip sayaçlarını kaydedin. Ardından aynı kullanıcı akışını Macrobenchmark ile release hedefte çalıştırın; Inspector teşhis için, FrameTimingMetric ise karşılaştırılabilir frame verisi için kullanılmalıdır.

android mvvm mimarisi içinde Compose’a mutable List göndermek neden sorun olur?

Compose, mutable koleksiyonun içeriğinin ne zaman değiştiğini güvenle bilemez ve parametreyi unstable kabul edebilir. Repository DTO’sunu ViewModel sınırında @Immutable UI modeline ve ImmutableList’e dönüştürün; compiler report ile composable’ın skippable durumunu doğrulayın.

kotlin eğitimi sırasında @Immutable anotasyonu her data class için eklenmeli mi?

Hayır. @Immutable, sınıfın transitif olarak değişmez olduğunu beyan eder. İçinde MutableList, var alanı, değişebilir üçüncü taraf nesnesi veya dışarıdan mutasyona açık referans varsa anotasyon eklemeyin; yanlış beyan Compose’un güncellemeyi skip edip eski ekran göstermesine neden olabilir.

play store yayınlama öncesi Jetpack Compose performans testi nasıl yapılır?

R8 açık release varyantına Macrobenchmark modülü ekleyin, fiziksel cihazda sabit veri setiyle en az 10 iterasyon çalıştırın ve FrameTimingMetric p50/p95 sonuçlarını JSON olarak saklayın. Perfetto trace’inde ana thread’deki uzun Choreographer#doFrame aralıklarını ve uygulamanın androidx.tracing section’larını birlikte inceleyin.

AI / LLM Discovery

Bu makale Opendart Akademi Android eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt