Pemecahan masalah performa praktis di Jetpack Compose

1. Sebelum memulai

Dalam codelab ini, Anda akan mempelajari cara meningkatkan performa runtime aplikasi Compose. Anda akan mengikuti pendekatan ilmiah untuk mengukur, men-debug, dan mengoptimalkan performa. Anda akan menyelidiki beberapa masalah performa dengan pelacakan sistem dan mengubah kode runtime dengan performa kurang baik di aplikasi contoh berisi beberapa layar yang mewakili tugas berbeda. Masing-masing layar dibangun berbeda dan mencakup hal berikut:

  • Layar pertama adalah daftar dua kolom dengan item gambar dan beberapa tag di atas item. Di sini, Anda akan mengoptimalkan composable berat.

8afabbbbbfc1d506.gif

  • Layar kedua dan ketiga berisi status yang sering direkomposisi. Di sini, Anda akan menghapus rekomposisi yang tidak perlu untuk mengoptimalkan performa.

f0ccf14d1c240032.gif 51dc23231ebd5f1a.gif

  • Layar terakhir berisi item yang tidak stabil. Di sini, Anda akan menstabilkan item dengan berbagai teknik.

127f2e4a2fc1a381.gif

Prasyarat

Yang Anda pelajari

Yang Anda perlukan

2. Memulai persiapan

Untuk memulai, ikuti langkah-langkah ini:

  1. Buat clone repositori GitHub:
$ git clone https://github.com/android/codelab-android-compose.git

Atau, Anda dapat mendownload repositori sebagai file ZIP:

  1. Buka project PerformanceCodelab yang berisi cabang berikut:
  • main: Berisi kode awal untuk project ini, yang dapat Anda ubah untuk menyelesaikan codelab.
  • end: Berisi kode solusi untuk codelab ini.

Sebaiknya Anda memulai dengan cabang main dan mengikuti codelab langkah demi langkah sesuai kemampuan Anda.

  1. Jika Anda ingin melihat kode solusi, jalankan perintah ini:
$ git clone -b end https://github.com/android/codelab-android-compose.git

Selain itu, Anda dapat mendownload kode solusi:

Opsional: Rekaman aktivitas sistem yang digunakan dalam codelab ini

Anda akan menjalankan beberapa benchmark yang merekam aktivitas sistem selama codelab.

Jika Anda tidak dapat menjalankan benchmark ini, berikut daftar rekaman aktivitas sistem yang dapat Anda download:

3. Pendekatan untuk memperbaiki masalah performa

Menemukan UI yang lambat dan berperforma kurang baik dapat dilakukan hanya dengan melihat sekilas dan menjelajahi aplikasi. Namun, sebelum mulai memperbaiki kode berdasarkan asumsi, Anda harus mengukur performa kode untuk memahami apakah perubahan yang Anda lakukan akan membuat perbedaan.

Selama pengembangan dengan build debuggable aplikasi, Anda mungkin memperhatikan ada performa yang kurang baik dan mungkin ingin mulai mengatasi masalah ini. Namun, performa aplikasi debuggable tidak mewakili apa yang akan dilihat pengguna Anda, sehingga penting untuk memastikan dengan aplikasi non-debuggable bahwa memang ada masalah. Dalam aplikasi debuggable, semua kode harus diinterpretasikan oleh runtime.

Saat berpikir tentang performa di Compose, tidak ada aturan ketat yang harus Anda ikuti untuk mengimplementasikan fungsi tertentu. Anda tidak boleh melakukan hal berikut sebelum waktunya:

  • Jangan mencari dan memperbaiki setiap parameter tidak stabil yang tersembunyi dalam kode Anda.
  • Jangan menghapus animasi yang menyebabkan rekomposisi composable tersebut.
  • Jangan melakukan pengoptimalan yang sulit dibaca berdasarkan firasat Anda.

Semua perubahan ini harus dilakukan dengan cara yang cermat menggunakan alat yang tersedia untuk memastikan bahwa perubahan tersebut mengatasi masalah performa.

Ketika menangani masalah performa, Anda harus mengikuti pendekatan ilmiah berikut:

  1. Tetapkan performa awal dengan pengukuran.
  2. Amati penyebab masalah.
  3. Modifikasi kode berdasarkan pengamatan.
  4. Ukur dan bandingkan dengan performa awal.
  5. Ulangi.

Jika Anda tidak mengikuti metode terstruktur, beberapa perubahan mungkin meningkatkan performa, tetapi perubahan lainnya mungkin menurunkannya. Pada akhirnya, Anda memperoleh hasil yang sama.

Sebaiknya tonton video berikut tentang cara meningkatkan performa aplikasi dengan Compose yang membahas proses memperbaiki masalah performa dan bahkan menunjukkan beberapa tips tentang cara meningkatkannya.

Membuat Profil Dasar Pengukuran

Sebelum menyelidiki masalah performa lebih dalam, buat Profil Dasar Pengukuran untuk aplikasi Anda. Di Android 6 (level API 23) dan yang lebih baru, aplikasi menjalankan kode yang diinterpretasikan saat runtime dan mengompilasi just-in-time (JIT) dan ahead-of-time (AOT) saat penginstalan. Kode yang diinterpretasikan dan dikompilasi JIT berjalan lebih lambat daripada AOT, tetapi membutuhkan lebih sedikit ruang pada disk dan memori. Itulah sebabnya tidak semua kode harus dikompilasi AOT.

Dengan menerapkan Profil Dasar Pengukuran, Anda dapat meningkatkan proses mulai aplikasi sebesar 30% dan mengurangi kode yang berjalan dalam mode JIT saat runtime sebanyak delapan kali lipat seperti yang ditunjukkan pada gambar berikut berdasarkan aplikasi contoh Now in Android:

b51455a2ca65ea8.png

Untuk informasi lebih lanjut mengenai Profil Dasar Pengukuran, lihat referensi berikut:

Mengukur performa

Untuk mengukur performa, sebaiknya Anda menyiapkan dan menulis benchmark dengan Jetpack Macrobenchmark. Macrobenchmark adalah uji instrumentasi yang berinteraksi dengan aplikasi seperti pengguna saat memantau performa aplikasi Anda. Artinya, kode tersebut tidak mencemari kode aplikasi dengan kode pengujian sehingga memberikan informasi performa yang andal.

Dalam codelab ini, kami sudah menyiapkan codebase dan menulis benchmark agar Anda hanya fokus pada perbaikan masalah performa. Jika tidak yakin mengenai cara menyiapkan dan menggunakan Macrobenchmark dalam project Anda, lihat referensi berikut:

Dengan Macrobenchmark, Anda dapat memilih salah satu mode kompilasi berikut:

  • None: Mereset status kompilasi dan menjalankan semuanya dalam mode JIT.
  • Partial: Melakukan pra-kompilasi aplikasi dengan Profil Dasar Pengukuran dan/atau iterasi persiapan, dan menjalankan dalam mode JIT.
  • Full: Melakukan pra-kompilasi seluruh kode aplikasi, sehingga tidak ada kode yang berjalan dalam mode JIT.

