Araçlar ve Performans

Compose'da kendinizi aşın

Hoparlörler

Hakkında

Konuşma hakkında

Jetpack Compose, yüksek kaliteli kullanıcı arayüzleri tasarlamayı kolaylaştırır ancak kullanıcı arayüzü performansını etkileyen sorunları tespit etmek zor olabilir. Bu tür sorunlar, kullanıcılarınızın fark edeceği sorunlara (ör. başlatma gecikmesi ve kaydırma sırasında takılma veya kesik kesik animasyonlar olarak görünen kare düşüşleri) yol açabilir.

Bu konuşmada, Perfetto ve (yeniden) oluşturma izlemeye odaklanarak kullanıcı arayüzünüzün performansını etkileyen unsurları ortaya çıkaracak araçları tanıtacağız. Ardından, farkında olmadan maliyetinizi artıran belirli anti-kalıpları, bunları kodunuzda nasıl bulabileceğinizi ve bunların yerine hangi olumlu kalıpları kullanabileceğinizi inceleyeceğiz. Bu konuşmanın sonunda, Compose'da performans odaklı düşünmeyi ve uygulamanızdaki performans optimizasyonlarını nasıl profilleyeceğinizi, analiz edeceğinizi ve doğrulayacağınızı öğreneceksiniz.

Özet

  • Perfetto izleri, cihazdaki etkinliği kaydeder ve ana iş parçacığı boyunca oluşturma ve düzen gibi Oluşturma dilimlerini gösterir. Kare zaman çizelgelerini incelemek ve aşama sürelerini izlemek için bu araçları kullanın.
  • Eş yordamları başlatma ve iptal etme de dahil olmak üzere, composable'ların içindeki her işlemin bir maliyeti vardır. Yan etki API'lerini denetleyin ve oluşturma ek yükünü azaltmak için efektleri aynı anahtarlarla birleştirmeyi düşünün.
  • Oluşturma izleme, composable işlevlere ek açıklama ekler. Yeniden oluşturma izleme (1.13+) ise yeniden oluşturmaya neden olan durum okuma ve yazma işlemlerini izler. Doğrudan Perfetto izlerinde beklenmedik işlemleri teşhis etmeye yardımcı olur.
  • ComputedState (Compose 1.13+), istenmeyen geçersiz kılma işlemlerini önler ve yazma sonrası okuma geçersiz kılma işlemleri sırasında genellikle DerivedState'ten daha hızlıdır. DerivedState, yüksek maliyetli hesaplamalar veya ayırmalar önbelleğe alma işleminden yararlandığında kullanışlı olmaya devam eder.
  • Oluşturulabilir öğeleri atlamak, bakım maliyetlerine ve parametre karşılaştırma ek yüküne neden olur. Kararlılığı zorlamak yerine güçlü atlama ve nesne kimliği eşitliğine güvenin veya geçersiz kılma işlemlerinden tamamen kaçının.

Beceriler

Kaynaklar

Gemini Notebook

Konular

  • jetpack-compose
  • android-performance
  • perfetto
  • karşılaştırma
  • eş yordamlar
  • composition-tracing
  • android-cli

Transkript

Giriş

  • Merhaba.

  • Herkese merhaba.

  • Umarım güzel bir tatil geçirmişsinizdir.

  • Umarım kahvenizi içmiş, cildinizi nemlendirmiş ve diğer her şeyi yapmışsınızdır.

  • Bugün konuyu biraz daha ayrıntılı inceleyeceğiz.

  • Evet, başlayalım.

  • Bugün biraz daha ayrıntılı inceleme yapacağız.

  • Adım Andrei Shikov. Bugün sahneyi Andrew Bailey ile paylaşıyorum.

  • İkimiz de Compose derleyici çalışma zamanı ekibinde çalışıyoruz ancak performans da dahil olmak üzere başka birçok konuyla da ilgileniyoruz.

  • Bugünkü konuşmanın konusu, performansla ilgili sorularınızı yanıtlamak. Jetpack Compose, Android geliştiricisi için varsayılan kullanıcı arayüzü araç seti olarak kabul edildi.

  • Şimdi de birçok kişi, Compose kullanıcı arayüzünün mümkün olduğunca yüksek performanslı olmasını nasıl sağlayabileceğimizi soruyor.

  • Bugün, daha önce de söylediğim gibi, ayrıntılı bir inceleme yapacağız.

  • Ayrıca, Compose'da kendinizi aşmanızı sağlayacak bazı araçları da tanıtacağız.

  • Öncelikle performansın bizim için neden önemli olduğundan bahsetmek istiyorum.

  • Kalite ve kaliteli uygulamalar oluşturma dendiğinde genellikle animasyonlar ve zengin özellikler akla gelir. Performans bazen gözden kaçar ancak kullanıcıların algısı gerçekten uygulamanızın performansına bağlıdır.

  • Bu nedenle, animasyonlar takılırsa algı hemen düşer.

  • Bugün bahsedeceğimiz tüm metrikler ve sayılar, kullanıcıların uygulama, animasyonlar ve ekranda gerçekleşen her şey hakkında ne hissettiğini temsil etmek için seçtiğimiz dolaylı metriklerdir.

  • Yani temel olarak uygulamanızı optimize etmek, daha iyi bir deneyim için optimizasyon yapmak demektir.

  • Bugün birçok konuyu ayrıntılı olarak ele alacağız. Bu nedenle, bu zihniyeti benimsemenizi istiyorum.

  • Uygulamanızın performansını incelemeye başlarken üç şeyi hızlıca yapmanızı istiyorum.

  • Öncelikle, performansı ölçüyorsanız bunu sürüm modunda yaparsınız. Bu nedenle, hata ayıklanabilir çok fazla bilgiye sahip olmayız ve tüm bunlar uygulamamızı yavaşlatır.

  • İkinci olarak, R8'i veya diğer kod sadeleştiricileri etkinleştirirsiniz.

  • Yalnızca bu yıl, eş yordam performansını iki kat artıran bir optimizasyon sunduk ve başka birçok şey de yapıyoruz.

  • Compose'u optimize ederken R8'den çok yararlanıyoruz.

  • Son olarak, özellikle başlangıç metrikleri için temel profil kullandığınızdan emin olun.

  • Bu nedenle, başlangıçta yüklediğiniz sınıflar, uygulama başlatıldığında önceden derlenir ve önceden yüklenir.

  • Gördüğünüz gibi, uygulamanın normal hata ayıklanabilir derlemesine kıyasla 6 kat fark var.

  • Tüm bunları ölçtüğünüzde, kodunuzda elde ettiğiniz diğer kazanımlarla birleşirler.

  • Tüm bunları yaptınız ancak uygulama hâlâ biraz gecikiyor veya performansla ilgili başka sorunlar var. Bu noktada performans döngüsüne girmeniz gerekir.

  • Belirli senaryolardaki bazı metrikleri incelemeye başlamak istiyorsunuz.

  • Örneğin, uygulamanızın yüklenmesi çok uzun sürüyor diyelim. Uygulamanızı sıfırlayıp normal başlatıcıdan başlatmak ve uygulamanın ilk veri ekranına ulaşmasının ne kadar sürdüğünü kontrol etmek isteyebilirsiniz.

  • Uygulama biraz takılıyorsa kaydırarak bu kaydırma işleminin tekrarlanabilir ve kararlı olduğundan emin olursunuz.

  • Bu slayttaki kararlı ortam gerçekten önemlidir. Çünkü daha sonra optimizasyon yaparken metriklerinizde iyileşme gördüğünüzden emin olmak ve bu optimizasyonların gerçekten amaçlandığı gibi çalıştığını doğrulamak istersiniz.

  • Karşılaştırmalar bu konuda çok iyidir çünkü karşılaştırmalar zaten çok fazla veri yakalar, cihazınızda çok fazla gürültü olmadığından emin olmak için bunu birçok kez tekrarlar ve ayrıca size yardımcı olacak birçok cihaz kurulumu yapar.

  • Karşılaştırmalar da varsayılan olarak tekrarlanabilir senaryolardır ve burada yapmaya çalıştığımız şey de budur.

  • Ancak isterseniz bunu bir dizi komut dosyasıyla da yapabilirsiniz.

  • Son olarak, profil oluşturur, uygulamanızda bir şeyleri iyileştirir, verilere bakar ve ardından tekrar edersiniz. Belki de uygulamanızın zorlandığı farklı senaryo metrikleriyle tekrar edersiniz.

