Compose, bir kareyi kesin olarak sıralanmış, ileriye doğru akan üç aşamada yürütür:
- Oluşturma: Kullanıcı arayüzü ağacını oluşturmak ve güncellemek için
@Composableişlevlerini çalıştırır. - Düzen: Alt öğeleri ölçer ve ardından yerleştirir.
- Çizim: Pikselleri ekrana oluşturmak için tuval çizim komutları yayınlar.
Herhangi bir aşamada bir Compose State okunduğunda Compose, bu durum ile ilgili aşama arasında otomatik olarak bir bağımlılık kaydeder.
Bir yazıyı "geriye doğru" yapan nedir?
Geriye doğru yazma, bir durumun okunduğu aşamadan daha sonraki bir aşamada (veya alt akış kapsamda, yani aynı oluşturma geçişinde daha sonra yürütülen bir composable kapsamda) değiştirilmesi durumunda gerçekleşir. Bu durumda Compose, daha önceki bir aşamayı veya composable'ı tekrar çalıştırılmak üzere planlayarak yeniden oluşturmaya zorlanır. Geriye doğru yazma, optimize edilmemiş bir yeniden oluşturma döngüsüdür.
Geriye dönük yazma işlemlerinin sonuçları
Geriye doğru yazma işlemleri her zaman kötü bir şey değildir ve her zaman kilitlenmeleri tetiklemez ancak verimsizdir ve uygulama performansına çeşitli şekillerde zarar verebilir:
- Ek kare oluşturma ve atlanan kareler: Geriye doğru yazma, Compose'u ardışık karelerde gereksiz oluşturma geçişleri yapmaya zorlar. Bu da CPU ve GPU kaynaklarını boşa harcar ve takılmaya neden olabilir.
- İlk kare doğruluğu sorunları: Bileşeniniz, nihai boyutlarını veya durumunu çözmek için geriye doğru yazma gerektiriyorsa ilk kare geçersiz, varsayılan veya belirsiz verilerle (ör. sıfır boyut veya yanlış ofset) oluşturulur. Bu durum, ikinci kare oluşturulurken görsel patlamaya 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 yazarsa ekranın hiçbir zaman sabitlenmeden her kareyi sürekli olarak yeniden oluşturduğ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 boyunca ileri doğru ilerlemelidir:
| Okuma aşaması | Bağlam yazma | Kabul edilebilir mi? | Neden? |
|---|---|---|---|
Düzen (Modifier.offset { }) |
Oluşturma | Evet | Oluşturma, durumu günceller → Düzen, yeniden oluşturmadan aynı karede daha sonra okur. |
Berabere (graphicsLayer { }, drawBehind { }) |
Oluşturma | Evet | Oluşturma, durumu günceller → Çizim, son aşamada durumu okur. Oluşturma ve düzen tamamen atlanır. |
| Çizim | Düzen | Evet | Düzen, durumu günceller ve çizim bunu okur. Bu geçerli bir akıştır. |
| Oluşturma | Durum değişikliğini tetikleyen etkinlik geri çağırma (onClick, onValueChange). Not: Düzen geri çağırmaları etkinlik olarak sayılmaz. |
Evet | Etkinlik, oluşturmayı yönlendirmek için kullanılan durumu değiştirir. Etkinlik, kare dışında (Oluşturma, 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 katmanlamasına işaret edebilir. Mümkün olduğunca bu tür URL'lerden kaçınılmalıdır. |
| Yerleşim (Düzende) | Ölçüm (Düzende) | Evet | Layout'ta durumu güncelleyip yerleşimde bu durumu okumak kabul edilebilir. |
| Ölçüm (Düzende) | Yerleşim (Düzende) | Hayır - geriye doğru yazma | Yerleşimde duruma yazma işlemi, daha sonra okuma durumunda olduğundan yeniden ölçüm döngüsüne neden olur. |
| Kompozisyon | Düzen (onSizeChanged, LayoutModifier) |
Hayır - Geriye doğru yazma | Düzen, oluşturmayı geçersiz kılar → Yeniden oluşturma döngüsü. |
| Kompozisyon | Berabere (drawWithContent, Canvas) |
Hayır - Geriye doğru yazma | Çizim, oluşturma → yeniden oluşturma döngüsünü geçersiz kılar. |
Aşama kombinasyonları: Geriye dönük ve ileriye dönük
Aşağıda, Compose'da geriye doğru yazma örnekleri ve bunları nasıl çözebileceğiniz açıklanmıştır.
Geriye doğru: Oluşturma'da okuma, düzende yazma
- Ne olur?: Oluşturma, hangi kullanıcı arayüzünün yayınlanacağını belirlemek için
componentHeightdeğerini okur. Daha sonra karede, Düzen aşaması görünümleri ölçer veya yerleştirir vecomponentHeightiçin yeni bir değer yazar (örneğin,onSizeChanged,onGloballyPositionedveya özelLayoutModifierkullanarak). - Sonuç: Düzen aşamasında
componentHeightdeğiştirildiğinde, yeni tamamlanan oluşturma aşaması geçersiz kılınır.onSizeChangedöğesinin, düzen ölçümü geçişi tamamlandıktan sonra boyutunu bildirdiğini unutmayın. Güncellenen durum değeri bir sonraki geçişte sabitlenirse yeniden oluşturma işlemi bir ek kareden sonra durabilir. Ancak yeni değer boyutu değiştirmeye devam ederse sonsuz kare döngüsü oluşur. AyrıcaonGloballyPositioned, hem düzenden hem de yerleşimden sonra çalışır. Bu nedenle, içindeki durum yazma işlemleri, ardışık kareler arasında sürekli yeniden oluşturma ve yeniden düzenleme döngülerine karşı daha hassas 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) } } }
Ters: Oluşturma'da okuma, Çizim'de yazma
- Ne olur: Durum, Composable gövdesinde (oluşturma aşaması) okunur ancak
Modifier.drawWithContent,Modifier.drawBehindveyaCanvas(çizim aşaması) içinde değiştirilir. - Sonuç: Çizim aşaması durumu değiştirir → Oluşturma 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: Oluşturma sırasında okuma, oluşturma sırasında yazma (aynı aşama)
- Ne olur?:
count, composable işlevde okunur ve okunduktan sonra doğrudan başka bir composable içerik alanında değiştirilircount. - Sonuç: Anlık görüntü sistemi, aynı oluşturma geçişinde okuma ve sonraki yazma işlemlerini kaydederek mevcut kapsamı hemen 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 dönük yazma işlemlerini önlemeye yönelik temel kurallar
onGloballyPositioned,onSizeChanged,onPlaced,onLayoutRectChangedveyaLayoutModifieriçinde, oluşturma sırasında okunan bir duruma yazmayın. Bu, ilk kare doğruluğu sorununa 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 (ör.
Modifier.drawWithCacheveyaModifier.layoutkullanarak). - Pencere düzeyinde boyutlandırma (
WindowWidthSizeClass) için: Boyut gözlemini pencere düzeyine yükseltin. Oluşturma, yerel ölçüm gerçekleşmeden önce pencere boyutu sınıflarına göre dallara ayrılır. - Kompozisyonu Tek Tip Tutun: Tek bir özel düzen veya
FlowRowya daLazyVerticalGridgibi bileşenler kullanın. Bunlar, kompozisyonda kullanılan durumu yeniden oluşturmadan veya değiştirmeden 2. aşamada ölçümü ve yerleşimi ayarlar. - Alt oluşturma kullanın: Alt composable'ların yerel genişlik veya yüksekliğe göre dallanması gerektiğinde
BoxWithConstraintsveyaSubcomposeLayoutkullanın. Dikkatli kullanın: Alt bileşim, performans maliyetine neden olur ve genellikle önlenebilir. - Son çare olarak: İlk karenin yanlış olmasına izin verin ve boyutu
onSizeChangediçinde depolayarak ikinci bir kare yeniden oluşturma işlemini tetikleyin. Bu durum, görünür düzen patlamasına, takılmalara 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 (ör.
- Oluşturma işleminde ilk kez okunduktan sonra durumu değiştirmeyin:
- Oluşturma sırasında
MutableStatenesnelerineSideEffectdışında güvenli bir şekilde yazabilseniz de daha önce oluşturma sırasında okumuş olabileceğiniz bir duruma yazmadığınızdan emin olmak için özel dikkat gösterin. Oluşturma işleminde durum yazma ihtiyacı ortaya çıktığındarememberUpdatedStatekullanmanız önerilir. Oluşturma sırasında bir duruma başka bir şekilde yazma, genellikle eksik bir efektin veya uygunsuz şekilde tasarlanmış bir durumun ya da composable'ın işaretidir. Oluşturma iyimserdir ve her zaman bir durumun en son değeriyle yürütülür. Bu nedenle, yeniden oluşturmalarda tüm durum değişikliklerini göremeyebilirsiniz. Kullanıcı arayüzü güncellemeleri, tek seferlik etkinlikleri işlemek için kullanılmamalıdır. Bu nedenle, bir durumun değerinin yeniden oluşturma sonucunda güncellenmesi yaygın bir durum değildir. - Oluşturma dışında gözlemlenen durumları (ör.
ViewModelalanları veyaisVisibleişaretleri) değiştirmekten kaçının. Oluşturmayı etkileyen durum yazma işlemleri, etkinlik lambda'larına (onClick), eş yordamlara (LaunchedEffect) veya yan etkilere (SideEffect) aittir.rememberUpdatedState, yalnızca@Composablegövdesinde kullanılan durumu değiştirmek için tasarlandığından bir istisnadır.
- Oluşturma sırasında
- Durum okumalarını mümkün olan en son aşamaya erteleme:
- Durumları Draw (
Modifier.graphicsLayer { alpha = ... }) veya Layout (Modifier.offset { IntOffset(...) }) aşamasında okumak, değişikliklerin yalnızca 2. veya 3. aşamayı geçersiz kılmasını ve 1. aşamayı (Oluşturma) tamamen atlamasını sağlar.
- Durumları Draw (