Dalam codelab ini, Anda hanya akan menggunakan mode CompilationMode.Full() untuk benchmark karena yang penting hanya perubahan yang Anda buat pada kode, bukan status kompilasi aplikasi. Pendekatan ini memungkinkan Anda mengurangi varians yang disebabkan oleh kode yang berjalan dalam mode JIT, yang harus dikurangi saat menerapkan Profil Dasar Pengukuran kustom. Perhatikan bahwa mode Full dapat berdampak negatif pada proses mulai aplikasi, jadi jangan gunakan mode ini untuk benchmark yang mengukur proses mulai aplikasi, tetapi hanya gunakan untuk benchmark yang mengukur peningkatan performa runtime.

Jika Anda sudah selesai meningkatkan performa dan ingin memeriksa performa saat pengguna menginstal aplikasi, gunakan mode CompilationMode.Partial() yang menggunakan Profil Dasar Pengukuran.

Di bagian berikutnya, Anda akan mempelajari cara membaca rekaman aktivitas untuk menemukan masalah performa.

4. Menganalisis performa dengan pelacakan sistem

Dengan build debuggable aplikasi, Anda dapat menggunakan Layout Inspector dengan jumlah komposisi untuk memahami dengan cepat ketika rekomposisi terjadi terlalu sering.

b7edfea340674732.gif

Namun, hal ini hanya bagian dari penyelidikan performa secara keseluruhan karena Anda hanya mendapatkan pengukuran proksi, bukan waktu sebenarnya yang diperlukan untuk merender composable tersebut. Tidak masalah jika rekomposisi terjadi N kali ketika total durasinya kurang dari satu milidetik. Namun di sisi lain, akan jadi masalah jika komposisi hanya terjadi sekali atau dua kali dan membutuhkan waktu 100 milidetik. Sering kali, composable hanya disusun satu kali, tetapi memerlukan waktu terlalu lama dan memperlambat layar Anda.

Untuk menyelidiki masalah performa dengan andal serta mendapatkan laporan tentang apa yang dilakukan aplikasi Anda dan apakah prosesnya memerlukan waktu lebih lama dari yang seharusnya, Anda dapat menggunakan pelacakan sistem dengan pelacakan komposisi.

Pelacakan sistem memberi Anda informasi waktu tentang apa pun yang terjadi di aplikasi Anda. Hal ini tidak menambah overhead apa pun pada aplikasi. Oleh karena itu, Anda dapat menyimpannya di aplikasi produksi tanpa perlu mengkhawatirkan efek negatif terhadap performa.

Menyiapkan pelacakan komposisi

Compose otomatis mengisi beberapa informasi saat fase runtime seperti ketika terjadi rekomposisi atau ketika tata letak lambat mengambil data item. Namun, informasi tersebut tidak cukup untuk benar-benar mengetahui bagian yang bermasalah. Anda dapat meningkatkan jumlah informasi dengan menyiapkan pelacakan komposisi, yang memberi Anda nama setiap composable yang disusun selama pelacakan. Dengan begitu, Anda dapat menyelidiki masalah performa tanpa harus menambahkan banyak bagian trace("label") kustom.

Untuk mengaktifkan Pelacakan komposisi, ikuti langkah-langkah berikut:

  1. Tambahkan dependensi runtime-tracing ke modul :app Anda:
implementation("androidx.compose.runtime:runtime-tracing:1.0.0-beta01")

Pada tahap ini, Anda dapat merekam aktivitas sistem dengan profiler Android Studio yang akan menyertakan semua informasi, tetapi kita akan menggunakan Macrobenchmark untuk pengukuran performa dan perekaman aktivitas sistem.

  1. Tambahkan dependensi tambahan ke modul :measure untuk memungkinkan pelacakan komposisi dengan Macrobenchmark:
implementation("androidx.tracing:tracing-perfetto:1.0.0")
implementation("androidx.tracing:tracing-perfetto-binary:1.0.0")
  1. Tambahkan argumen instrumentasi androidx.benchmark.fullTracing.enable=true ke file build.gradle modul :measure:
defaultConfig {
    // ...
    testInstrumentationRunnerArguments["androidx.benchmark.fullTracing.enable"] = "true"
}

Untuk informasi selengkapnya tentang cara menyiapkan pelacakan komposisi, seperti cara menggunakannya dari terminal, baca dokumentasi ini.

Merekam performa awal dengan Macrobenchmark

Ada beberapa cara untuk mengambil file pelacakan sistem. Misalnya, Anda dapat merekam dengan profiler Android Studio, merekam di perangkat, atau mengambil rekaman aktivitas sistem dengan Macrobenchmark. Dalam codelab ini, Anda menggunakan rekaman aktivitas yang diambil dengan library Macrobenchmark.

Project ini berisi benchmark dalam modul :measure yang dapat Anda jalankan untuk mendapatkan pengukuran performa. Benchmark dalam project ini disetel agar hanya menjalankan satu iterasi untuk menghemat waktu selama codelab ini. Di aplikasi sebenarnya, disarankan untuk menetapkan minimal 10 iterasi jika varians output tinggi.

Untuk merekam performa awal, gunakan uji AccelerateHeavyScreenBenchmark yang men-scroll layar tugas pertama. Ikuti langkah-langkah berikut:

  1. Buka file AccelerateHeavyScreenBenchmark.kt.
  2. Jalankan benchmark dengan tindakan gutter di samping class benchmark:

e93fb1dc8a9edf4b.png

Benchmark ini men-scroll layar Task 1 dan mencatat waktu render frame dan bagian

rekaman aktivitas kustom.

8afabbbbbfc1d506.gif

Setelah benchmark selesai, Anda akan melihat hasilnya di panel output Android Studio:

AccelerateHeavyScreenBenchmark_accelerateHeavyScreenCompilationFull
ImagePlaceholderCount               min  20.0,   median  20.0,   max  20.0
ImagePlaceholderMs                  min  22.9,   median  22.9,   max  22.9
ItemTagCount                        min  80.0,   median  80.0,   max  80.0
ItemTagMs                           min   3.2,   median   3.2,   max   3.2
PublishDate.registerReceiverCount   min   1.0,   median   1.0,   max   1.0
PublishDate.registerReceiverMs      min   1.9,   median   1.9,   max   1.9
frameDurationCpuMs                  P50    5.4,   P90    9.0,   P95   10.5,   P99   57.5
frameOverrunMs                      P50   -4.2,   P90   -3.5,   P95   -3.2,   P99   74.9
Traces: Iteration 0

Metrik penting dalam output ini adalah sebagai berikut:

  • frameDurationCpuMs: Menunjukkan waktu yang diperlukan untuk merender frame. Makin pendek makin baik.
  • frameOverrunMs: Menunjukkan lama waktu frame melebihi batas, termasuk tugas di GPU. Angka negatif bagus karena berarti masih ada waktu.