Perfetto izleriyle profil oluşturma

  • Verileri incelerken bugün tanıtacağımız ve gerçekten aşina olmak isteyeceğiniz ilk araç Perfetto izleridir.

  • Perfetto izleri, izi kaydettiğiniz sırada cihazda gerçekleşen her şeyi kaydeder. Örneğin, makro karşılaştırma testi çalıştırılırken veya kaydettiğiniz bir komut dosyası sırasında.

  • Ayrıca Android performansı üzerinde çalışan her mühendis için büyük bir yardımcıdır.

  • Benim için, bugünlerde günümün çoğunu Perfetto izlerine bakarak geçiriyorum.

  • Şuna benzer bir şey görürsünüz.

  • Bu çok fazla veri demek.

  • Dediğim gibi, cihazda gerçekleşen her şeyi yakalarlar.

  • Bu nedenle, ilk başta uygulamanızın tam olarak nerede olduğunu bulmakta zorlanabilirsiniz. Bu kullanışlı arama çubuğunu kullanmanızı öneririm.

  • Compose'u, uygulama paketinizi ve benzer öğeleri arayın.

  • Bildiğiniz şeylere odaklanın.

  • Uygulama sürecinizi belirleyip kendinizi geliştirdikçe kare süresi çizgilerini ve uygulamanızdaki birçok işlemin ne kadar sürdüğünü göreceksiniz.

  • Bunlardan birini ayırırsak şöyle bir şey görürsünüz.

  • Birincisi, ana iş parçacığı çalışmasıdır.

  • Compose, işlemlerinin çoğunu burada gerçekleştirir.

  • Ardından RenderThread gelir.

  • Tuval komutunu işlediğimiz ve GPU'ya kodladığımız yer burasıdır.

  • Ardından GPU işini yapar ve sistem bunu birleştirir.

  • Bugün yalnızca ana ileti dizisine bakacağız.

  • Çünkü Compose, işini burada yapar.

  • Compose'dan bahsettiğimiz için yalnızca buna odaklanmak mantıklıdır.

  • Ana iş parçacığında bu kareyi izole ettiğimizde artık Oluşturma dilimlerini görmeye başlarız.

  • Sizin için birkaç tane seçtim.

  • Oluşturma, ölçme, düzen ve çizim gibi.

  • Daha önce performansla ilgili araştırma yaptıysanız Compose aşamalarını duymuşsunuzdur.

  • Perfetto izinde her birinin ayrı dilimleri vardır. Bu sayede, her aşamanın ne kadar sürdüğünü ve her karede hangi aşamaların çalıştığını kolayca öğrenebilirsiniz.

  • Bu çalışmaların bir kısmını optimize etme hakkında uzun uzun konuştuk. Doğru aşamada doğru çalışmayı yaptığınızdan emin olmak istersiniz.

  • Örneğin, kutunuzun rengini güncelleyen bir animasyonunuz varsa yalnızca çizim aşamasını geçersiz kılmak istersiniz, oluşturma ve düzen gibi aşamaları değil.

  • Bu, performansla ilgili temel bir konu. Bugün biraz daha ayrıntılı bir şekilde ele alacağız ve özellikle kompozisyona odaklanacağız.

  • Şimdi sözü Andrew'ya bırakıyorum.

  • Teşekkürler, Andrei.

  • Andrei'nin de belirttiği gibi, performans bağlamında optimizasyonlar hakkında konuşurken çok zaman harcıyoruz. Örneğin, geriye doğru okumalardan kaçınma, durum okumalarını oluşturma yaşam döngüsünde doğru aşamaya erteleme ve optimizasyon yapma, oluşturulabilir öğelerinizin beklediğiniz gibi geçersiz kılındığından emin olma, böylece her karede çok fazla iş yapmamanız gibi konuları ele alıyoruz.

  • Bu gerçekten iyi bir başlangıç noktası ancak bu konuda zaten çok fazla materyalimiz var ve bu sohbeti biraz daha ileriye taşımak istiyoruz.

  • Bugün, bu optimizasyonları yaptıktan sonra oluşturmada kalanları ve composable'larınızın ne yaptığını ele alacağız.

  • Oluşturma kodunuzdaki her şeyin ve performans açısından uygulamanızla birlikte gönderdiğiniz her şeyin bir maliyeti vardır.

  • Uygulamanızda yayınladığınız kodların hiçbiri ücretsiz değildir. Durum okuma ve oluşturma işleminizde yan etki gibi öğeler oluşturma gibi önemsiz görünen işlemlerin bile somut maliyetleri vardır. Özellikle oluşturulabilir öğelerinizin gerçekte ne yaptığını incelemeye ve uygulamanızda daha fazla performans elde etmeye başladığınızda bu maliyetleri göz önünde bulundurmanız önemlidir.

  • Bunun en mikroskobik ölçekte nasıl göründüğünü göstermek için kodunuzun herhangi bir yerinde, özellikle de composable'larınızda yapabileceğiniz en küçük işlemlerden bazılarını burada bulabilirsiniz. Bu çözünürlükte, bu işlemlerin kabaca ne kadara mal olduğu konusunda nanosaniye ölçeğinden bahsediyoruz.

  • Performansınızı artırmak istiyorsanız iki seçeneğiniz vardır.

  • Çok sayıda düşük maliyetli işlemi veya daha az sayıda yüksek maliyetli işlemi kaldırabilirsiniz.

  • Özellikle bu grafikte, eş yordamlar bu işlemler söz konusu olduğunda daha pahalı tarafta yer alır ve nereden başlayacağımıza dair iyi bir örnek teşkil eder. Çünkü bu örneklerden birkaçını kaldırırsak uygulamamızda daha somut faydaları daha hızlı görmeye başlayabiliriz.

  • Eş yordamlarla ilgili olarak akılda tutulması gereken bir diğer nokta da, slaytta da belirttiğim gibi, eş yordam başlatmanın bir maliyeti olduğu gibi eş yordam iptal etmenin de bir maliyeti olduğudur.

  • İptal maliyeti, özellikle içerikleri silerken ve uygulamanızdaki diğer alanlara geçerken ortaya çıktığı için dikkat çekicidir.

  • Başka bir sayfaya gitmek, kare süresinden en iyi şekilde yararlanmanız ve sahip olduğunuz her şeyi en iyi şekilde kullandığınızdan emin olmanız gereken birçok uygulamada son derece maliyetli bir durumdur.

  • Bu nedenle, bu maliyetlerin her ikisini de göz önünde bulundurmak önemlidir.

  • Neye bakarsak bakalım, tüm bunlar yaklaşık 16 milyon nanosaniyelik bir ölçekte toplanır.

  • Burada azaltmamız gereken bir bütçemiz var ve eş yordamlar, başlamak için iyi bir örnek olacak.

