Oluşturma işlemi, kesinlikle sıralı ve ileriye doğru akan üç aşamada gerçekleştirilir:
- Oluşturma: Kullanıcı arayüzü ağacını oluşturmak ve güncellemek için
@Composableişlevlerini çalıştırır. - Düzen: Çocukları ölçer ve yerleştirir.
- Çizim: Pikselleri ekranda oluşturmak için tuval çizim komutları gönderir.
Herhangi bir aşamada bir Compose State okunduğunda Compose, bu durum ile ilgili aşama arasında otomatik olarak bağımlılık kaydeder.
Yazma işlemini "geriye" doğru yapan nedir?
Geriye doğru yazma, bir durum daha sonraki bir aşamada değiştirildiğinde (veya aşağı akış kapsamı içinde, yani aynı kompozisyon geçişinde daha sonra sırayla yürütülen birleştirilebilir bir kapsamda) okunduğu yerden daha sonra gerçekleşir. Bu durum, Compose'un daha önceki bir aşamayı veya birleştirilebilir öğeyi tekrar çalıştıracak şekilde planlayarak yeniden oluşturmaya zorlar. Geriye doğru yazma, optimize edilmemiş bir yeniden oluşturma döngüsüdür.
Geriye doğru yazma işlemlerinin sonuçları
Geriye doğru yazma işlemi mutlaka kötü bir şey değildir ve her zaman kilitlenmeye neden olmaz. Ancak verimsizdir ve uygulama performansına çeşitli şekillerde zarar verebilir:
- Ekstra kare oluşturma ve atlanan kareler: Geriye doğru yazma, Compose'u art arda gelen karelerde gereksiz kompozisyon geçişleri yapmaya zorlar. Bu durum, CPU ve GPU kaynaklarının boşa harcanmasına ve olası duraklamalara neden olur.
- İlk karede doğruluk sorunları: Bileşeninizin son boyutlarını veya durumunu çözmek için geriye doğru yazma işlemi yapması gerekiyorsa ilk kare, geçersiz, varsayılan veya yerleşmemiş verilerle (ör. sıfır boyut ya da yanlış ofset) oluşturulur. Bu durum, ikinci kare oluşturulurken görsel olarak belirgin bir atlama veya düzenin yanıp sönmesine neden olur.
- Sonsuz yeniden oluşturma döngüleri: Bir durum değişikliği düzen boyutunu değiştirirse ve düzen boyutu sürekli olarak duruma yeni bir değer geri yazarsa ekranın hiç dengelenmeden her karede sürekli olarak yeniden oluşturulduğu sonsuz bir kare döngüsü oluşturma riskiyle karşı karşıya kalabilirsiniz.
Aşamalar arasında ileriye doğru akış
Durum değişiklikleri her zaman aşamalar arasında ilerlemelidir:
| Read Phase (Okuma Aşaması) | Yazma Bağlamı | Kabul edilebilir mi? | Neden? |
|---|---|---|---|
Düzen (Modifier.offset { }) |
Kompozisyon | Evet | Bileşen durumu günceller → Düzen, aynı çerçevede daha sonra yeniden oluşturmadan okur. |
Çizim (graphicsLayer { }, drawBehind { }) |
Kompozisyon | Evet | Bileşen durumu günceller → Çizim, son aşamada durumu okur. Kompozisyon ve Düzen tamamen atlanır. |
| Çiz | Düzen | Evet | Düzen durumu günceller ve çizim bunu okur. Geçerli akış. |
| Kompozisyon | Etkinlik geri çağırması (onClick, onValueChange) durum değişikliğine neden oluyor. Not: Düzen geri çağırmaları etkinlik olarak sayılmaz. |
Evet | Bir etkinlik, kompozisyonu yönlendirmek için kullanılan durumu değiştirir. Etkinlik, çerçeve dışında (Kompozisyon, Düzen veya Çizim'de değil) gerçekleşirse bu geçerlidir. |
| Kompozisyon | Eş yordam (LaunchedEffect) |
Evet, dikkatli bir şekilde | Yaşam döngüsüne/etkinliklere yanıt olarak durumu eşzamansız şekilde günceller. Efektlerden yazma işlemleri geçerli olabilir ancak verimsiz durum katmanlarına işaret edebilir. Mümkün olduğunda bunlardan kaçınılmalıdır. |
| Yerleşim (düzende) | Ölçü (Düzende) | Evet | Düzen'de, durumu güncelleyip yerleşimde okumak kabul edilebilir. |
| Ölçü (Düzende) | Yerleşim (düzende) | Hayır - geriye doğru yazma | Yerleştirme sırasında yazma işlemi yapılması ve ardından okuma durumuna geçilmesi, yeniden ölçüm döngüsüne neden olur. |
| Kompozisyon | Düzen (onSizeChanged, LayoutModifier) |
Hayır - Tersine yazma | Düzen, Bileşimi geçersiz kılıyor → Yeniden oluşturma döngüsü. |
| Kompozisyon | Çizim (drawWithContent, Canvas) |
Hayır - Tersine yazma | Çizim, bileşimi geçersiz kılar → Yeniden oluşturma döngüsü. |
Evre kombinasyonları: Geriye dönük ve ileriye dönük
Aşağıda, Oluşturma'daki geriye doğru yazma örnekleri ve bunları nasıl çözebileceğiniz açıklanmaktadır.
Geriye doğru: Besteleme modunda okuma, Düzen modunda yazma
- Ne olur?: Bileşen, hangi kullanıcı arayüzünün yayınlanacağını belirlemek için
componentHeightöğesini okur. Çerçevenin ilerleyen kısımlarında, düzen aşamasında görünümler ölçülür veya yerleştirilir vecomponentHeightiçin yeni bir değer yazılır (örneğin,onSizeChanged,onGloballyPositionedveya özelLayoutModifierkullanılarak). - Sonuç: Düzen'de
componentHeightdeğiştirildiğinde, yeni tamamlanan Kompozisyon aşaması geçersiz kılınır.onSizeChangedraporlarının, düzen ölçümü geçişi tamamlandıktan sonra boyutlandığını unutmayın. Güncellenen durum değeri bir sonraki geçişte sabitlenirse yeniden oluşturma işlemi bir ek kare sonra durabilir. Ancak yeni değer boyutu değiştirmeye devam ederse sonsuz kare döngüsüne neden olur. AyrıcaonGloballyPositioned, hem düzen hem de yerleşimden sonra çalışır. Bu nedenle, içinde durum yazma işlemleri, ardışık karelerde sürekli yeniden oluşturma ve yeniden düzenleme döngülerine daha da yatkın hale gelir.
// ❌ BAD: Read in Composition, Written in Layout (onSizeChanged) @Composable fun BadAspectRatioImage(painter: Painter) { var calculatedHeight by remember { mutableStateOf(0.dp) } val density = LocalDensity.current // State read during COMPOSITION: Image( painter = painter, contentDescription = "Dynamic Image", modifier = Modifier .fillMaxWidth() .height(calculatedHeight) .onSizeChanged { size -> // State write during LAYOUT phase! // Triggers backwards write and recomposition pass val aspectRatio = 16f / 9f val widthDp = with(density) { size.width.toDp() } calculatedHeight = widthDp / aspectRatio } ) } // ✅ GOOD: Measure and calculate aspect ratio height in Phase 2 (Layout) without recomposition @Composable fun GoodAspectRatioImage( painter: Painter, aspectRatio: Float = 16f / 9f, modifier: Modifier = Modifier ) { Layout( content = { Image( painter = painter, contentDescription = "Dynamic Image" ) }, modifier = modifier ) { measurables, constraints -> val width = constraints.maxWidth val height = (width / aspectRatio).toInt() // Illustrative, you can use Modifier.aspectRatio() val imageConstraints = constraints.copy( minWidth = width, maxWidth = width, minHeight = height, maxHeight = height ) val placeable = measurables.first().measure(imageConstraints) layout(width, height) { placeable.placeRelative(0, 0) } } }
Geriye doğru: Bestede okuma, çizimde yazma
- Ne olur?: Durum, Composable gövdesinde (Beste aşaması) okunur ancak
Modifier.drawWithContent,Modifier.drawBehindveyaCanvasiçinde değiştirilir (Çizim aşaması). - Sonuç: Çizim aşaması durumu değiştirir → Kompozisyon geçersiz kılınır → Sonsuz döngü.
// ❌ BAD: Read in Composition, Written in Draw () @Composable fun BadBackwardsWriteDraw() { var componentHeight by remember { mutableStateOf(0.dp) } // State read during COMPOSITION: Text( text = "Height is: $componentHeight", modifier = Modifier.drawBehind { // State write during the DRAW phase! // Invalidates Composition -> triggers recomposition loop! componentHeight = size.height.dp } ) }
Geriye doğru: Kompozisyon okuma, kompozisyon yazma (aynı aşama)
- Ne olur?
countbir composable işlevde okunur ve okunduktan sonra doğrudan başka bir composable işlevin içerik yuvasındacountdeğiştirilir. - Sonuç: Anlık görüntü sistemi, okuma ve sonraki yazma işlemlerini aynı oluşturma geçişinde kaydeder ve mevcut kapsamı anında geçersiz kılar.
// ❌ BAD: Direct write in Composable body after read @Composable fun BadCounter() { var count by remember { mutableIntStateOf(0) } Text("Count: $count") // State read in Composition Button(onClick = {}) { count++ // State write in Composition (Backwards write!) } } // Acceptable - but error-prone as someone may add a read before the write : Direct write in Composable body before read @Composable fun OkCounter() { var count by remember { mutableIntStateOf(0) } Button(onClick = {}) { count++ // State write in Composition } Text("Count: $count") // State read in Composition }
Geriye doğru yazma işlemlerini önlemeye yönelik temel kurallar
- Bu durum, ilk karede doğruluk sorununa neden olduğundan
onGloballyPositioned,onSizeChangedveyaLayoutModifierdurumunda yazmayın.- Düzen koordinatları veya boyutları yalnızca özel çizim için gerekliyse bunları doğrudan Çizim veya Düzen aşamasında okuyun (örneğin,
Modifier.drawWithCacheveyaModifier.layoutkullanarak). - Pencere düzeyinde boyutlandırma (
WindowWidthSizeClass) için: Boyutlandırma gözlemini pencere düzeyine taşıyın. Yerel ölçüm gerçekleşmeden önce pencere boyutu sınıflarına göre kompozisyon dalları. - Besteyi Tek Tip Tutun: 2. aşamada ölçümü ve yerleşimi yeniden oluşturmadan veya beste için kullanılan durumu değiştirmeden ayarlayan tek bir özel düzen ya da
FlowRowveyaLazyVerticalGridgibi bileşenler kullanın. - Alt kompozisyon kullanın: Alt composable'lar yerel genişliğe veya yüksekliğe göre dallanması gerektiğinde
BoxWithConstraintsveyaSubcomposeLayoutkullanın. Dikkatli olun: Alt bileşen oluşturma, performans maliyetine neden olur ve genellikle önlenebilir. - Son çare olarak: İkinci bir çerçeve yeniden oluşturma işlemini tetiklemek için boyutu
onSizeChangediçinde saklayarak ilk çerçevenin yanlış olmasına izin verin. Bu durum, düzenin görünür şekilde atlamasına, duraklamasına ve sonsuz döngü riskine neden olur.
- Düzen koordinatları veya boyutları yalnızca özel çizim için gerekliyse bunları doğrudan Çizim veya Düzen aşamasında okuyun (örneğin,
- Durumu, kompozisyonda ilk kez okunduktan sonra değiştirmeyin:
MutableStatenesnelerineSideEffectdışında oluşturma sırasında güvenli bir şekilde yazabilirsiniz ancak daha önce oluşturma sırasında okumuş olabileceğiniz bir duruma yazmadığınızdan emin olmak için özel dikkat gösterin. Bu durumda, kompozisyonda durum yazma ihtiyacı ortaya çıktığındarememberUpdatedStatekullanılması önerilir. Kompozisyon sırasında başka bir şekilde duruma yazmak genellikle eksik bir efektin veya uygunsuz şekilde tasarlanmış bir durumun ya da composable'ın işaretidir. Kompozisyonun iyimser olduğunu ve her zaman bir durumun en son değeriyle yürütüldüğünü unutmayın. Bu nedenle, yeniden oluşturma işlemlerinde tüm durum değişikliklerini görmeyebilirsiniz. Kullanıcı arayüzü güncellemeleri, tek seferlik etkinlikleri işleme yöntemi olarak kullanılmamalıdır. Bu nedenle, yeniden oluşturma sonucunda bir durumun değerinin güncellenmesi nadir görülür.- Bileşimin dışında gözlemlenen durumları değiştirmekten kaçının (örneğin,
ViewModelalanları veyaisVisibleişaretleri). Durum yazma işlemlerinin, kompozisyonu etkileyen etkinlik lambdalarında (onClick), coroutine'lerde (LaunchedEffect) veya yan etkilerde (SideEffect) yer alması gerekir.rememberUpdatedState, yalnızca@Composablegövdesinde kullanılan durumu değiştirmek üzere tasarlandığı için bir istisnadır.
- Durum okumalarını mümkün olan en son aşamaya erteleyin:
- Çizim (
Modifier.graphicsLayer { alpha = ... }) veya Düzen (Modifier.offset { IntOffset(...) }) modundaki okuma durumları, değişikliklerin yalnızca 2. veya 3. aşamayı geçersiz kılmasını sağlar ve 1. aşama (Bileşim) tamamen atlanır.
- Çizim (