Metrik lainnya, seperti metrik ImagePlaceholderMs, menggunakan bagian rekaman aktivitas kustom dan output yang menjumlahkan durasi semua bagian tersebut dalam file rekaman aktivitas dan frekuensi terjadinya dengan metrik ImagePlaceholderCount.

Semua metrik tersebut dapat membantu memahami apakah perubahan yang dilakukan pada codebase meningkatkan performa.

Membaca file rekaman aktivitas

Anda dapat membaca file rekaman aktivitas dari Android Studio atau dengan alat berbasis web Perfetto.

Meskipun profiler Android Studio bagus untuk membuka rekaman aktivitas dengan cepat dan menunjukkan proses aplikasi Anda, Perfetto menyediakan kemampuan penyelidikan yang lebih mendalam untuk semua proses yang berjalan di sistem dengan kueri SQL yang canggih dan banyak lagi. Dalam codelab ini, Anda menggunakan Perfetto untuk menganalisis rekaman aktivitas sistem.

  1. Buka situs Perfetto, yang akan memuat dasbor alat.
  2. Temukan rekaman aktivitas sistem yang direkam oleh Macrobenchmark di sistem file hosting Anda dan disimpan dalam folder [module]/outputs/connected_android_test_additional_output/benchmarkRelease/connected/[device]/. Setiap iterasi benchmark merekam file rekaman aktivitas terpisah yang masing-masing berisi interaksi yang sama dengan aplikasi Anda.

51589f24d9da28be.png

  1. Tarik file AccelerateHeavyScreenBenchmark_...iter000...perfetto-trace ke UI Perfetto dan tunggu hingga file rekaman aktivitas dimuat.
  2. Opsional: Jika Anda tidak dapat menjalankan benchmark dan menghasilkan file rekaman aktivitas, download file rekaman aktivitas kami dan tarik ke Perfetto:

547507cdf63ae73.gif

  1. Temukan proses aplikasi Anda yang bernama com.compose.performance. Biasanya, aplikasi latar depan ada di bawah baris informasi hardware dan beberapa baris sistem.
  2. Buka menu drop-down dengan nama proses aplikasi. Anda akan melihat daftar thread yang berjalan di aplikasi Anda. Biarkan file rekaman aktivitas tetap terbuka karena Anda memerlukannya di langkah berikutnya.

582b71388fa7e8b.gif

Untuk menemukan masalah performa di aplikasi, Anda dapat memanfaatkan linimasa Expected dan Actual di bagian atas daftar thread aplikasi Anda:

1bd6170d6642427e.png

Expected Timeline memberi tahu Anda saat sistem mengharapkan frame yang dihasilkan oleh aplikasi Anda menampilkan UI yang intuitif dan berperforma baik, yang dalam hal ini 16 md dan 600 µs (1000 md/60). Actual Timeline menunjukkan durasi sebenarnya dari frame yang dihasilkan oleh aplikasi Anda, termasuk tugas GPU.

Anda mungkin akan melihat warna yang berbeda, yang menunjukkan hal berikut:

  • Frame hijau: Frame dihasilkan tepat waktu.
  • Frame merah: Frame mengalami jank lebih lama dari yang diharapkan. Anda harus menyelidiki tugas yang dilakukan di frame ini untuk mencegah masalah performa.
  • Frame hijau muda: Frame dihasilkan dalam batas waktu, tetapi terlambat ditampilkan sehingga mengakibatkan peningkatan latensi input.
  • Frame kuning: Frame mengalami jank, tetapi penyebabnya bukan aplikasi.

Ketika UI dirender di layar, perubahan harus dilakukan lebih cepat daripada durasi pembuatan frame yang diharapkan perangkat. Secara historis, durasinya adalah sekitar 16,6 md mengingat kecepatan refresh layar adalah 60 Hz, tetapi untuk perangkat Android modern mungkin sekitar 11 md atau kurang karena kecepatan refresh layar adalah 90 Hz atau lebih cepat. Durasi tersebut juga dapat berbeda untuk setiap frame karena kecepatan refresh bervariasi.

Misalnya, jika UI Anda terdiri dari 16 item, durasi pembuatan setiap item adalah sekitar 1 md agar frame tidak dilewati. Sementara itu, jika hanya ada satu item, seperti pemutar video, perlu waktu hingga 16 md untuk menyusunnya tanpa jank.

Memahami call chart pelacakan sistem

Gambar berikut adalah contoh versi sederhana rekaman aktivitas sistem yang menunjukkan rekomposisi.

8f16db803ca19a7d.png

Setiap batang dari atas ke bawah adalah total waktu batang di bawahnya. Batang tersebut juga sesuai dengan bagian kode fungsi yang dipanggil. Compose memanggil rekomposisi di hierarki komposisi Anda. Composable pertama adalah MaterialTheme. MaterialTheme berisi komposisi lokal yang menyediakan informasi penerapan tema. Dari sana, composable HomeScreen dipanggil. Composable layar utama memanggil composable MyImage and MyButton sebagai bagian dari komposisinya.

Selisih dalam rekaman aktivitas sistem berasal dari kode tidak terlacak yang dijalankan karena rekaman aktivitas sistem hanya menampilkan kode yang ditandai untuk pelacakan. Kode yang dijalankan terjadi setelah MyImage dipanggil, tetapi sebelum MyButton dipanggil dan menghabiskan waktu yang diperlukan untuk mengukur selisih tersebut.

Pada langkah berikutnya, Anda akan menganalisis rekaman aktivitas yang diambil di langkah sebelumnya.

5. Mengakselerasi composable berat

Sebagai tugas pertama saat mencoba mengoptimalkan performa aplikasi, Anda harus mencari composable berat atau tugas yang berjalan lama di thread utama. Tugas yang berjalan lama mungkin memiliki arti yang berbeda tergantung seberapa rumit UI Anda dan berapa banyak waktu yang tersedia untuk menyusun UI.

Jadi, jika frame mengalami penurunan, Anda perlu menemukan composable yang memakan waktu terlalu lama dan mempercepatnya dengan mengurangi beban thread utama atau melewati beberapa tugas yang dilakukan di thread utama.

Untuk menganalisis rekaman aktivitas yang diambil dari pengujian AccelerateHeavyScreenBenchmark, ikuti langkah-langkah berikut:

  1. Buka rekaman aktivitas sistem yang diambil di langkah sebelumnya.
  2. Perbesar frame panjang pertama yang berisi inisialisasi UI setelah data dimuat. Isi frame terlihat sama seperti gambar berikut:

838787b87b14bbaf.png

Di rekaman aktivitas, Anda dapat melihat bahwa ada banyak hal yang terjadi dalam satu frame, yang dapat ditemukan di bagian Choreographer#doFrame. Dari gambar, terlihat bahwa bagian tugas terbesar berasal dari composable yang berisi bagian ImagePlaceholder yang memuat gambar berukuran besar.

Jangan memuat gambar ukuran besar di thread utama

Mungkin memuat gambar secara asinkron dari jaringan menggunakan salah satu library praktis seperti Coil atau Glidedapat dilakukan, tetapi bagaimana jika Anda ingin menampilkan gambar ukuran besar yang ada di aplikasi lokal?