Yan etkileri yönetme

  • Compose'da eş yordamlar en sık efekt API'leriyle birlikte görünür.

  • Burada, SideEffects ile kullanabileceğiniz, yüksek işlevsellikten düşük gecikmeye kadar çeşitli API'ler vardır.

  • Bu spektrumun en işlevsel tarafında LaunchedEffect var. Bu, eşzamansız çalışma için her şeyi içerir. Özellikle animasyonlar gibi, eş yordam kapsamına ihtiyaç duyduğunuz her şey buna dahildir.

  • Ancak buna ihtiyacınız yoksa ve görevinizi daha ucuz bir API ile gerçekleştirebiliyorsanız kullanım alanınızın ihtiyaçlarına uygunsa bunu kullanmayı düşünebilirsiniz.

  • Bunun pratikte nasıl göründüğünü göstermek için tüm farklı SideEffect'leri ve ne zaman görünebileceklerini gösteren bir Medya Oynatıcı composable'ım var.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val mediaPlayer = rememberMediaPlayer()
    
        // Asynchronous work and animations
        LaunchedEffect(contentSource) { mediaPlayer.load(contentSource) }
    }
  • LaunchedEffect ile başlayabiliriz.

  • Bu örnekte, medya oynatıcıya verileri eşzamansız olarak yüklemek için kullanılıyor.

  • Bir eş yordam kapsamına ihtiyacınız varsa veya genel olarak eş yordamlara ihtiyacınız varsa ya da askıya alma çağrısı yapmak istiyorsanız LaunchedEffect kullanın.

  • Hâlâ daha pahalı tarafta olmasına ve eş yordamlarla ilişkili bir maliyet olmasına rağmen, bundan yararlandığınız ve bunu gerekçelendirdiğiniz sürece iş için doğru API'yi kullanın.

  • Ancak LaunchedEffect kullanıyor ancak eş yordamdan yararlanmıyorsanız aynı şeyi daha düşük maliyetle yapabilen diğer API'leri kullanmayı düşünebilirsiniz.

  • Bu nedenle, spektrumda aşağı doğru ilerlediğimizde DisposableEffect gibi diğer yan etki örneklerini görebiliriz.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val activity = LocalActivity.current
    
        // Setup and teardown
        DisposableEffect(Unit) {
            activity.requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE
            onDispose {
                activity.requestedOrientation = SCREEN_ORIENTATION_UNSPECIFIED
            }
        }
    }
  • DisposableEffect, efektinizle ilişkili bir kurulum ve kaldırma işleminiz olduğunda idealdir.

  • Burada, aynı medya oynatıcı örneğinde, composable mevcut olduğunda etkinliğin yönünü yatay olarak kilitleyen ve daha sonra composable kaybolduğunda bu yön kilidini kaldıran bir örnek var.

  • LaunchedEffect ve DisposableEffect'nin tek seferlik etkisini istiyorsanız ancak eş yordamlara ihtiyacınız yoksa ve dispose efektiniz yoksa Compose 1.12'de kullanıma sunduğumuz anahtarlarla yeni SideEffect API'yi de kullanabilirsiniz.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val mediaPlayer = rememberMediaPlayer()
    
        // One-shot events
        SideEffect(Unit) { mediaPlayer.play() }
    }
  • Bu, diğer iki API'ye ulaştıysanız alternatif olarak mükemmel bir seçenektir. LaunchedEffect kolaylık sağlamak için veya DisposableEffect tek seferlik bir etki için gereklilik nedeniyle, anahtar API'leri ile SideEffect'ye gitmek veya geçiş yapmak, özellikle tüm bir uygulama boyunca birden fazla kez geçiş yapılıyorsa daha düşük ek yük ve daha iyi performansla aynı şeyi başarabilir.

  • Son olarak, en ucuz API ise standart SideEffect API'dir.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val mediaPlayer = rememberMediaPlayer()
    
        // Synchronize the outside world to the state of composition
        SideEffect { mediaPlayer.playbackRate = playbackRate }
    }
  • Bu, dış dünyadaki durumu oluşturmada olanlarla senkronize etmek istediğinizde en uygun yöntemdir.

  • Medya oynatıcı örneğinde, oynatma durumunu oluşturma tarafından alınan bir parametreyle senkronize eden başka bir örnek daha var.

  • Yan etki kullanımlarınızı denetlemeye ve daha az maliyetli bir yan etkiye geçmeye karar verirseniz, bahsettiğimiz diğer davranışlara ve kullanım alanlarına ek olarak, yan etkilerle ilgili sıralama garantilerinde de bir fark olduğunu unutmayın.

  • Önce tüm tek kullanımlık efektler, ardından anahtarlı veya anahtarsız normal yan efektler yürütülür. Kare tamamlandıktan sonra ise bir dağıtım işlemi olduğu için başlatılan tüm efektler çalıştırılır.

  • Yayınlanan efektleriniz için bu zamanlamaya güveniyorsanız geçiş yapmayı düşünürken bunu göz önünde bulundurun.

  • Ayrıca, herhangi bir taşıma işlemi için kodunuzu test ettiğinizden ve bu zamanlama garantilerinden hiçbirine bağlı olmadığınızdan ve kodunuzun daha sonra da çalıştığından emin olun.

  • Yan etkinin karmaşıklığını azaltamıyorsanız yan etkileri birleştirerek sayısını azaltabilirsiniz.

    @Composable
    fun ShoppingCartScreen(
        viewModel: CartViewModel
    ) {
        LaunchedEffect(Unit) { viewModel.loadCart() }
        LaunchedEffect(Unit) { viewModel.loadPaymentMethods() }
        LaunchedEffect(Unit) { viewModel.loadDeliveryAddresses() }
    
        CartContent(...)
    }
  • Bu, özellikle aynı anahtarı kullanan birden fazla efektiniz varsa dikkate alınması gereken bir noktadır.

  • Aynı anda yürütülebildikleri için birleştirme için iyi bir adaydır.

  • Bu örnekte, her biri bir görünüm modeline eşzamansız çağrı yapan birkaç LaunchedEffect var.

  • Bunları tek bir LaunchedEffect ile değiştirip diğer başlatma çağrılarını bunun içine yerleştirirsem, burada çalışan eş yordam sayısı aynı olsa bile, işi oluşturma yerine eş yordamlara taşıdığı için daha az ek yük oluşturur.

    @Composable
    fun ShoppingCartScreen(
        viewModel: CartViewModel
    ) {
        LaunchedEffect(Unit) {
            launch { viewModel.loadCart() }
            launch { viewModel.loadPaymentMethods() }
            launch { viewModel.loadDeliveryAddresses() }
        }
    
        CartContent(...)
    }
  • Bu nedenle, mümkün olduğunda efekt sayısını azaltmak için bu gibi durumlarda yerinde taşıma yapmayı düşünmek iyi bir fikirdir.

  • Efektler ve eş yordamlar, uygulamanızda denetleyebileceğiniz yerlerden yalnızca biridir. Ancak her composable ve her uygulama farklıdır ve farklı fırsatlar sunar.

  • Uygulamanızla ilgili olanları anlamak için Andrei'nin tanıtacağı bir sonraki araç olan oluşturma izleme hakkında konuşmamız gerekiyor.

