Yaygın Oluşturma hatalarıyla karşılaşabilirsiniz. Bu hatalar, yeterince iyi çalıştığı görünen ancak kullanıcı arayüzü performansınızı olumsuz etkileyebilecek kodlar oluşturmanıza neden olabilir. Compose'da uygulamanızı optimize etmek için en iyi uygulamaları izleyin.
Pahalı hesaplamaları en aza indirmek için remember kullanma
Composable işlevler, animasyonun her karesi için olduğu kadar çok sık çalışabilir. Bu nedenle, composable'ınızın gövdesinde mümkün olduğunca az hesaplama yapmalısınız.
Önemli bir teknik, remember ile hesaplama sonuçlarını depolamaktır. Bu sayede hesaplama bir kez çalışır ve sonuçları gerektiğinde getirebilirsiniz.
Örneğin, aşağıda adların sıralanmış bir listesini görüntüleyen ancak sıralamayı çok maliyetli bir şekilde yapan bir kod verilmiştir:
@Composable fun ContactList( contacts: List<Contact>, comparator: Comparator<Contact>, modifier: Modifier = Modifier ) { LazyColumn(modifier) { // DON’T DO THIS items(contacts.sortedWith(comparator)) { contact -> // ... } } }
ContactsList her yeniden oluşturulduğunda, liste değişmemiş olsa bile tüm kişi listesi baştan sona yeniden sıralanır. Kullanıcı listede kaydırma yaptığında, yeni bir satır her göründüğünde Composable yeniden oluşturulur.
Bu sorunu çözmek için listeyi LazyColumn dışında sıralayın ve sıralanmış listeyi remember ile birlikte saklayın:
@Composable fun ContactList( contacts: List<Contact>, comparator: Comparator<Contact>, modifier: Modifier = Modifier ) { val sortedContacts = remember(contacts, comparator) { contacts.sortedWith(comparator) } LazyColumn(modifier) { items(sortedContacts) { // ... } } }
Artık liste, ContactList ilk oluşturulduğunda bir kez sıralanıyor. Kişiler veya karşılaştırıcı değişirse sıralanmış liste yeniden oluşturulur. Aksi takdirde, composable, sıralanmış ve önbelleğe alınmış listeyi kullanmaya devam edebilir.
Lazy layout tuşlarını kullanma
Lazy düzenler, öğeleri verimli bir şekilde yeniden kullanır ve yalnızca gerektiğinde yeniden oluşturur veya yeniden düzenler. Ancak, yeniden oluşturma için tembel düzenleri optimize etmeye yardımcı olabilirsiniz.
Bir kullanıcı işlemi, listedeki bir öğenin taşınmasına neden olduğunu varsayalım. Örneğin, değiştirilme zamanına göre sıralanmış bir not listesi gösterdiğinizi ve en son değiştirilen notun en üstte olduğunu varsayalım.
@Composable fun NotesList(notes: List<Note>) { LazyColumn { items( items = notes ) { note -> NoteRow(note) } } }
Ancak bu kodla ilgili bir sorun var. En alttaki notun değiştirildiğini varsayalım. Bu not artık en son değiştirilen not olduğu için listenin en üstüne taşınır ve diğer tüm notlar bir sıra aşağı iner.
Yazma, yardımınız olmadan değişmeyen öğelerin listede yalnızca taşındığını fark etmez. Bunun yerine, Oluşturma özelliği eski "öğe 2"nin silindiğini ve 3. öğe, 4. öğe ve diğer tüm öğeler için yeni bir öğe oluşturulduğunu düşünüyor. Sonuç olarak, yalnızca bir öğe değişmiş olsa bile Oluşturma, listedeki her öğeyi yeniden oluşturur.
Buradaki çözüm, öğe anahtarlarısağlamaktır. Her öğe için sabit bir anahtar sağlamak, Compose'un gereksiz yeniden oluşturmaları önlemesini sağlar. Bu durumda Compose, 3. sıradaki öğenin 2. sıradaki öğeyle aynı olduğunu belirleyebilir. Bu öğeyle ilgili verilerin hiçbiri değişmediğinden Compose'un öğeyi yeniden oluşturması gerekmez.
@Composable fun NotesList(notes: List<Note>) { LazyColumn { items( items = notes, key = { note -> // Return a stable, unique key for the note note.id } ) { note -> NoteRow(note) } } }
Yeniden oluşturma işlemlerini sınırlamak için derivedStateOf kullanma
Kompozisyonlarınızda durumu kullanmanın bir riski, durumun hızlı bir şekilde değişmesi durumunda kullanıcı arayüzünüzün gerekenden daha fazla yeniden oluşturulabilmesidir. Örneğin, kaydırılabilir bir liste gösterdiğinizi varsayalım. Listedeki ilk görünür öğenin hangisi olduğunu görmek için listenin durumunu incelersiniz:
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } val showButton = listState.firstVisibleItemIndex > 0 AnimatedVisibility(visible = showButton) { ScrollToTopButton() }
Buradaki sorun, kullanıcı listede kaydırdığında listState simgesinin, kullanıcı parmağını sürükledikçe sürekli değişmesidir. Bu nedenle liste sürekli olarak yeniden oluşturulur. Ancak, aslında bu işlemi o kadar sık yapmanız gerekmez. Alt kısımda yeni bir öğe görünene kadar yeniden oluşturmanız gerekmez. Bu nedenle, çok fazla ek hesaplama yapılması kullanıcı arayüzünüzün performansını olumsuz etkiler.
Çözüm, türetilmiş durumu kullanmaktır. Türetilmiş durum, Compose'a hangi durum değişikliklerinin yeniden oluşturmayı tetiklemesi gerektiğini söylemenize olanak tanır. Bu durumda, ilk görünür öğenin ne zaman değiştiğini önemsemenizi belirtin. Bu durum değeri değiştiğinde kullanıcı arayüzünün yeniden oluşturulması gerekir. Ancak kullanıcı, yeni bir öğeyi en üste getirecek kadar kaydırma yapmadıysa yeniden oluşturulması gerekmez.
val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } AnimatedVisibility(visible = showButton) { ScrollToTopButton() }
Okuma işlemlerini mümkün olduğunca erteleyin.
Bir performans sorunu tespit edildiğinde durum okumalarını ertelemek yardımcı olabilir. Durum okumalarını ertelemek, yeniden oluşturma sırasında Compose'un mümkün olan en az kodu yeniden çalıştırmasını sağlar. Örneğin, kullanıcı arayüzünüzde composable ağacında yukarı taşınmış bir durum varsa ve durumu alt composable'da okuyorsanız okunan durumu bir lambda işlevine sarmalayabilirsiniz. Bu işlem, okuma işleminin yalnızca gerçekten gerektiğinde yapılmasını sağlar. Referans olarak, Jetsnack örnek uygulamasındaki uygulamaya bakın. Jetsnack, ayrıntı ekranında daralan araç çubuğu benzeri bir efekt uygular. Bu tekniğin neden işe yaradığını anlamak için Jetpack Compose: Debugging Recomposition başlıklı blog yayınını inceleyin.
Bu efekti elde etmek için Title composable'ın, Modifier kullanarak kendini kaydırması için kaydırma uzaklığına ihtiyacı vardır. Optimizasyon yapılmadan önceki Jetsnack kodunun basitleştirilmiş bir sürümünü aşağıda bulabilirsiniz:
@Composable fun SnackDetail() { // ... Box(Modifier.fillMaxSize()) { // Recomposition Scope Start val scroll = rememberScrollState(0) // ... Title(snack, scroll.value) // ... } // Recomposition Scope End } @Composable private fun Title(snack: Snack, scroll: Int) { // ... val offset = with(LocalDensity.current) { scroll.toDp() } Column( modifier = Modifier .offset(y = offset) ) { // ... } }
Kaydırma durumu değiştiğinde Compose, en yakın üst yeniden oluşturma kapsamını geçersiz kılar. Bu durumda, en yakın kapsam SnackDetail
composable'dır. Box işlevinin satır içi bir işlev olduğunu ve bu nedenle yeniden oluşturma kapsamı olmadığını unutmayın. Bu nedenle Compose, SnackDetail ve SnackDetail içindeki tüm composable'ları yeniden oluşturur. Kodunuzu yalnızca gerçekten kullandığınız durumu okuyacak şekilde değiştirirseniz yeniden oluşturulması gereken öğe sayısını azaltabilirsiniz.
@Composable fun SnackDetail() { // ... Box(Modifier.fillMaxSize()) { // Recomposition Scope Start val scroll = rememberScrollState(0) // ... Title(snack) { scroll.value } // ... } // Recomposition Scope End } @Composable private fun Title(snack: Snack, scrollProvider: () -> Int) { // ... val offset = with(LocalDensity.current) { scrollProvider().toDp() } Column( modifier = Modifier .offset(y = offset) ) { // ... } }
Kaydırma parametresi artık bir lambda. Bu durumda Title, yükseltilmiş duruma referans vermeye devam edebilir ancak değer yalnızca gerçekten ihtiyaç duyulduğu Title içinde okunur. Sonuç olarak, kaydırma değeri değiştiğinde en yakın yeniden oluşturma kapsamı artık Title composable'dır. Compose'un artık Box'ın tamamını yeniden oluşturması gerekmez.
Bu iyi bir gelişme ancak daha iyisini yapabilirsiniz. Bir Composable'ı yeniden düzenlemek veya yeniden çizmek için yeniden oluşturmaya neden oluyorsanız şüphelenmelisiniz. Bu durumda, tek yaptığınız Title composable'ın ofsetini değiştirmektir. Bu işlem, düzen aşamasında yapılabilir.
@Composable private fun Title(snack: Snack, scrollProvider: () -> Int) { // ... Column( modifier = Modifier .offset { IntOffset(x = 0, y = scrollProvider()) } ) { // ... } }
Daha önce, kodda Modifier.offset(x: Dp, y: Dp) kullanılıyordu. Bu işlev, parametre olarak ofseti alıyordu. Değiştiricinin lambda sürümüne geçerek işlevin, düzen aşamasında kaydırma durumunu okumasını sağlayabilirsiniz. Bu nedenle, kaydırma durumu değiştiğinde Compose, kompozisyon aşamasını tamamen atlayıp doğrudan düzen aşamasına geçebilir. Sık değişen durum değişkenlerini değiştiricilere aktarırken mümkün olduğunda değiştiricilerin lambda sürümlerini kullanmanız gerekir.
Bu yaklaşıma dair başka bir örnek aşağıda verilmiştir. Bu kod henüz optimize edilmedi:
// Here, assume animateColorBetween() is a function that swaps between // two colors val color by animateColorBetween(Color.Cyan, Color.Magenta) Box( Modifier .fillMaxSize() .background(color) )
Burada kutunun arka plan rengi iki renk arasında hızla değişiyor. Bu nedenle, bu durum çok sık değişir. Daha sonra composable, bu durumu arka plan değiştiricisinde okur. Sonuç olarak, renk her karede değiştiğinden kutunun her karede yeniden oluşturulması gerekir.
Bunu iyileştirmek için lambda tabanlı bir değiştirici kullanın. Bu örnekte drawBehind değiştiricisi kullanılabilir.
Bu, renk durumunun yalnızca çizim aşamasında okunduğu anlamına gelir. Sonuç olarak, renk değiştiğinde Compose, kompozisyon ve düzen aşamalarını tamamen atlayıp doğrudan çizim aşamasına geçebilir.
val color by animateColorBetween(Color.Cyan, Color.Magenta) Box( Modifier .fillMaxSize() .drawBehind { drawRect(color) } )
Geriye doğru yazmaktan kaçının
Oluşturma özelliğinin temel varsayımı, okunmuş bir durumu belirtmek için asla yazmayacağınızdır. Bunu yaptığınızda geriye doğru yazma işlemi gerçekleşir ve bu durum, her karede sonsuza kadar yeniden oluşturmaya neden olabilir.
Aşağıdaki composable'da bu tür bir hatanın örneği gösterilmektedir.
@Composable fun BadComposable() { var count by remember { mutableIntStateOf(0) } // Causes recomposition on click Button(onClick = { count++ }, Modifier.wrapContentSize()) { Text("Recompose") } Text("$count") count++ // Backwards write, writing to state after it has been read</b> }
Bu kod, önceki satırda okuduktan sonra composable'ın sonundaki sayıyı günceller. Bu kodu çalıştırırsanız düğmeyi tıkladıktan sonra (bu işlem yeniden oluşturmaya neden olur) sayacın sonsuz bir döngüde hızla arttığını görürsünüz. Bunun nedeni, Compose'un bu Composable'ı yeniden oluşturması, güncel olmayan bir durum okuma işlemi görmesi ve bu nedenle başka bir yeniden oluşturma planlamasıdır.
Composition'da hiçbir zaman duruma yazmayarak geriye doğru yazma işlemlerini tamamen önleyebilirsiniz. Mümkünse her zaman bir etkinliğe yanıt olarak ve önceki onClick örneğindeki gibi bir lambda'da durum belirtmek için yazın.
Geriye doğru yazma, yeniden oluşturma döngüleri ve aşama koordinasyonu hakkında ayrıntılı bilgi için Compose'da geriye doğru yazma başlıklı makaleyi inceleyin.
Ek kaynaklar
- Uygulama performansı kılavuzu: Android'de performansı artırmaya yönelik en iyi uygulamaları, kitaplıkları ve araçları keşfedin.
- Performansı İncele: Uygulama performansını inceleyin.
- Karşılaştırma: Uygulama performansını karşılaştırın.
- Uygulama başlatma: Uygulama başlatmayı optimize edin.
- Temel profiller: Temel profilleri anlayın.
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir.
- Durum ve Jetpack Compose
- Grafik Değiştiriciler
- Compose'da düşünme