Fungsi composable painterResource umum yang memuat gambar dari resource akan memuat gambar di thread utama selama komposisi. Artinya, ukuran gambar yang besar dapat memblokir thread utama dengan beberapa tugas.

Dalam kasus Anda, Anda dapat melihat masalah sebagai bagian dari placeholder gambar asinkron. Composable painterResource memuat gambar placeholder yang memerlukan waktu sekitar 23 md untuk dimuat.

c83d22c3870655a7.jpeg

Berikut beberapa cara untuk memperbaiki masalah ini:

  • Muat gambar secara asinkron.
  • Perkecil ukuran gambar agar dapat dimuat lebih cepat.
  • Gunakan vektor drawable yang diskalakan berdasarkan ukuran yang diperlukan.

Untuk memperbaiki masalah performa ini, ikuti langkah-langkah berikut:

  1. Buka file AccelerateHeavyScreen.kt.
  2. Temukan composable imagePlaceholder() yang memuat gambar. Gambar placeholder memiliki dimensi 1600x1600 px, yang jelas terlalu besar untuk ditampilkan.

53b34f358f2ff74.jpeg

  1. Ubah drawable menjadi R.drawable.placeholder_vector:
@Composable
fun imagePlaceholder() =
    trace("ImagePlaceholder") { painterResource(R.drawable.placeholder_vector) }
  1. Jalankan kembali uji AccelerateHeavyScreenBenchmark, yang membangun kembali aplikasi dan merekam aktivitas sistem lagi.
  2. Tarik rekaman aktivitas sistem ke dasbor Perfetto.

Selain itu, Anda dapat mendownload rekaman aktivitas:

  1. Cari bagian rekaman aktivitas ImagePlaceholder yang menunjukkan secara langsung bagian yang ditingkatkan.

abac4ae93d599864.png

  1. Perhatikan bahwa fungsi ImagePlaceholder tidak lagi memblokir thread utama.

8e76941fca0ae63c.jpeg

Sebagai solusi alternatif dalam aplikasi sebenarnya, mungkin bukan gambar placeholder yang menyebabkan masalah, tetapi karya seni. Dalam hal ini, Anda dapat menggunakan composable rememberAsyncImage Coil yang memuat composable secara asinkron. Solusi ini akan menampilkan ruang kosong hingga placeholder dimuat, jadi ketahui bahwa Anda mungkin perlu memiliki placeholder untuk jenis gambar ini.

Masih ada beberapa hal lain dengan performa kurang baik yang akan Anda atasi di langkah berikutnya.

6. Mengurangi beban operasi berat ke thread latar belakang

Jika terus menyelidiki item yang sama untuk menemukan masalah lain, Anda akan menemukan bagian dengan nama binder transaction, yang masing-masing memerlukan waktu sekitar 1 md.

5c08376b3824f33a.png

Bagian yang disebut binder transaction menunjukkan bahwa ada komunikasi antar-proses (IPC) yang terjadi antara proses Anda dan beberapa proses sistem. Hal ini adalah cara normal untuk mengambil beberapa informasi dari sistem, seperti mengambil layanan sistem.

Transaksi ini disertakan dalam banyak API yang berkomunikasi dengan sistem. Misalnya, ketika mengambil layanan sistem dengan getSystemService, mendaftarkan penerima siaran, atau meminta ConnectivityManager.

Sayangnya, transaksi ini tidak memberikan banyak informasi tentang apa yang diminta, jadi Anda harus menganalisis kode pada penggunaan API yang disebutkan dan kemudian menambahkan bagian trace kustom untuk memastikan bahwa bagian tersebut memang bermasalah.

Untuk meningkatkan transaksi binder, ikuti langkah-langkah berikut:

  1. Buka file AccelerateHeavyScreen.kt.
  2. Temukan composable PublishedText. Composable ini menggunakan format tanggal dan waktu sesuai zona waktu saat ini dan mendaftarkan objek BroadcastReceiver yang melacak perubahan zona waktu. Composable ini berisi variabel status currentTimeZone dengan zona waktu sistem default sebagai nilai awal, lalu DisposableEffect yang mendaftarkan penerima siaran untuk perubahan zona waktu. Terakhir, composable ini menampilkan tanggal dan waktu yang diformat dengan Text. DisposableEffect, yang merupakan pilihan bagus dalam skenario ini karena Anda memerlukan cara untuk membatalkan pendaftaran penerima siaran, yang dilakukan di lambda onDispose. Masalahnya, kode di dalam DisposableEffect memblokir thread utama:
@Composable
fun PublishedText(published: Instant, modifier: Modifier = Modifier) {
    val context = LocalContext.current
    var currentTimeZone: TimeZone by remember { mutableStateOf(TimeZone.currentSystemDefault()) }

    DisposableEffect(Unit) {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                currentTimeZone = TimeZone.currentSystemDefault()
            }
        }

        // TODO Codelab task: Wrap with a custom trace section
        context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED))

        onDispose { context.unregisterReceiver(receiver) }
    }

    Text(
        text = published.format(currentTimeZone),
        style = MaterialTheme.typography.labelMedium,
        modifier = modifier
    )
}
  1. Gabungkan context.registerReceiver dengan panggilan trace untuk memastikan bahwa memang inilah penyebab semua binder transactions:
trace("PublishDate.registerReceiver") {
    context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED))
}

Secara umum, kode yang berjalan selama itu di thread utama mungkin tidak menimbulkan banyak masalah, tetapi fakta bahwa transaksi ini berjalan untuk setiap item yang terlihat di layar mungkin menimbulkan masalah. Dengan asumsi ada enam item yang terlihat di layar, keenam item tersebut harus disusun dengan frame pertama. Panggilan ini saja dapat memakan waktu 12 md, yang hampir merupakan batas waktu keseluruhan untuk satu frame.

Untuk memperbaikinya, Anda perlu mengurangi beban pendaftaran siaran ke thread lain. Anda dapat melakukannya dengan coroutine.

  1. Dapatkan cakupan yang terikat dengan siklus proses composable val scope = rememberCoroutineScope().
  2. Di dalam efek, luncurkan coroutine pada operator yang bukan Dispatchers.Main. Misalnya, Dispatchers.IO dalam hal ini. Dengan cara ini, pendaftaran siaran tidak memblokir thread utama, tetapi currentTimeZone status sebenarnya disimpan di thread utama.
val scope = rememberCoroutineScope()

DisposableEffect(Unit) {
    val receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            currentTimeZone = TimeZone.currentSystemDefault()
        }
    }

    // launch the coroutine on Dispatchers.IO
    scope.launch(Dispatchers.IO) {
        trace("PublishDate.registerReceiver") {
            context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED))
        }
    }

    onDispose { context.unregisterReceiver(receiver) }
}

Ada satu langkah lagi untuk mengoptimalkan hal ini. Anda hanya memerlukan satu penerima siaran untuk semua item dalam daftar. Anda harus mengangkatnya!