Oluşturma ve yeniden oluşturma izleme

  • İşte böyle.

  • Oluşturma izleme, daha önce farklı bir slaytta incelediğimiz Perfetto karesinin bu aslına uygun kopyasına bakarsanız farklı aşamalarla ilgili zamanlama bilgileri verir ancak neler olduğu hakkında pek ayrıntı vermez.

  • Ne?

  • Evet, hangi composable işlevlerin çalıştırıldığı, oluşturma işlemi sırasında gerçekleşir.

  • Oluşturma izleme de bu konuda bize yardımcı olur.

  • Bu nedenle, Compose derleyicisine bazı işaretçiler ekledik. Bu işaretçiler, her işlevi açıklayıp Perfetto izinde görünür hale getirir. Böylece her işlevin ne kadar sürdüğünü kontrol edebilirsiniz.

  • Bu sayede, tam olarak hangi işlevlerin yeniden oluşturulduğunu öğrenebilirsiniz.

  • Ancak bunun biraz ek yükü vardır. Bu nedenle, en doğru performans verilerini yakalamak istiyorsanız normal Perfetto'yu kullanmanız veya Perfetto yakalamaları olmadan çalıştırıp diğer metrikleri kullanmanız gerekir.

  • Ancak her bir composable'ın ne kadar sürdüğünü öğrenmek istiyorsanız oluşturma izleme özelliği çok kullanışlıdır.

  • Bazen de öğelerin neden yeniden oluşturulduğunu anlamak istersiniz. Compose Tracing çalışma zamanı izlemenin bir sonraki sürümünde (1.13) bu sorunu çözmeye çalışıyoruz.

  • Yeniden oluşturma izleme özelliğini kullanıma sunuyoruz. Bu özellik, yeniden oluşturmayla sonuçlanan her durum okuma ve durum yazma işlemini açıklıyor.

  • Perfetto izinizde buna benzer bir şey görürsünüz. Burada her bir durum yazma işlemini ve yazma ile okuma işlemlerinin gerçekleştiği çağrı yığınlarını görebilirsiniz. Böylece bunları, kodunuzda bu durumlara erişilen veya değiştirilen gerçek satırlarla eşleştirebilirsiniz.

  • Daha fazla bilgi edinmek isterseniz buradaki bağlantıya göz atın.

  • Bu sayfada hem oluşturma izleme hem de yeniden oluşturma izleme ile ilgili tüm dokümanlar yer alır.

  • Şimdi, durum yazma işlemlerini önlemek için bu özelliği nasıl daha verimli kullanabileceğimizi anlatması için sözü Andrew'a bırakıyorum.

