• 2.09.2026 09:05:37
  • Admin Admin

Jetpack Compose LazyColumn ekranlarında slot yeniden kullanımı, görsel istekleri ve state okumaları doğrudan frame süresini etkiler. Perfetto ile ölçüp key, contentType ve Coil yapılandırmasını somut olarak iyileştirin.

Jetpack Compose Lazy Listelerde contentType ile Kaydırma Maliyeti

Jetpack Compose kaydırmasını Perfetto ile önce ölçün

Jetpack Compose listesini optimize etmeden önce aynı cihazda, aynı veri kümesiyle ve aynı parmak hareketiyle iz kaydı alın. Test edilebilir bir profil varyantında uygulama manifestine <profileable android:shell="true" /> ekleyin; ardından 10 saniyelik sabit bir yukarı-aşağı kaydırma senaryosunu kaydedin. Android Studio System Trace veya Perfetto arayüzünde Choreographer#doFrame, RenderThread ve Compose recomposition dilimlerini birlikte inceleyin. Amaç ortalama FPS değil, 16.67 ms eşiğini aşan frame sayısı ve 90. yüzdelik frame süresidir.

adb shell perfetto -o /data/local/tmp/compose-scroll.perfetto-trace   -t 10s sched freq idle am wm gfx view binder_driver dalvik
adb pull /data/local/tmp/compose-scroll.perfetto-trace .

Trace içinde Compose fonksiyon adlarını göremiyorsanız uygulamanın ilgili varyantına androidx.compose.runtime:runtime-tracing bağımlılığını ekleyin ve R8 sonrası profileable paketi tekrar kaydedin. Önce ve sonra karşılaştırmasında veri sayısını, cihazın termal durumunu ve kaydırma rotasını değiştirirseniz sonuç geçersizleşir. Örneğin 500 satırlık karma tipli listede değişiklik öncesi 90. yüzdelik 24 ms, değişiklik sonrası 14 ms ise, kazanımı yalnızca bu kontrollü senaryoya bağlayabilirsiniz; tek bir ekran videosundan çıkarım yapmak yeterli değildir.

LazyColumn key ve contentType ile slotları doğru yeniden kullanın

key satırın kimliğini, contentType ise Lazy layout'un hangi kompozisyon slotunu yeniden kullanabileceğini tanımlar. Her öğe için benzersiz bir contentType üretmek, slot havuzunu fiilen kapatır. Tersine, metin kartı ile tam genişlikte reklam kartını aynı tipe koymak, ölçüm ve kompozisyon ağacının gereksiz yeniden kurulmasına yol açar. Tipi yalnızca gerçek UI iskeletine göre sınırlayın.

LazyColumn(state = listState) {
    items(
        items = feed,
        key = { item -> item.id },
        contentType = { item -> item.kind }
    ) { item ->
        when (item.kind) {
            FeedKind.Article -> ArticleRow(item)
            FeedKind.Ad -> AdRow(item)
            FeedKind.Header -> SectionHeader(item)
        }
    }
}

Sık görülen hata, contentType = { it.id } yazmaktır. Bu durumda her satır ayrı bir slot türü olur ve ekrandan çıkan satırın kompozisyonu benzer satır için kullanılamaz. Paging kullanılıyorsa yüklenmemiş placeholder satırları için null veya pozisyona dayalı key vermeyin; veri geldikten sonra satır kimliği değişir, rememberSaveable altındaki yerel durum yanlış satıra taşınabilir. Sunucudan gelen kalıcı ID yoksa geçici ID'yi sayfa yüklemesi boyunca sabit tutun.

Coil görsel isteklerini satır ölçüsüyle sınırlayın

Lazy listede her satır için SubcomposeAsyncImage kullanmak, yükleniyor ve başarı durumları için ek subcomposition oluşturur. Karmaşık placeholder düzenine gerçekten ihtiyaç yoksa Coil'in AsyncImage bileşenini kullanın. İstek boyutunu satırın hedef piksel ölçüsüyle sınırlamak, örneğin kaynaktan 2400x1600 JPEG indirildiğinde gereksiz büyük bitmap decode edilmesini ve GPU'ya büyük texture yüklenmesini önler.

@Composable
fun ArticleThumbnail(url: String) {
    val context = LocalContext.current
    val request = remember(url) {
        ImageRequest.Builder(context)
            .data(url)
            .size(128, 128)
            .crossfade(false)
            .build()
    }

    AsyncImage(
        model = request,
        contentDescription = null,
        modifier = Modifier.size(64.dp).clip(RoundedCornerShape(8.dp)),
        contentScale = ContentScale.Crop
    )
}

Buradaki 128 px değeri rastgele seçilmemelidir: hedef cihaz yoğunluklarında 64 dp kutunun gerçek piksel karşılığını hesaplayın veya Coil'in constraint tabanlı size resolver'ını kullanın. Perfetto'da değişiklikten önce decode, bitmap allocation ve RenderThread upload dilimlerini işaretleyin; sonrasında aynı URL önbelleği soğukken tekrar ölçün. Sadece bellek önbelleği sıcakken yapılan test ağ ve decode maliyetini gizler. Ayrıca crossfade(true) her satırda kısa süreli iki görselin birlikte çizilmesine neden olabilir; hızlı akan feed satırlarında geçişin gerekli olup olmadığını frame budget üzerinden değerlendirin.