Anda dapat mengangkatnya dan meneruskan parameter zona waktu ke bawah hierarki composable atau menggunakan komposisi lokal karena parameter tersebut tidak digunakan di banyak tempat pada UI.

Untuk tujuan codelab ini, Anda menyimpan penerima siaran sebagai bagian dari hierarki composable. Namun, di aplikasi sebenarnya, memisahkannya dalam lapisan data mungkin bermanfaat untuk mencegah pencemaran kode UI Anda.

  1. Tentukan komposisi lokal dengan zona waktu sistem default:
val LocalTimeZone = compositionLocalOf { TimeZone.currentSystemDefault() }
  1. Perbarui composable ProvideCurrentTimeZone yang menggunakan lambda content untuk menyediakan zona waktu saat ini:
@Composable
fun ProvideCurrentTimeZone(content: @Composable () -> Unit) {
    var currentTimeZone = TODO()

    CompositionLocalProvider(
        value = LocalTimeZone provides currentTimeZone,
        content = content,
    )
}
  1. Pindahkan DisposableEffect dari composable PublishedText ke composable baru untuk mengangkatnya dan ganti currentTimeZone dengan status dan efek samping:
@Composable
fun ProvideCurrentTimeZone(content: @Composable () -> Unit) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    var currentTimeZone: TimeZone by remember { mutableStateOf(TimeZone.currentSystemDefault()) }

    DisposableEffect(Unit) {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                currentTimeZone = TimeZone.currentSystemDefault()
            }
        }

        scope.launch(Dispatchers.IO) {
            trace("PublishDate.registerReceiver") {
                context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED))
            }
        }

        onDispose { context.unregisterReceiver(receiver) }
    }

    CompositionLocalProvider(
        value = LocalTimeZone provides currentTimeZone,
        content = content,
    )
}
  1. Gabungkan composable dengan ProvideCurrentTimeZone agar komposisi lokalnya valid. Anda dapat menggabungkan seluruh AccelerateHeavyScreen seperti yang ditampilkan dalam cuplikan berikut:
@Composable
fun AccelerateHeavyScreen(items: List<HeavyItem>, modifier: Modifier = Modifier) {
    // TODO: Codelab task: Wrap this with timezone provider
    ProvideCurrentTimeZone {
        Box(
            modifier = modifier
                .fillMaxSize()
                .padding(24.dp)
        ) {
            ScreenContent(items = items)

            if (items.isEmpty()) {
                CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
            }
        }
    }
}
  1. Ubah composable PublishedText agar hanya memuat fungsi pemformatan dasar dan membaca nilai komposisi lokal saat ini melalui LocalTimeZone.current:
@Composable
fun PublishedText(published: Instant, modifier: Modifier = Modifier) {
    Text(
        text = published.format(LocalTimeZone.current),
        style = MaterialTheme.typography.labelMedium,
        modifier = modifier
    )
}
  1. Jalankan kembali benchmark untuk membangun aplikasi.

Selain itu, Anda dapat mendownload rekaman aktivitas sistem dengan kode yang benar:

  1. Tarik file rekaman aktivitas ke dasbor Perfetto. Semua bagian binder transactions hilang dari thread utama.
  2. Cari nama bagian yang sama dengan langkah sebelumnya. Anda dapat menemukannya di salah satu thread lain yang dibuat oleh coroutine (DefaultDispatch):

87feee260f900a76.png

7. Menghapus subkomposisi yang tidak perlu

Anda telah memindahkan kode berat dari thread utama sehingga tidak lagi memblokir komposisi. Masih ada potensi peningkatan. Anda dapat menghapus beberapa overhead tidak perlu dalam bentuk composable LazyRow di setiap item.

Dalam contoh, setiap item berisi baris tag seperti yang ditandai dalam gambar berikut:

e821c86604d3e670.png

Baris ini diimplementasikan dengan composable LazyRow karena menulis dengan cara ini lebih mudah. Teruskan item ke composable LazyRow yang akan menangani sisanya:

@Composable
fun ItemTags(tags: List<String>, modifier: Modifier = Modifier) {
    // TODO: remove unnecessary lazy layout
    LazyRow(
        modifier = modifier
            .padding(4.dp)
            .fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(2.dp)
    ) {
        items(tags) { ItemTag(it) }
    }
}

Masalahnya adalah, meskipun tata letak Lazy unggul dalam tata letak tempat Anda memiliki lebih banyak item daripada ukuran yang dibatasi, muncul sejumlah biaya tambahan yang tidak perlu jika komposisi lambat tidak diperlukan.

Mengingat sifat composable Lazy, yang menggunakan composable SubcomposeLayout, composable tersebut selalu ditampilkan sebagai beberapa bagian tugas. Tugas pertama adalah container dan tugas kedua adalah item yang saat ini terlihat di layar. Anda juga dapat menemukan rekaman aktivitas compose:lazylist:prefetch dalam rekaman aktivitas sistem, yang menunjukkan bahwa item tambahan masuk ke area tampilan. Oleh karena itu, item tersebut diambil datanya agar siap lebih awal.

b3dc3662b5885a2e.jpeg

Untuk menentukan durasi waktu yang diperlukan dalam kasus Anda, buka file rekaman aktivitas yang sama. Anda dapat melihat bahwa ada bagian yang terlepas dari item induk. Setiap item terdiri dari item sebenarnya yang sedang disusun dan kemudian item tag. Dengan cara ini, setiap item menghasilkan waktu komposisi sekitar 2,5 milidetik, yang jika dikalikan dengan jumlah item yang terlihat, akan menghasilkan banyak bagian tugas.

a204721c80497e0f.jpeg

Untuk memperbaiki hal ini, ikuti langkah-langkah berikut:

  1. Buka file AccelerateHeavyScreen.kt dan temukan composable ItemTags.
  2. Ubah implementasi LazyRow menjadi composable Row yang melakukan iterasi pada daftar tags seperti dalam cuplikan berikut:
@Composable
fun ItemTags(tags: List<String>, modifier: Modifier = Modifier) {
    Row(
        modifier = modifier
            .padding(4.dp)
            .fillMaxWidth()
        horizontalArrangement = Arrangement.spacedBy(2.dp)
    ) {
        tags.forEach { ItemTag(it) }
    }
}
  1. Jalankan kembali benchmark yang juga akan membangun aplikasi.
  2. Opsional: Download pelacakan sistem dengan kode yang benar:

  1. Temukan bagian ItemTag, perhatikan bahwa waktu yang dibutuhkan lebih sedikit, dan menggunakan bagian root Compose:recompose yang sama.

219cd2e961defd1.jpeg

Situasi serupa mungkin terjadi pada container lain yang menggunakan composable SubcomposeLayout, misalnya composable BoxWithConstraints. Hal ini dapat mencakup pembuatan item di seluruh bagian Compose:recompose, yang mungkin tidak ditampilkan secara langsung sebagai frame yang mengalami jank, tetapi dapat terlihat oleh pengguna. Jika memungkinkan, cobalah untuk menghindari composable BoxWithConstraints di setiap item karena mungkin hanya diperlukan saat Anda membuat UI yang berbeda berdasarkan ruang yang tersedia.