Durum optimizasyonu ve geçersiz kılmalar

  • Teşekkürler, Andrei.

  • Bu incelemeden elde ettiğiniz sonuçlara göre, oluşturma işleminizde uygulamanız için yararlı olmayan ve en başta önlenebilecek geçersiz kılmalar olduğunu fark edebilirsiniz.

  • Örneğin, en sık karşılaştığım örnek, görünürlüğü veya kaydırma ilerlemesine bağlı olan bir "başa kaydır" düğmesidir.

    @Composable
    fun ScrollToTopButton(
        scrollState: ScrollState
    ) {
        val showScrollToTopButton = scrollState.value > 0
        val coroutineScope = rememberCoroutineScope()
    
        if (showScrollToTopButton) {
            FloatingActionButton(
                onClick = {
                    coroutineScope.launch { scrollState.animateScrollTo(0) }
                }
            ) {
                Icon(Icons.Default.ArrowUpward, "Scroll to Top")
            }
        }
    }
  • Burada, oluşturma içindeki bir kaydırma durumunun değerini okuyorum. Bu, çok sık değişen bir durum. Kare başına bir kez değişebilir. Bu da önlenebilecek birçok yeniden oluşturmaya neden olur. Çünkü kaydırma durumunun kendisi değişse bile burada önemsediğimiz asıl koşul o kadar sık değişmez.

  • Bu, uygulamanız için önemli olmayan durum değişikliklerinin geçersiz kılınmasını önleyebilecek hesaplanmış durum gibi bir şey kullanmanızı önerdiğimiz bir örnektir.

  • Bu örnekte, hesaplamayı hesaplanan bir durumun içine sarmaladım. Bu durumu okuduğumuzda, kaydırma konumuna göre geçersiz kılmak yerine, bu composable'ı tüm koşula göre koşullu olarak geçersiz kılar ve diğer durumdan türetir.

    @Composable
    fun ScrollToTopButton(
        scrollState: ScrollState
    ) {
        val showScrollToTopButton by remember { computedStateOf { scrollState.value > 0 } }
        val coroutineScope = rememberCoroutineScope()
    
        if (showScrollToTopButton) {
            FloatingActionButton(
                onClick = {
                    coroutineScope.launch { scrollState.animateScrollTo(0) }
                }
            ) {
                Icon(Icons.Default.ArrowUpward, "Scroll to Top")
            }
        }
    }
  • Bu kalıbı daha önce gördüyseniz muhtemelen türetilmiş durumla kullanıldığını görmüşsünüzdür.

  • ComputedState, Compose 1.13'te yeni bir özelliktir. ComputedState ve DerivedState, hesaplamaları değiştiğinde koşullu olarak geçersiz kılma konusunda benzer bir amaca sahip olsa da bunu yapma şekilleri arasında çok küçük bir fark vardır.

  • Compose'un ilk sürümünden beri kullandığımız DerivedState, hesapladığı değeri önbelleğe alır ve bağımlılıkları değişene kadar yeniden kullanır.

  • ComputedState ise değeri her okuduğunuzda hesaplar.

  • Önbelleğe alma faydalı gibi görünse de aslında çoğu durumda önbelleğe alma, sonucu okumaktan daha maliyetlidir.

  • Bunun nasıl gerçekleştiğini anlamak için bu durumların uygulamanızda nasıl kullanılabileceğine dair bir örneğe bakabilir ve her birinin gerçekte ne yaptığını inceleyebiliriz.

  • Burada, yukarı kaydırma düğmesinde yaptığımız işlemi kabaca yansıtan aynı durum var. Bu durum, başka bir duruma dayalı bir koşula sahip ve bunu hem DerivedState hem de ComputedState olarak ayarladım.

    var a by mutableStateOf(0)
    
    val derivedState = derivedStateOf { a > 0 }
    val computedState = computedStateOf { a > 0 }
    
    // First read
    derivedState.value
    computedState.value
    
    // Second read
    derivedState.value
    computedState.value
  • Ayrıca durumu iki kez okuyorum ve her iki seferde de aynı değeri alacağız ancak arka planda gerçekleşenler ikisi arasında farklı.

  • Perfetto gibi bir araçta burada gösterilecek izi basitleştirmek için bu durumların ikisini de okuduğunuzda gerçekte ne olduğunu inceleyebiliriz.

  • Burada, computedState.value'i çağırarak başlıyoruz.

  • Hesaplama lambda'sını yürütür. Bu lambda, durumun değerini okur. Ayrıca, okuma işleminin gerçekleştiğini bildirmek için de biraz bakım yapılır.

  • Bu, Compose'daki tüm anlık görüntü durumları için geçerlidir.

  • İkinci okuma, ilk okumayla tamamen aynı şekilde davranır ve aynı performans özelliklerine sahiptir.

  • Buna karşılık, DerivedState'in ilk okuması benzer bir şey yapar. Burada, hesaplama lambda'sını çağırması ve diğer bildirimi yapması gerekir. Ancak, kontrol edilecek önbellek olmadığından ilk seferde biraz kısa devre yapan önbellek yönetimi ek yükü vardır.

  • Bu nedenle, önbelleğe alma işlemi için biraz bakım yapar ve ardından aynı şeyi biraz daha fazla sürede gerçekleştirir.

  • Ancak DerivedState için ikinci okuma, her ikisi de oldukça önemsiz durumlar olsa da ComputedState için ikinci okumaya göre daha hızlıdır.

  • Önceki slaytlarda da belirttiğim gibi, anlık görüntü okuma maliyeti bazı durumlarda önemsiz değildir ve önbellekten okuma işlemi, okuma işleminin kendisinden daha hızlıdır. Bu nedenle, DerivedState için ikinci okuma işleminde çok küçük bir kazanç görüyoruz.

  • Ancak asıl ilginç senaryo, durumun geçersiz kılınması gerektiğinde ne olacağıdır.

  • Bu, yazma sonrası okuma senaryosu olarak adlandırılır. Bu senaryoda, hesaplamada kullanılan durumlardan birini güncellersiniz ve hesaplamanın tekrar yürütülmesi gerekir.

  • Kodda bu, a'yı değiştirip (ör. artırıp) değeri tekrar okumak veya yeniden oluşturmak gibi bir işlemle yapılır.

    var a by mutableStateOf(0)
    
    val derivedState = derivedStateOf { a > 0 }
    val computedState = computedStateOf { a > 0 }
    
    ...
    
    // Read-after-write
    a++
    derivedState.value
    computedState.value
  • Bu, asıl diyagramda da ComputedState için tamamen aynı şekilde görünecektir.

  • Yönetilecek bir önbelleği yoktur. Bu, ilk, ikinci, üçüncü veya kaçıncı okuma olursa olsun tamamen aynı görünür.

  • Ancak DerivedState'in işi daha zordur.

  • Önbelleğin geçersiz olduğunu görmek için çok maliyetli bir yoldan geçer. Güncelleme yapmadan ve hesaplamayı çağırmadan önce önbellek değerini geri yazmak ve tüm bu değerlerin aslında değiştiğini anlamak için bağımlılık takibi yapması ve birçok bağımlı gözlemi yönetmesi gerekir.

  • Tüm bunlar bir araya geldiğinde DerivedState'i geçersiz kılmanın maliyeti, ComputedState'e kıyasla çok daha yüksek olur.

  • Tüm bu iddiaları sayılarla ifade etmek gerekirse ComputedState, ilk okumada genellikle yaklaşık 200 ns daha hızlıdır.

  • ComputedState, belirli karşılaştırma koşulları altında Pixel 6'da yaklaşık 350 ns kaybeder.

  • Bu nedenle, ikinci okumada ComputedState, DerivedState'e kıyasla biraz daha yavaş olur.

  • Ancak asıl ilgilendiğimiz durumda, yani geçersiz kıldığımız ve muhtemelen kare üzerinde çalışmanız gereken durumda, ComputedState, DerivedState'e kıyasla neredeyse iki kat daha hızlıdır.

  • Durumlarınızı ne için kullandığınıza bağlı olarak ComputedState, tercih edebileceğiniz çok iyi bir ilk seçenek olabilir.

  • Genel olarak, DerivedState ve ComputedState kullanılırken hâlâ bir karar verilmesi gerekir.

  • ComputedState'i kullanımdan kaldırmıyoruz. Her ikisinin de kullanım alanları var.

  • Genellikle, hangisini seçeceğinizden emin değilseniz hesaplanan durum iyi bir varsayılan seçenektir.

  • Türetilmiş durum, önbelleğe almadan gerçekten yararlandığınızda en kullanışlıdır. Yani karmaşık bir algoritma, büyük bir ayırma, önbelleğe almadan gerçekten yararlanan çok sayıda durum okuma gibi önemsiz olmayan bir şey yaparsınız veya nesne eşitliği ve nesne kimliği eşitliği gibi şeylere bağlı olan önemsiz olmayan herhangi bir şey yaparsınız. Burada farkı kapatır ve türetilmiş durumun sunduğu önbelleğe almanın avantajlarından yararlanırsınız.