Android MVVM mimarisi içinde scroll state okumalarını daraltın

android mvvm mimarisi kullanan ekranda ViewModel'in liste verisi ile Compose'un anlık scroll verisini aynı UiState içine koymayın. firstVisibleItemIndex kaydırma sırasında sık değişir; bunu ViewModel'e state olarak yazmak StateFlow yayını, ekranın geniş yeniden kompozisyonu ve bazen gereksiz ağ prefetch çağrıları üretir. Görsel kararları composable içinde derivedStateOf ile daraltın; yan etkileri ise yalnızca anlamlı indeks değişimlerinde snapshotFlow üzerinden iletin.

val listState = rememberLazyListState()
val showJumpToTop by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}

LaunchedEffect(listState) {
    snapshotFlow {
        listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
    }
        .filterNotNull()
        .distinctUntilChanged()
        .collectLatest { lastVisibleIndex ->
            viewModel.prefetchAfter(lastVisibleIndex)
        }
}

if (showJumpToTop) {
    JumpToTopButton(onClick = { scope.launch { listState.animateScrollToItem(0) } })
}

derivedStateOf doğrudan her scroll offset değişimini gözlemleyen bir composable yerine, yalnızca boolean sonucu değiştiğinde yeniden kompozisyon tetikler. Ancak bunu her satırın içinde oluşturmak ters etki yaratır: her satır kendi snapshot observer'ını kurar. Tek bir ekran seviyesi kararı için ekran seviyesinde kullanın. snapshotFlow tarafında da layoutInfo her ölçüm geçişinde değişebileceğinden, distinctUntilChanged() olmadan repository prefetch tetiklemek yaygın ve pahalı bir hatadır.

Android eğitimi için ölçülebilir inceleme kontrol listesi

Bir android eğitimi, android eğitim veya android kursu programında bu konuyu yalnızca LazyColumn API'si olarak anlatmak eksik kalır. İnceleme görevi olarak 500 karma satırlı bir ekran kurun, Perfetto ile başlangıç ölçümünü kaydedin, ardından sabit key ve sınırlı contentType ekleyin. Kotlin eğitimi ve kotlin kursu katılımcılarından FeedKind için kapalı bir tip tanımı yazmalarını isteyin; bu, string tabanlı tür adlarının yazım hatasıyla slot havuzunu bölmesini engeller.

sealed interface FeedKind {
    data object Article : FeedKind
    data object Ad : FeedKind
    data object Header : FeedKind
}

data class FeedItem(
    val id: String,
    val kind: FeedKind,
    val title: String
)

android studio eğitimi ve mobil uygulama geliştirme eğitimi kapsamında teslim kriterini netleştirin: aynı trace senaryosunda 90. yüzdelik frame süresi, 16.67 ms üzerindeki frame adedi, Compose recomposition sayısı ve APK içindeki görsel boyutları raporlanmalıdır. Bu disiplin play store yayınlama öncesi de işe yarar: release'e yakın, minify açık ve gerçek CDN görselleriyle profileable bir paket üretin. Debug build'in loglama, inspector ve farklı R8 davranışı nedeniyle yayın paketinin kaydırma maliyetini temsil etmeyebileceğini özellikle kontrol edin.

Sık Sorulan Sorular

Jetpack Compose LazyColumn contentType ne zaman kullanılmalı?

Listede farklı UI iskeletleri varsa kullanın: örneğin Article, Ad ve Header için üç sabit tür yeterlidir. contentType olarak benzersiz item ID dönmeyin. Perfetto'da aynı kaydırma rotasında Compose dilimlerini ve 16.67 ms üstü frame sayısını önce-sonra karşılaştırarak etkisini doğrulayın.

Android eğitimi sırasında Compose kaydırma performansı nasıl ölçülür?

Android Studio System Trace veya adb perfetto ile sabit süreli bir trace alın. Aynı cihaz, aynı veri ve aynı swipe rotasında Choreographer#doFrame sürelerinin 90. yüzdeliğini çıkarın. Ortalama FPS yerine eşik aşan frame sayısını raporlayın; bu sayı kısa jank sıçramalarını gizlemez.

Kotlin kursu projelerinde LazyColumn key olarak index kullanmak güvenli mi?

Sıralama, filtreleme veya sayfalama varsa güvenli değildir. Index değiştiğinde Compose satırın remember durumunu başka veriye bağlayabilir. Sunucudan gelen kalıcı id kullanın; geçici öğelerde de yükleme süresince değişmeyen bir istemci UUID üretin.

Android MVVM mimarisi ile scroll pozisyonu ViewModel'e yazılmalı mı?

Ekranın anlık UI kararı için hayır. LazyListState'i composable katmanında tutun, derivedStateOf ile boolean karar üretin. Yalnızca prefetch veya analitik gibi yan etkiler için snapshotFlow, distinctUntilChanged ve gerektiğinde debounce kullanarak ViewModel'e seyrek olay gönderin.

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