Di bagian ini, Anda mempelajari cara memperbaiki komposisi yang memakan waktu terlalu lama.

8. Membandingkan hasil dengan benchmark awal

Sekarang setelah selesai mengoptimalkan performa layar, Anda harus membandingkan hasil benchmark dengan hasil awal.

  1. Buka Test History di panel run Android Studio 667294bf641c8fc2.png
  2. Pilih proses terlama yang terkait dengan benchmark awal tanpa perubahan apa pun dan bandingkan metrik frameDurationCpuMs dan frameOverrunMs. Anda akan melihat hasil yang serupa dengan tabel berikut:

Sebelum

AccelerateHeavyScreenBenchmark_accelerateHeavyScreenCompilationFull
ImagePlaceholderCount               min  20.0,   median  20.0,   max  20.0
ImagePlaceholderMs                  min  22.9,   median  22.9,   max  22.9
ItemTagCount                        min  80.0,   median  80.0,   max  80.0
ItemTagMs                           min   3.2,   median   3.2,   max   3.2
PublishDate.registerReceiverCount   min   1.0,   median   1.0,   max   1.0
PublishDate.registerReceiverMs      min   1.9,   median   1.9,   max   1.9
frameDurationCpuMs                  P50    5.4,   P90    9.0,   P95   10.5,   P99   57.5
frameOverrunMs                      P50   -4.2,   P90   -3.5,   P95   -3.2,   P99   74.9
Traces: Iteration 0
  1. Pilih run terbaru yang terkait dengan benchmark serta semua pengoptimalannya. Anda akan melihat hasil yang serupa dengan tabel berikut:

Setelah

AccelerateHeavyScreenBenchmark_accelerateHeavyScreenCompilationFull
ImagePlaceholderCount               min  20.0,   median  20.0,   max  20.0
ImagePlaceholderMs                  min   2.9,   median   2.9,   max   2.9
ItemTagCount                        min  80.0,   median  80.0,   max  80.0
ItemTagMs                           min   3.4,   median   3.4,   max   3.4
PublishDate.registerReceiverCount   min   1.0,   median   1.0,   max   1.0
PublishDate.registerReceiverMs      min   1.1,   median   1.1,   max   1.1
frameDurationCpuMs                  P50    4.3,   P90    7.7,   P95    8.8,   P99   33.1
frameOverrunMs                      P50  -11.4,   P90   -8.3,   P95   -7.3,   P99   41.8
Traces: Iteration 0

Jika memeriksa baris frameOverrunMs secara khusus, Anda dapat melihat bahwa semua persentilnya meningkat:

P50

P90

P95

P99

sebelum

-4,2

-3,5

-3,2

74,9

setelah

-11,4

-8,3

-7,3

41,8

peningkatan

171%

137%

128%

44%

Di bagian berikutnya, Anda akan mempelajari cara memperbaiki komposisi yang terlalu sering terjadi.

9. Mencegah rekomposisi yang tidak perlu

Compose memiliki 3 tahap:

  • Komposisi menentukan apa yang akan ditampilkan dengan membuat hierarki composable.
  • Tata Letak mengambil hierarki tersebut dan menentukan tempat composable akan muncul di layar.
  • Gambar menggambar composable di layar.

Urutan tahap ini umumnya sama, memungkinkan data mengalir dalam satu arah dari komposisi ke tata letak hingga gambar untuk menghasilkan frame UI.

2147ae29192a1556.png

BoxWithConstraints, tata letak lambat (misalnya LazyColumn atau LazyVerticalGrid), dan semua tata letak berdasarkan composable SubcomposeLayout adalah pengecualian penting, karena komposisi turunannya bergantung pada tahap tata letak induknya.

Secara umum, komposisi adalah tahap yang paling mahal untuk dijalankan karena ada banyak tugas yang harus dilakukan dan Anda juga dapat menyebabkan rekomposisi pada composable lain yang tidak terkait.

Sebagian besar frame berisi ketiga tahap tersebut, tetapi sebenarnya Compose dapat melewati seluruh tahap jika tidak ada tugas yang harus dilakukan. Anda dapat memanfaatkan kemampuan ini untuk meningkatkan performa aplikasi Anda.

Menunda tahap komposisi dengan pengubah lambda

Fungsi composable dijalankan dalam tahap komposisi. Untuk mengizinkan kode dijalankan pada waktu berbeda, Anda dapat menyediakannya sebagai fungsi lambda.

Untuk melakukannya, ikuti langkah berikut:

  1. Buka file PhasesComposeLogo.kt.
  2. Buka layar Task 2 dalam aplikasi. Anda akan melihat logo yang memantul dari tepi layar.
  3. Buka Layout Inspector dan periksa Recomposition counts. Anda akan melihat jumlah rekomposisi yang meningkat pesat.

a9e52e8ccf0d31c1.png

  1. Opsional: Temukan file PhasesComposeLogoBenchmark.kt dan jalankan untuk mengambil rekaman aktivitas sistem guna melihat komposisi bagian rekaman aktivitas PhasesComposeLogo yang terjadi di setiap frame. Rekomposisi ditampilkan dalam rekaman aktivitas sebagai bagian berulang dengan nama yang sama.

4b6e72578c89b2c1.jpeg 7036a895a31138d3.png

  1. Jika perlu, tutup profiler dan Layout Inspector, lalu kembali ke kode. Anda akan melihat composable PhaseComposeLogo yang terlihat seperti berikut:
@Composable
fun PhasesComposeLogo() = trace("PhasesComposeLogo") {
    val logo = painterResource(id = R.drawable.compose_logo)
    var size by remember { mutableStateOf(IntSize.Zero) }
    val logoPosition by logoPosition(size = size, logoSize = logo.intrinsicSize)

    Box(
        modifier = Modifier
            .fillMaxSize()
            .onPlaced {
                size = it.size
            }
    ) {
        with(LocalDensity.current) {
            Image(
                painter = logo,
                contentDescription = "logo",
                modifier = Modifier.offset(logoPosition.x.toDp(), logoPosition.y.toDp())
            )
        }
    }
}

Composable logoPosition berisi logika yang mengubah statusnya pada setiap frame dan terlihat sebagai berikut:

@Composable
fun logoPosition(size: IntSize, logoSize: Size): State<IntOffset> =
    produceState(initialValue = IntOffset.Zero, size, logoSize) {
        if (size == IntSize.Zero) {
            this.value = IntOffset.Zero
            return@produceState
        }

        var xDirection = 1
        var yDirection = 1

        while (true) {
            withFrameMillis {
                value += IntOffset(x = MOVE_SPEED * xDirection, y = MOVE_SPEED * yDirection)

                if (value.x <= 0 || value.x >= size.width - logoSize.width) {
                    xDirection *= -1
                }

                if (value.y <= 0 || value.y >= size.height - logoSize.height) {
                    yDirection *= -1
                }
            }
        }
    }