Compose'da atlama ve kararlılık

  • Bahsetmek istediğim bir diğer önemli konu da türetilmiş durum ve hesaplanan durumun, composable'ın kendisini geçersiz kılmaktan kaçınmaya çalışmasıdır.

  • Bu, bir composable'ı atlamaktan farklıdır. Koşullu olarak geçersiz kılma veya bir composable içinde işlem yapmama hakkında düşündüğümüzde genellikle bundan bahsederiz.

  • Burada açıklamak istediğim gerçek şu: Compose'da kararlılık ve atlama hakkında çok fazla konuşulsa da bu her şey demek değildir ve her zaman optimizasyon için doğru şey değildir.

  • Daha önce de belirttiğimiz gibi, oluşturmadaki her şeyin belirli bir maliyeti vardır. Atlamanın da maliyeti vardır.

    // What you write
    @Composable fun ContactListItem(
        contact: Contact
    ) { ... }
    
    // What you get
    @Composable fun ContactListItem(
        contact: Contact,
        $composer: Composer?
        $changed: Int
    ) {
        val $dirty = $changed or
            $composer.changed(contact)
    
        if ($dirty) { /* Your function body */ }
        else $composer.skipToGroupEnd()
    }
  • Atlama işlemi her zaman kolay olmayabilir.

  • Öncelikle bu özelliği etkinleştirmek için yapmamız gereken çok fazla bakım çalışması var.

  • Tüm parametrelerinizi depolamak, ilk oluşturma işleminde bunlar için yer bulmak ve yeniden oluşturma işlemi yaptığınızda bu kaydı güncellemek gibi işlemler yapmamız gerekir.

  • Ayrıca, oluşturulabilir öğenizin atlanıp atlanmayacağını anlamak için değerlerinizi önceki değerlerle karşılaştırmamız gerekir.

  • Ardından, tüm bunlar kodu atlayan veya atlamayan bir if kontrolünün gücüdür.

  • Bu, burada ne olduğuna bağlı olarak pahalı olabilir.

  • Derleyici için arka planda bir optimizasyon vardır. Aynı parametreyi iletirseniz eşitlik kontrollerini yeniden kullanabiliriz ancak bu her şey demek değildir ve bunu geçersiz kılmak oldukça kolaydır.

  • Atlamanın bizi biraz ele verdiği bir örneğe bakalım. Klasik bir örnek olarak, ekranda oluşturulacak tüm verileri içeren devasa bir nesneye sahip kaydırılabilir bir listeyi inceleyebiliriz.

    data class ContactsModel(
        val header: String,
        val contacts: List<Contact>
    )
    
    data class Contact(...)
    
    @Composable
    fun ContactsScreen(model: ContactsModel) {
        Column {
            TopAppBar(title = { Text(model.header) })
            ContactsList(model.contacts)
        }
    }
    
    @Composable
    fun ContactsList(contacts: List<Contact>) {
        LazyColumn {
            items(contacts) { ContactListItem(it) }
        }
    }
    
    @Composable
    fun ContactListItem(contact: Contact) { ... }
  • Burada ekran modeli, başlık ekranı gibi bir şey ve ardından tüm kişi listesini içerir. Bu liste, daha sonra birkaç composable'dan geçer ve sonunda bir lazy list çağırır ve bunları bazı öğe hücreleriyle ekrana atar.

  • Bu kodun oluşturma hiyerarşisi şuna benzer: Yine kişi ekranıyla başlarız, biraz genişletiriz ve ardından öğelerimizi çağırıp sunan başka composable'lar ekleriz.

  • Verilerimizin nasıl aktarıldığına bakarsak üstteki modelle başlarız.

  • Bunu önce bir dizeye, ardından bir listeye ayırırız. Bu liste daha sonra geç listeye yönelik bir öğe lambda'sına dönüştürülür ve geç liste, ekranda görünen tüm öğeleri oluşturur.

  • Bu listedeki son kişiyi değiştirdiğimizde ne olacağına bakalım.

  • Bizim istediğimiz, listedeki son öğenin geçersiz kılınması. Ancak burada gerçekleşecek olan bu değil.

  • Bu yeniden oluşturma işleminin oluşturma izine bakarsak kişi ekranından başlayıp liste öğesine kadar giden bir iz görürüz.

  • Bu, buraya inmenin oldukça pahalı bir yolu.

  • Ayrıca, izleme içinde hiçbir şey olmuyormuş gibi görünen tüm bu bölümlerin olduğunu da görebiliriz.

  • Burada net olmasa da aslında composable'lar atlanıp atlanmayacağına burada karar verir.

  • Bu örnekte, geç listelerin yeniden oluşturulma şekli nedeniyle yalnızca iki composable atlanır.

  • Yeni bir içerik lambda'sı verdiğinizde, sahip olduğu tüm öğeleri geçersiz kılar.

  • Bu nedenle, değişen kişinin yanı sıra değişmeyen iki kişiyi de geçersiz kılar ve burada yalnızca bu iki öğe atlanır.

  • Diğer her şey, eşitlik karşılaştırmasını atlayarak aslında yürütülmesi gerektiğini fark eder.

  • Nesneleriniz sabitse bu karşılaştırma oldukça maliyetli olabilir.

  • Tüm veri yapılarınızı incelememizi zorunlu kılarsanız bu durum, yalnızca iki nesnenin eşit olup olmadığını anlamak için önemli bir maliyet oluşturabilir.

  • Daha da kötüsü, yapıyı bozduğumuz için listede aynı karşılaştırmayı iki kez yapmamız gerekir.

  • Derleyicinin varsayılan davranışla çalışmasına izin verir ve güçlü atlamayı kullanırsanız atlama özelliğinden yararlanmaya devam edebilir ve composable'larınızı koşullu olarak atlamak için kontroller yapabilirsiniz. Ancak bu kontroller, yapısal eşitlik yerine nesne kimliği eşitliğini kullanarak çok daha hızlı bir yol izler.

  • Bu, hesaplama açısından çok daha hızlıdır. Aynı öğeleri bu karede yeniden oluştursak bile (burada hızlı bir şekilde girmemiz önemlidir) bu composable'ları atlayamadığımızı anlamak için çok fazla zaman harcamayız.

  • Bu nedenle, doğrudan yeniden oluşturma sayıları yerine uygulamanızda neler olduğunu anlamak için karşılaştırmalar ve izler gibi araçları kullanmanızı öneririz.

  • Faydalı bir gösterge olabilirler ancak uygulamalarınızın optimizasyonu sırasında mutlaka aşırı odaklanılması veya aşırı endekslenmesi gereken bir şey değildir.

  • Oluşturulabilir öğenizi atlamaya çalışmak yerine, en baştan geçersiz kılmaktan kaçınmanız gerektiğini unutmayın.

Yöntem izleriyle yürütmeyi analiz etme

  • Bu örnekte, oluşturma izlerinin neler olduğu hakkında çok fazla bilgi verebileceğini ancak resmin tamamını göstermeyebileceğini gördük.

  • Andrei, yöntem izleri hakkında bilgi verecek.

  • İşte böyle.

  • Andrew'un da belirttiği gibi, oluşturma izleme yalnızca composable işlevlere ek açıklama eklediği için her zaman her şeyi göstermez. Perfetto izlemeyi genişleten oluşturma izlemede boş alan gördüğünüz durumlar olabilir ancak bu boş alanlar da her şeyi göstermez.

  • Bazen Compose'un yaptığı bir şey, bazen de kodunuzdaki bir şey olabilir. Bunu yalnızca buradaki oluşturma izine bakarak anlamak gerçekten zordur.

  • Şimdi yöntem izini almanız gerekir.

  • Android Studio'daki profil oluşturucuda bir öğe var. Ayrıca izi almak için ADB komutlarını da kullanabilirsiniz. Bu komutlar, Java dünyasında gerçekleşen her işlev çağrısını size verir.

  • Compose derleyicisinin kodladığı her şeyi, Compose çalışma zamanının yaptığı her şeyi ve kodunuzun yaptığı her şeyi içerir. İzleme, tam olarak neyin yürütüldüğünü ve bu alanda hangi işlevlerin çağrıldığını araştırmak için çok kullanışlıdır.

  • Bir şey daha var. Ortadaki bu öğeyi görmek istiyorum.

  • Yöntem izlemenin çok yüksek ek yükü olduğunu belirtmek isterim.

  • İşlev ağacının karmaşıklığına bağlı olarak 10 ila 20 kat daha fazla ek yükünüz olur çünkü her işlev çağrısı biraz daha ek yük getirir.

  • Bunu göstermek için ekran görüntüsüne bakabiliriz.

  • Uygulamanın başlangıcındaki yöntem izi genellikle bu şekilde görünür. "Bunu nasıl anlamlandırabilirim?" diye sorabilirsiniz.

  • Oluşturma izini başlatıp buradaki işlevlere bakarsınız.

  • Bu, basitleştirilmiş bir oluşturma izi gibidir. ScreenContent, bazı düğmeler ve diğer işlevler bulunur.

  • ScreenContent'te neler olduğunu öğrenmek istediğimizi varsayalım.

  • Perfetto'da bir arama çubuğu vardır.

  • Yöntem izinizi Perfetto görüntüleyicide yükleyip buradakiyle aynı işlevi (ScreenContent) aradığınızda ekranın yöntem izi olan buna benzer bir şey görürsünüz.

  • Burada bazı Composer.startGroup ve grup aramaları olduğunu görebilirsiniz.

  • Çalışma zamanları ve derleyici, oluşturma işleminizin gerçekten sihrini göstermesi için arka planda bunu yapar ancak buradaki ilginç çağrı Composer.changed çağrısıdır.

  • Bu şekilde, parametrelerin composable'ın önceki çağrıları ile mevcut çağrısı arasında değişip değişmediğini kontrol ederiz.

  • Bu Composer.changed sayısının büyük bir kısmını ScreenModel.equals oluşturuyor.

  • ScreenModel, bir tür büyük veri sınıfı olabilir. Yeniden oluşturma sayısını azaltmak için bu sınıfı kararlı hale getirmiş olabiliriz. Bu nedenle, derleyici raporunda görünmüş olabilir.

  • Bu kararlı ek açıklamayı kaldırmak, bu özel durumda performansa yardımcı olabilir.

  • Bu nedenle, bu sorunu aramanız gerekir ve bunu bulmak için en iyi araç, oluşturma yöntemi izleridir.

  • İşte böyle.

  • Bugün birçok konuyu ele aldık. Şimdi sözü Andrew'a bırakıyorum.