Status sedang dibaca dalam composable PhasesComposeLogo dengan pengubah Modifier.offset(x.dp, y.dp), yang berarti status dibaca dalam komposisi.

Pengubah ini adalah penyebab aplikasi merekomposisi di setiap frame animasi ini. Dalam hal ini, ada alternatif sederhana: pengubah Offset berbasis lambda.

  1. Perbarui composable Image untuk menggunakan pengubah Modifier.offset, yang menerima lambda yang menampilkan objek IntOffset seperti dalam cuplikan berikut:
Image(
  painter = logo,
  contentDescription = "logo",
  modifier = Modifier.offset { IntOffset(logoPosition.x,  logoPosition.y) }
)
  1. Jalankan kembali aplikasi dan periksa Layout Inspector. Anda akan melihat bahwa animasi tidak lagi menghasilkan rekomposisi apa pun.

Ingat, Anda tidak perlu melakukan rekomposisi hanya untuk menyesuaikan tata letak layar, terutama saat scrolling, yang menyebabkan frame mengalami jank. Rekomposisi yang terjadi selama scrolling hampir selalu tidak diperlukan dan harus dihindari.

Pengubah lambda lainnya

Pengubah Modifier.offset bukan satu-satunya pengubah dengan versi lambda. Dalam tabel berikut, Anda dapat melihat pengubah umum yang akan melakukan rekomposisi setiap saat, yang dapat diganti dengan alternatif yang ditangguhkan ketika meneruskan nilai status yang sering berubah:

Pengubah umum

Alternatif yang ditangguhkan

.background(color)

.drawBehind { drawRect(color) }

.offset(0.dp, y)

.offset { IntOffset(0, y.roundToPx()) }

.alpha(a).rotate(r).scale(s)

.graphicsLayer { alpha = a; rotationZ = r; scaleX = s; scaleY = s}

10. Menunda tahap Compose dengan tata letak kustom

Menggunakan pengubah berbasis lambda biasanya adalah cara termudah untuk menghindari pembatalan validasi komposisi, tetapi terkadang tidak ada satu pun pengubah berbasis lambda yang dapat melakukan apa yang Anda perlukan. Dalam kasus ini, Anda dapat langsung mengimplementasikan tata letak kustom atau bahkan composable Canvas dan langsung menuju tahap gambar. Pembacaan status Compose yang dilakukan di dalam tata letak kustom hanya akan membuat tata letak tidak valid dan melewati rekomposisi. Sebagai panduan umum, jika hanya ingin menyesuaikan tata letak atau ukuran, tetapi tidak menambahkan atau menghapus composable, Anda dapat memperoleh efek tanpa membatalkan validasi komposisi sama sekali.

Untuk melakukannya, ikuti langkah berikut:

  1. Buka file PhasesAnimatedShape.kt, lalu jalankan aplikasi.
  2. Buka layar Task 3. Layar ini berisi bentuk yang berubah ukurannya saat Anda mengklik tombol. Nilai ukuran dianimasikan dengan Animation API Compose animateDpAsState.

51dc23231ebd5f1a.gif

  1. Buka Layout Inspector.
  2. Klik Toggle size.
  3. Perhatikan bahwa bentuk direkomposisi di setiap frame animasi.

63d597a98fca1133.png

Composable MyShape menggunakan objek size sebagai parameter, yang merupakan pembacaan status. Artinya, ketika objek size berubah, composable PhasesAnimatedShape (cakupan rekomposisi terdekat) akan direkomposisi. Selanjutnya, composable MyShape direkomposisi karena inputnya telah berubah.

Untuk melewati rekomposisi, ikuti langkah-langkah berikut:

  1. Ubah parameter size menjadi fungsi lambda sehingga perubahan ukuran tidak secara langsung merekomposisi composable MyShape:
@Composable
fun MyShape(
    size: () -> Dp,
    modifier: Modifier = Modifier
) {
  // ...
  1. Perbarui situs panggilan di composable PhasesAnimatedShape untuk menggunakan fungsi lambda:
MyShape(size = { size }, modifier = Modifier.align(Alignment.Center))

Mengubah parameter size menjadi lambda akan menunda pembacaan status. Sekarang hal itu terjadi saat lambda dipanggil.

  1. Ubah isi composable MyShape menjadi berikut:
Box(
    modifier = modifier
        .background(color = Purple80, shape = CircleShape)
        .layout { measurable, _ ->
            val sizePx = size()
                .roundToPx()
                .coerceAtLeast(0)

            val constraints = Constraints.fixed(
                width = sizePx,
                height = sizePx,
            )

            val placeable = measurable.measure(constraints)
            layout(sizePx, sizePx) {
                placeable.place(0, 0)
            }
        }
)

Pada baris pertama pengubah layout mengukur lambda, Anda dapat melihat bahwa lambda size dipanggil. Karena ada di dalam pengubah layout, hal ini hanya membuat tata letak tidak valid, bukan komposisi.

  1. Jalankan kembali aplikasi, buka layar Task 3, lalu buka Layout Inspector.
  2. Klik Toggle Size lalu amati bahwa ukuran bentuk yang dianimasikan sama seperti sebelumnya, tetapi composable MyShape tidak mengalami rekomposisi.

11. Mencegah rekomposisi dengan class stabil

Compose menghasilkan kode yang dapat melewati eksekusi composable jika semua parameter inputnya stabil dan tidak berubah dari komposisi sebelumnya. Suatu jenis dikatakan stabil jika tidak dapat diubah atau jika mesin Compose dapat mengetahui apakah nilainya telah berubah saat rekomposisi.

Jika tidak yakin apakah suatu composable stabil, mesin Compose akan menganggapnya tidak stabil dan tidak akan menghasilkan logika kode untuk melewati rekomposisi, yang berarti bahwa composable akan direkomposisi setiap saat. Hal ini dapat terjadi ketika class bukan jenis primitif dan salah satu situasi berikut terjadi:

  • Class dapat berubah Misalnya, class berisi properti yang dapat berubah.
  • Class ditentukan dalam modul Gradle yang tidak menggunakan Compose. Class tidak memiliki dependensi pada compiler Compose.
  • Class berisi properti tidak stabil.

Perilaku ini mungkin tidak diinginkan dalam beberapa kasus, sehingga menyebabkan masalah performa dan dapat diubah ketika Anda melakukan hal berikut:

  • Mengaktifkan mode skipping kuat.
  • Menganotasi parameter dengan anotasi @Immutable atau @Stable.
  • Menambahkan class ke file konfigurasi stabilitas.

Untuk informasi selengkapnya mengenai stabilitas, baca dokumentasi ini.

Dalam tugas ini, Anda memiliki daftar item yang dapat ditambahkan, dihapus, atau diperiksa, dan Anda perlu memastikan bahwa item tidak direkomposisi jika tidak diperlukan. Ada dua jenis item, yaitu item yang dibuat ulang setiap saat dan yang tidak.

Item yang dibuat ulang setiap saat bertindak sebagai simulasi kasus penggunaan nyata ketika data berasal dari database lokal (misalnya Room atau sqlDelight) atau sumber data jarak jauh (seperti permintaan API atau entitas Firestore), dan menampilkan instance baru dari objek setiap kali ada perubahan.

Beberapa composable dilengkapi pengubah Modifier.recomposeHighlighter(), yang dapat Anda temukan di repositori GitHub kami. Pengubah ini menampilkan batas setiap kali composable direkomposisi dan dapat berfungsi sebagai solusi sementara alternatif untuk Layout Inspector.

127f2e4a2fc1a381.gif

Mengaktifkan mode skipping kuat

Compiler Jetpack Compose 1.5.4 dan yang lebih baru dilengkapi dengan opsi untuk mengaktifkan mode skipping kuat, yang berarti bahwa composable dengan parameter tidak stabil dapat menghasilkan kode skipping. Mode ini diharapkan dapat secara drastis mengurangi jumlah composable yang tidak dapat dilewati dalam proyek Anda, sehingga meningkatkan performa tanpa perubahan kode apa pun.

Untuk parameter yang tidak stabil, logika skipping dibandingkan dengan persamaan instance, yang berarti parameter tersebut akan dilewati jika instance yang sama diteruskan ke composable seperti pada kasus sebelumnya. Sebaliknya, parameter stabil menggunakan persamaan struktural (dengan memanggil metode Object.equals()) untuk menentukan logika skipping.

Selain logika skipping, mode skipping kuat juga otomatis mengingat lambda yang ada di dalam fungsi composable. Artinya, Anda tidak memerlukan panggilan remember untuk menggabungkan fungsi lambda, misalnya fungsi yang memanggil metode ViewModel.

Mode skipping kuat dapat diaktifkan berdasarkan modul Gradle.

Untuk mengaktifkannya, ikuti langkah-langkah berikut:

  1. Buka file build.gradle.kts aplikasi.
  2. Perbarui blok composeCompiler dengan cuplikan berikut:
composeCompiler {
    // Not required in Kotlin 2.0 final release
    suppressKotlinVersionCompatibilityCheck = "2.0.0-RC1"

    // This settings enables strong-skipping mode for all module in this project.
    // As an effect, Compose can skip a composable even if it's unstable by comparing it's instance equality (===).
    enableExperimentalStrongSkippingMode = true
}

Tindakan ini menambahkan argumen compiler experimentalStrongSkipping ke modul Gradle.

  1. Klik b8a9619d159a7d8e.png Sync project with Gradle files.
  2. Bangun ulang project.
  3. Buka layar Task 5, lalu perhatikan bahwa item yang menggunakan persamaan struktural ditandai dengan ikon EQU dan tidak direkomposisi saat Anda berinteraksi dengan daftar item.

1de2fd2c42a1f04f.gif

Namun, jenis item lainnya masih direkomposisi. Anda akan memperbaikinya di langkah berikutnya.

Memperbaiki stabilitas dengan anotasi

Seperti disebutkan sebelumnya, jika mode skipping kuat diaktifkan, composable akan melewati eksekusinya jika parameter memiliki instance yang sama seperti komposisi sebelumnya. Namun, hal ini tidak berlaku pada situasi ketika setiap perubahan akan menghasilkan instance baru dari class yang tidak stabil.

Dalam situasi Anda, class StabilityItem tidak stabil karena berisi properti LocalDateTime yang tidak stabil.

Untuk memperbaiki stabilitas class ini, ikuti langkah-langkah berikut:

  1. Buka file StabilityViewModel.kt.
  2. Temukan class StabilityItem dan beri anotasi dengan anotasi @Immutable:
// TODO Codelab task: make this class Stable
@Immutable
data class StabilityItem(
    val id: Int,
    val type: StabilityItemType,
    val name: String,
    val checked: Boolean,
    val created: LocalDateTime
)
  1. Bangun kembali aplikasi.
  2. Buka layar Task 5 layar dan perhatikan bahwa tidak ada item daftar yang direkomposisi.

938aad77b78f7590.gif

Class ini sekarang menggunakan persamaan struktural untuk memeriksa apakah komposisinya berubah dari sebelumnya dan tidak melakukan rekomposisi.

Masih ada composable yang merujuk pada tanggal perubahan terakhir, yang terus melakukan rekomposisi terlepas dari apa yang Anda lakukan hingga saat ini.

Memperbaiki stabilitas dengan file konfigurasi

Pendekatan sebelumnya berfungsi dengan baik untuk class yang merupakan bagian dari codebase Anda. Namun, class yang berada di luar jangkauan Anda, seperti class dari library pihak ketiga atau class library standar, tidak dapat diedit.

Anda dapat mengaktifkan file konfigurasi stabilitas yang menggunakan class (dengan kemungkinan karakter pengganti) yang akan dianggap stabil.

Untuk mengaktifkannya, ikuti langkah-langkah berikut:

  1. Buka file build.gradle.kts aplikasi.
  2. Tambahkan opsi stabilityConfigurationFile ke blok composeCompiler:
composeCompiler {
    ...

    stabilityConfigurationFile = project.rootDir.resolve("stability_config.conf")
}
  1. Sinkronkan project dengan file Gradle.
  2. Buka file stability_config.conf di folder root project ini di samping file README.md.
  3. Tambahkan cuplikan berikut:
// TODO Codelab task: Make a java.time.LocalDate class stable.
java.time.LocalDate
  1. Bangun kembali aplikasi. Jika tanggalnya tetap sama, class LocalDateTime tidak akan menyebabkan composable Latest change was YYYY-MM-DD direkomposisi.

332ab0b2c91617f2.gif

Di aplikasi, Anda dapat memperluas file agar berisi pola, sehingga Anda tidak perlu menulis semua class yang harus dianggap stabil. Jadi dalam kasus Anda, Anda dapat menggunakan karakter pengganti java.time.*, yang akan memperlakukan semua class dalam paket sebagai stabil, seperti Instant, LocalDateTime, ZoneId, dan class lain dari java time.

Dengan mengikuti langkah-langkah ini, tidak ada item di layar ini yang direkomposisi kecuali item yang ditambahkan atau melakukan interaksi, yang merupakan perilaku yang diharapkan.

12. Selamat

Selamat, Anda telah mengoptimalkan performa aplikasi Compose! Meskipun hanya menampilkan sebagian kecil masalah performa yang mungkin Anda temui di aplikasi, Anda telah mempelajari cara melihat potensi masalah lainnya dan cara memperbaikinya.

Apa selanjutnya?

Jika Anda belum membuat Profil Dasar Pengukuran untuk aplikasi, kami sangat menyarankan Anda melakukannya.

Anda dapat mengikuti codelab Meningkatkan performa aplikasi dengan Profil Dasar Pengukuran. Jika Anda ingin informasi lebih lanjut tentang menyiapkan benchmark, lihat codelab Memeriksa performa aplikasi dengan Macrobenchmark.

Pelajari lebih lanjut