Hızlı, birleştirilebilir tasarım ve performans araçları

  • Bugün konuştuklarımızı özetlemek gerekirse, composable'ları tasarlarken ve mevcut uygulamalarınızı optimize ederken aklınızda bulundurmanız gereken birkaç nokta var.

  • Öncelikle, daha ucuz composable'ların ve geçersiz kılmadığınız composable'ların, atladığınız composable'lardan daha iyi olduğunu unutmayın.

  • Birleştirilebilir öğeler tasarlarken daha küçük ve karşılaştırılması daha kolay olan bağımsız değişkenleri dikkate almanın ve optimizasyondan yararlanmak için diğer işlev çağrılarıyla karşılaştırmaları yeniden kullanmanın daha iyi olduğunu unutmayın.

  • Birleştirilebilir öğelerinizde API'leri kullanırken performansın veya kare süresinin, API'lerin maliyetine ve bunları ne sıklıkta kullandığınıza bağlı olacağını unutmayın.

  • Bu maliyetlerin nasıl biriktiğine dikkat edin ve uygulamanız için en uygunsuz zamanda karşılaşabileceğiniz, bunlarla ilişkili kaldırma maliyetleri olabileceğini de unutmayın.

  • Ayrıca, uygulamanızın içinde optimizasyon yaparken iyi bir vatandaş olmanın önemli olduğunu unutmayın.

  • Bilinçli kararlar almak için karşılaştırmalar yaparak başlayın, kodunuzun doğru alanlarını hedeflediğinizden emin olmak için uygulamanızın profilini oluşturun ve değişiklikleri yaptığınızı ve karşılaştırmalar ile profil oluşturma sonucunda yaptığınız değişikliklerden beklediğiniz sonuçları gördüğünüzü doğrulayın.

  • Ayrıca, uygulamanızda sürdürülebilirlik ve performans arasında bir denge olabileceğini unutmayın.

  • Bu nedenle, aşırı optimizasyon yapmadığınızdan ve sürdürülebilirlik konusunda kendinizi köşeye sıkıştırmadığınızdan emin olmak için size ve ekibinize en uygun olanı yapın.

  • Daha fazla yardım için kullanabileceğiniz bir araç daha var. Bu araç, zaten sahip olduğunuz yapay zeka temsilcisidir.

  • Android KSA'nın bir parçası olarak ve orada sunduğumuz becerilerimizin bir parçası olarak, bugün bahsettiğimiz bilgilerin çoğunu içeren ve uygulamanızı inceleyip denetleyebilen ve bu tür optimizasyonlar için önerilerde bulunabilen bir Jetpack Compose performans becerisi kullanıma sunduk.

  • Android KSA'yı kullanarak mevcut temsilcinize yükleyebilirsiniz.

    android skills add jetpack-compose-performance
  • Android KSA hakkında daha fazla bilgi edinmek için developer.android.com adresini ziyaret edebilirsiniz.

  • Becerilerimiz hakkında daha fazla bilgiyi GitHub depomuzda bulabilirsiniz.

  • Güvenli karşılaştırmalar ve keyifli besteler dileriz.

Kod

Kod snippet'leri

"Yan etkileri yönetme" bölümünde 09:34'teki Kotlin snippet'i
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val mediaPlayer = rememberMediaPlayer()

    // Asynchronous work and animations
    LaunchedEffect(contentSource) { mediaPlayer.load(contentSource) }
}
"Yan etkileri yönetme" bölümünde 10:15'teki Kotlin snippet'i
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val activity = LocalActivity.current

    // Setup and teardown
    DisposableEffect(Unit) {
        activity.requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE
        onDispose {
            activity.requestedOrientation = SCREEN_ORIENTATION_UNSPECIFIED
        }
    }
}
"Yan etkileri yönetme" bölümünde 10:36'daki Kotlin snippet'i
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val mediaPlayer = rememberMediaPlayer()

    // One-shot events
    SideEffect(Unit) { mediaPlayer.play() }
}
"Yan etkileri yönetme" bölümünde 11:12'deki Kotlin snippet'i
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val mediaPlayer = rememberMediaPlayer()

    // Synchronize the outside world to the state of composition
    SideEffect { mediaPlayer.playbackRate = playbackRate }
}
"Yan etkileri yönetme" bölümünde 12:12'deki Kotlin snippet'i
@Composable
fun ShoppingCartScreen(
    viewModel: CartViewModel
) {
    LaunchedEffect(Unit) { viewModel.loadCart() }
    LaunchedEffect(Unit) { viewModel.loadPaymentMethods() }
    LaunchedEffect(Unit) { viewModel.loadDeliveryAddresses() }

    CartContent(...)
}
"Yan etkileri yönetme" bölümünde 12:36'daki Kotlin snippet'i
@Composable
fun ShoppingCartScreen(
    viewModel: CartViewModel
) {
    LaunchedEffect(Unit) {
        launch { viewModel.loadCart() }
        launch { viewModel.loadPaymentMethods() }
        launch { viewModel.loadDeliveryAddresses() }
    }

    CartContent(...)
}
"Durum optimizasyonu ve geçersiz kılmalar" videosunda 15:12'deki Kotlin snippet'i
@Composable
fun ScrollToTopButton(
    scrollState: ScrollState
) {
    val showScrollToTopButton = scrollState.value > 0
    val coroutineScope = rememberCoroutineScope()

    if (showScrollToTopButton) {
        FloatingActionButton(
            onClick = {
                coroutineScope.launch { scrollState.animateScrollTo(0) }
            }
        ) {
            Icon(Icons.Default.ArrowUpward, "Scroll to Top")
        }
    }
}
"Durum optimizasyonu ve geçersiz kılmalar" bölümünde 15:51'deki Kotlin snippet'i
@Composable
fun ScrollToTopButton(
    scrollState: ScrollState
) {
    val showScrollToTopButton by remember { computedStateOf { scrollState.value > 0 } }
    val coroutineScope = rememberCoroutineScope()

    if (showScrollToTopButton) {
        FloatingActionButton(
            onClick = {
                coroutineScope.launch { scrollState.animateScrollTo(0) }
            }
        ) {
            Icon(Icons.Default.ArrowUpward, "Scroll to Top")
        }
    }
}
"State optimization and invalidations" (Durum optimizasyonu ve geçersiz kılmalar) videosunda 16:49'daki Kotlin snippet'i
var a by mutableStateOf(0)

val derivedState = derivedStateOf { a > 0 }
val computedState = computedStateOf { a > 0 }

// First read
derivedState.value
computedState.value

// Second read
derivedState.value
computedState.value
"State optimization and invalidations" (Durum optimizasyonu ve geçersiz kılmalar) videosunda 18:35'teki Kotlin snippet'i
var a by mutableStateOf(0)

val derivedState = derivedStateOf { a > 0 }
val computedState = computedStateOf { a > 0 }

...

// Read-after-write
a++
derivedState.value
computedState.value
"Skipping and stability in Compose" videosunda 21:07'deki Kotlin snippet'i
// What you write
@Composable fun ContactListItem(
    contact: Contact
) { ... }

// What you get
@Composable fun ContactListItem(
    contact: Contact,
    $composer: Composer?
    $changed: Int
) {
    val $dirty = $changed or
        $composer.changed(contact)

    if ($dirty) { /* Your function body */ }
    else $composer.skipToGroupEnd()
}
"Skipping and stability in Compose" videosunda 21:53'teki Kotlin snippet'i
data class ContactsModel(
    val header: String,
    val contacts: List<Contact>
)

data class Contact(...)

@Composable
fun ContactsScreen(model: ContactsModel) {
    Column {
        TopAppBar(title = { Text(model.header) })
        ContactsList(model.contacts)
    }
}

@Composable
fun ContactsList(contacts: List<Contact>) {
    LazyColumn {
        items(contacts) { ContactListItem(it) }
    }
}

@Composable
fun ContactListItem(contact: Contact) { ... }
"Hızlı birleştirilebilir tasarım ve performans araçları" videosunda 29:40'taki kabuk snippet'i
android skills add jetpack-compose-performance