Panduan ini membahas harapan pengguna tentang status UI, dan opsi yang tersedia untuk mempertahankan status.
Menyimpan dan memulihkan status UI aktivitas dengan cepat setelah sistem menghancurkan aktivitas atau aplikasi sangat penting untuk pengalaman pengguna yang baik. Pengguna berharap status UI tetap sama, tetapi sistem mungkin menghancurkan aktivitas dan statusnya yang tersimpan.
Untuk menjembatani kesenjangan antara harapan pengguna dan perilaku sistem, gunakan kombinasi metode berikut:
- Objek
ViewModel
. - Status instance yang tersimpan dalam konteks berikut:
- Jetpack Compose:
rememberSaveable
. - View:
onSaveInstanceState()
API. - ViewModel:
SavedStateHandle
.
- Jetpack Compose:
- Penyimpanan lokal untuk mempertahankan status UI selama transisi aplikasi dan aktivitas.
Solusi yang optimal bergantung pada kompleksitas data UI, kasus penggunaan aplikasi, dan keseimbangan antara kecepatan akses data dan penggunaan memori.
Pastikan aplikasi Anda memenuhi harapan pengguna dan menawarkan antarmuka yang cepat dan responsif. Hindari penundaan saat memuat data ke dalam UI, terutama setelah perubahan konfigurasi umum seperti rotasi.
Ekspektasi pengguna dan perilaku sistem
Bergantung pada tindakan yang dilakukan oleh pengguna, mereka mengharapkan status aktivitas dihapus atau status dipertahankan. Pada beberapa kasus, sistem otomatis melakukan tindakan yang diharapkan oleh pengguna. Pada kasus lainnya, sistem melakukan kebalikan dari tindakan yang diharapkan oleh pengguna.
Penutupan status UI yang diinisialisasi pengguna
Pengguna ingin bahwa saat mereka memulai aktivitas, status UI sementara dari aktivitas tersebut akan tetap sama hingga pengguna sepenuhnya menutup aktivitas. Pengguna dapat menutup aktivitas sepenuhnya dengan melakukan hal berikut:
- Menggeser aktivitas di luar layar Ringkasan (Terbaru).
- Menutup atau memaksa keluar aplikasi dari layar Setelan.
- Memulai ulang perangkat.
- Menyelesaikan beberapa jenis tindakan "penyelesaian" (yang didukung oleh
Activity.finish()
).
Asumsi pengguna dalam kasus penutupan yang menyeluruh ini adalah bahwa pengguna menutup aktivitas secara permanen, dan jika aktivitas dibuka kembali, pengguna berharap aktivitas dimulai dari status bersih. Perilaku sistem yang mendasari skenario penutupan ini sesuai dengan ekspektasi pengguna - instance aktivitas akan dihancurkan dan dihapus dari memori, beserta status apa saja yang tersimpan di dalamnya dan catatan status instance tersimpan apa saja yang terkait dengan aktivitas.
Ada beberapa pengecualian aturan ini terkait penutupan menyeluruh—misalnya pengguna mungkin berharap browser membawa mereka ke halaman web yang mereka lihat sebelum keluar dari browser menggunakan tombol kembali.
Penutupan status UI yang diinisialisasi oleh sistem
Pengguna berharap status UI aktivitas tetap sama selama perubahan konfigurasi, seperti rotasi atau beralih ke mode multi-aplikasi. Akan tetapi, secara default sistem menghancurkan aktivitas saat perubahan konfigurasi tersebut terjadi, dengan menghapus status UI apa saja yang tersimpan di instance aktivitas. Untuk mempelajari konfigurasi perangkat lebih lanjut, lihat halaman Referensi konfigurasi. Perlu diperhatikan, Anda dapat (meski tidak direkomendasikan) mengganti perilaku default untuk perubahan konfigurasi. Lihat Menangani sendiri perubahan konfigurasi untuk detail selengkapnya.
Pengguna juga berharap status UI aktivitas tetap sama jika pengguna untuk sementara beralih ke aplikasi lain, lalu kembali ke aplikasi Anda nanti. Misalnya, pengguna melakukan penelusuran pada aktivitas penelusuran Anda, lalu menekan tombol layar utama atau menjawab panggilan telepon - saat kembali ke aktivitas penelusuran, pengguna berharap menemukan kata kunci penelusuran dan hasilnya tetap ada di sana, persis seperti sebelumnya.
Dalam skenario ini, aplikasi ditempatkan di latar belakang, sistem melakukan yang terbaik untuk mempertahankan proses aplikasi dalam memori. Akan tetapi, sistem dapat menghancurkan proses aplikasi saat pengguna berinteraksi dengan aplikasi lain. Dalam kasus tersebut, instance aktivitas dihancurkan, beserta status apa saja yang tersimpan di dalamnya. Saat pengguna meluncurkan ulang aplikasi, aktivitas secara tidak terduga berada dalam status bersih. Untuk mempelajari penghentian proses lebih lanjut, lihat Proses dan Siklus Proses Aplikasi.
Opsi untuk mempertahankan status UI
Jika ekspektasi pengguna tentang status UI tidak sesuai dengan perilaku sistem default, Anda harus menyimpan dan memulihkan status UI pengguna untuk memastikan bahwa penghancuran yang dimulai oleh sistem bersifat transparan kepada pengguna.
Tiap opsi untuk mempertahankan status UI bervariasi di sepanjang dimensi berikut yang memengaruhi pengalaman pengguna:
ViewModel | Status instance tersimpan | Penyimpanan persisten | |
---|---|---|---|
Lokasi penyimpanan | dalam memori | dalam memori | pada disk atau jaringan |
Tetap bertahan saat terjadi perubahan konfigurasi | Ya | Ya | Ya |
Tetap bertahan saat terjadi penghentian proses yang dimulai oleh sistem | Tidak | Ya | Ya |
Tetap bertahan saat terjadi penutupan aktivitas menyeluruh pengguna/onFinish() | Tidak | Tidak | Ya |
Keterbatasan data | objek kompleks tidak masalah, tetapi ruang dibatasi oleh memori yang tersedia | hanya untuk jenis primitif dan objek kecil sederhana seperti String | hanya dibatasi oleh kapasitas disk atau biaya/waktu pengambilan dari resource jaringan |
Waktu baca/tulis | cepat (hanya akses memori) | lambat (memerlukan serialisasi/deserialisasi) | lambat (memerlukan akses disk atau transaksi jaringan) |
Menggunakan ViewModel untuk menangani perubahan konfigurasi
ViewModel ideal untuk menyimpan dan mengelola data terkait UI saat pengguna aktif menggunakan aplikasi. Ini memungkinkan akses cepat ke data UI dan membantu Anda menghindari pengambilan kembali data dari jaringan atau disk selama rotasi, perubahan ukuran jendela, dan perubahan konfigurasi lainnya yang umum terjadi. Untuk mempelajari cara mengimplementasikan ViewModel, lihat panduan ViewModel.
ViewModel menyimpan data dalam memori, yang artinya lebih murah diambil daripada data dari disk atau jaringan. ViewModel terkait dengan aktivitas (atau beberapa pemilik siklus proses lainnya) - dan tetap berada dalam memori selama perubahan konfigurasi dan sistem secara otomatis mengaitkan ViewModel dengan instance aktivitas baru yang dihasilkan dari perubahan konfigurasi.
ViewModel otomatis dihancurkan oleh sistem saat pengguna logout
dari aktivitas atau fragmen Anda atau jika Anda memanggil finish()
, yang berarti status
akan dihapus seperti yang diharapkan pengguna dalam skenario ini.
Tidak seperti status instance yang tersimpan, ViewModel dihancurkan selama
penghentian proses yang dimulai oleh sistem. Untuk memuat ulang data setelah penghentian yang dimulai oleh sistem di
ViewModel, gunakan SavedStateHandle
API. Atau, jika data terkait
dengan UI dan tidak perlu disimpan di ViewModel, gunakan
onSaveInstanceState()
dalam sistem View atau rememberSaveable
di Jetpack
Compose. Jika datanya adalah data aplikasi, mungkin lebih baik pertahankan
data ke disk.
Jika Anda telah menyiapkan solusi dalam memori untuk menyimpan status UI selama perubahan konfigurasi, Anda mungkin tidak perlu menggunakan ViewModel.
Menggunakan Status instance tersimpan sebagai cadangan untuk menangani penghentian proses yang dimulai oleh sistem
Callback onSaveInstanceState()
di sistem View,
rememberSaveable
di Jetpack Compose, dan SavedStateHandle
di
ViewModel menyimpan data yang diperlukan untuk memuat ulang status pengontrol UI, seperti
aktivitas atau fragmen, jika sistem menghancurkan dan membuat ulang pengontrol
tersebut. Untuk mempelajari cara mengimplementasikan status instance yang tersimpan menggunakan
onSaveInstanceState
, lihat Menyimpan dan memulihkan status aktivitas di
panduan Siklus Proses Aktivitas.
Paket status instance yang tersimpan tetap bertahan saat terjadi perubahan konfigurasi dan penghentian proses, tetapi dibatasi oleh penyimpanan dan kecepatan karena API yang berbeda melakukan serialisasi data. Serialisasi dapat menghabiskan banyak memori jika objek yang diserialisasi rumit. Karena proses ini terjadi pada thread utama selama perubahan konfigurasi, serialisasi yang berjalan lama dapat menyebabkan penurunan frame dan tampilan visual yang patah-patah.
Jangan gunakan status instance tersimpan untuk menyimpan data dalam jumlah besar, seperti bitmap,
atau struktur data kompleks yang memerlukan serialisasi atau
deserialisasi yang panjang. Simpan hanya jenis primitif dan objek yang kecil
dan sederhana seperti String
. Oleh karena itu, gunakan status instance tersimpan untuk menyimpan data dalam jumlah minimum
yang diperlukan, seperti ID, untuk membuat ulang data yang diperlukan guna memulihkan UI
kembali ke status sebelumnya jika mekanisme persistensi lainnya gagal. Sebagian besar
aplikasi harus menerapkannya untuk menangani proses penghentian yang dimulai oleh sistem.
Bergantung pada kasus penggunaan aplikasi, Anda mungkin tidak perlu menggunakan status instance yang disimpan sama sekali. Misalnya, browser mungkin membawa pengguna kembali ke halaman web yang mereka lihat sebelum keluar dari browser. Jika aktivitas Anda menunjukkan perilaku seperti ini, Anda dapat mengabaikannya menggunakan status instance tersimpan dan mempertahankan semuanya secara lokal.
Selain itu, saat Anda membuka aktivitas dari suatu intent, paket yang berisikan tambahan dikirimkan ke aktivitas saat perubahan konfigurasi dan saat sistem memulihkan aktivitas. Jika sepotong data status UI seperti kueri penelusuran diteruskan sebagai tambahan intent saat aktivitas diluncurkan, Anda dapat menggunakan paket tambahan, bukan paket status instance tersimpan. Untuk mempelajari tambahan intent lebih lanjut, lihat Intent dan Filter Intent.
Dalam salah satu skenario ini, Anda tetap harus menggunakan ViewModel
untuk menghindari
pemborosan siklus pemuatan ulang data dari database selama perubahan konfigurasi.
Jika ingin mempertahankan data UI yang sederhana dan ringan, Anda dapat menggunakan API status instance tersimpan saja untuk mempertahankan data status.
Mengaitkan ke status tersimpan menggunakan SavedStateRegistry
Mulai dari Fragment 1.1.0 atau Activity
1.0.0 dependensi transitifnya, pengontrol UI, seperti Activity
atau Fragment
, mengimplementasikan
SavedStateRegistryOwner
dan memberikan SavedStateRegistry
yang
terikat dengan pengontrol tersebut. SavedStateRegistry
memungkinkan komponen agar terkait ke
status tersimpan pengontrol UI untuk memakainya atau berkontribusi untuknya. Misalnya,
modul Status Tersimpan untuk ViewModel menggunakan SavedStateRegistry
untuk membuat
SavedStateHandle
dan memberikannya ke objek ViewModel
Anda. Anda dapat mengambil
SavedStateRegistry
dari dalam pengontrol UI dengan memanggil
getSavedStateRegistry()
.
Komponen yang berkontribusi pada status tersimpan harus mengimplementasikan
SavedStateRegistry.SavedStateProvider
, yang menentukan satu metode
yang disebut saveState()
. Metode saveState()
memungkinkan komponen Anda
menampilkan Bundle
yang berisi status apa pun yang harus disimpan dari komponen tersebut.
SavedStateRegistry
memanggil metode ini selama fase status penyimpanan
siklus proses pengontrol UI.
Kotlin
class SearchManager : SavedStateRegistry.SavedStateProvider { companion object { private const val QUERY = "query" } private val query: String? = null ... override fun saveState(): Bundle { return bundleOf(QUERY to query) } }
Java
class SearchManager implements SavedStateRegistry.SavedStateProvider { private static String QUERY = "query"; private String query = null; ... @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); bundle.putString(QUERY, query); return bundle; } }
Untuk mendaftarkan SavedStateProvider
, panggil registerSavedStateProvider()
di
SavedStateRegistry
, dengan meneruskan kunci untuk dikaitkan dengan data penyedia
serta penyedia. Data yang disimpan sebelumnya untuk penyedia dapat
diambil dari status yang disimpan dengan memanggil consumeRestoredStateForKey()
pada SavedStateRegistry
, yang meneruskan kunci yang terkait dengan
data penyedia.
Dalam Activity
atau Fragment
, Anda dapat mendaftarkan SavedStateProvider
dalam
onCreate()
setelah memanggil super.onCreate()
. Atau, Anda dapat menyetel
LifecycleObserver
di SavedStateRegistryOwner
, yang mengimplementasikan
LifecycleOwner
, dan mendaftarkan SavedStateProvider
setelah
peristiwa ON_CREATE
terjadi. Dengan menggunakan LifecycleObserver
, Anda dapat memisahkan
pendaftaran dan pengambilan status tersimpan sebelumnya dari
SavedStateRegistryOwner
itu sendiri.
Kotlin
class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider { companion object { private const val PROVIDER = "search_manager" private const val QUERY = "query" } private val query: String? = null init { // Register a LifecycleObserver for when the Lifecycle hits ON_CREATE registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_CREATE) { val registry = registryOwner.savedStateRegistry // Register this object for future calls to saveState() registry.registerSavedStateProvider(PROVIDER, this) // Get the previously saved state and restore it val state = registry.consumeRestoredStateForKey(PROVIDER) // Apply the previously saved state query = state?.getString(QUERY) } } } override fun saveState(): Bundle { return bundleOf(QUERY to query) } ... } class SearchFragment : Fragment() { private var searchManager = SearchManager(this) ... }
Java
class SearchManager implements SavedStateRegistry.SavedStateProvider { private static String PROVIDER = "search_manager"; private static String QUERY = "query"; private String query = null; public SearchManager(SavedStateRegistryOwner registryOwner) { registryOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> { if (event == Lifecycle.Event.ON_CREATE) { SavedStateRegistry registry = registryOwner.getSavedStateRegistry(); // Register this object for future calls to saveState() registry.registerSavedStateProvider(PROVIDER, this); // Get the previously saved state and restore it Bundle state = registry.consumeRestoredStateForKey(PROVIDER); // Apply the previously saved state if (state != null) { query = state.getString(QUERY); } } }); } @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); bundle.putString(QUERY, query); return bundle; } ... } class SearchFragment extends Fragment { private SearchManager searchManager = new SearchManager(this); ... }
Menggunakan persistensi lokal untuk menangani penghentian proses untuk data yang kompleks atau besar
Penyimpanan lokal persisten, seperti database atau preferensi bersama, akan bertahan selama aplikasi Anda diinstal di perangkat pengguna (kecuali pengguna menghapus data untuk aplikasi). Meskipun penyimpanan lokal tersebut bertahan selama aktivitas yang dimulai oleh sistem dan penghentian proses aplikasi, penyimpanan lokal tersebut mungkin menjadi mahal untuk diambil karena harus dibaca dari penyimpanan lokal ke dalam memori. Biasanya penyimpanan lokal persisten mungkin sudah menjadi bagian dari arsitektur aplikasi untuk menyimpan semua data yang ingin Anda pertahankan jika Anda membuka dan menutup aktivitas.
ViewModel atau status instance tersimpan bukan solusi penyimpanan jangka panjang, dan oleh karena itu bukan pengganti penyimpanan lokal, seperti database. Anda harus menggunakan mekanisme ini hanya untuk menyimpan status UI sementara dan menggunakan penyimpanan persisten untuk data aplikasi lainnya. Lihat Panduan Arsitektur Aplikasi untuk detail selengkapnya tentang cara memanfaatkan penyimpanan lokal untuk mempertahankan data model aplikasi dalam jangka panjang (mis., setelah perangkat dimulai ulang beberapa kali).
Mengelola status UI: membagi dan menyelesaikan tugas
Anda dapat menyimpan dan memulihkan status UI secara efisien dengan cara membagi tugas ke beberapa jenis mekanisme persistensi. Biasanya, masing-masing mekanisme ini harus menyimpan jenis data yang berbeda yang digunakan dalam aktivitas, berdasarkan keseimbangan kompleksitas data, kecepatan akses, dan masa pakai:
- Persistensi lokal: Menyimpan semua data aplikasi yang ingin Anda pertahankan jika
Anda membuka dan menutup aktivitas.
- Contoh: Kumpulan objek lagu, yang dapat mencakup file audio dan metadata.
ViewModel
: Menyimpan semua data yang diperlukan untuk menampilkan UI terkait, status UI layar di memori.- Contoh: Objek lagu dari penelusuran terbaru dan kueri penelusuran terbaru.
- Status instance tersimpan: Menyimpan data dalam jumlah kecil yang diperlukan untuk memuat ulang
status UI jika sistem berhenti lalu membuat ulang UI. Daripada menyimpan
objek kompleks di sini, pertahankan objek kompleks pada penyimpanan lokal dan simpan
ID unik objek tersebut dalam API status instance yang disimpan.
- Contoh: Menyimpan kueri penelusuran terbaru.
Sebagai contoh, pertimbangkan aktivitas yang memungkinkan Anda menelusuri koleksi lagu Anda. Berikut adalah cara menangani berbagai peristiwa yang berbeda:
Saat pengguna menambahkan lagu, ViewModel
segera mendelegasikan untuk mempertahankan
data ini secara lokal. Jika lagu yang baru ditambahkan ini akan ditampilkan di UI, Anda
juga harus memperbarui data dalam objek ViewModel
untuk mencerminkan penambahan
lagu. Ingatlah untuk melakukan semua penyisipan database dari thread utama.
Saat pengguna menelusuri lagu, data lagu kompleks apa pun yang Anda muat dari
database akan segera disimpan dalam objek ViewModel
sebagai bagian dari
status UI layar.
Saat aktivitas beralih ke latar belakang dan sistem memanggil API berstatus instance
yang tersimpan, kueri penelusuran harus disimpan dalam status instance tersimpan,
jika proses dibuat ulang. Karena informasi diperlukan untuk memuat
data aplikasi yang dipertahankan dalam hal ini, simpan kueri penelusuran di
SavedStateHandle
ViewModel. Ini adalah semua informasi yang Anda perlukan untuk memuat data dan
mengembalikan UI ke statusnya saat ini.
Memulihkan status kompleks: menyusun ulang setiap bagian
Jika saatnya bagi pengguna untuk kembali ke aktivitas, ada dua kemungkinan skenario dalam membuat ulang aktivitas:
- Aktivitas dibuat ulang setelah dihentikan oleh sistem. Sistem
memiliki kueri yang tersimpan dalam paket status instance yang tersimpan, dan UI
harus meneruskan kueri tersebut ke
ViewModel
jikaSavedStateHandle
tidak digunakan.ViewModel
mengetahui bahwa hasil penelusuran belum disimpan ke cache dan mendelegasikan pemuatan hasil penelusuran menggunakan kueri penelusuran yang ditentukan. - Aktivitas dibuat setelah perubahan konfigurasi. Karena instance
ViewModel
belum dihancurkan,ViewModel
menyimpan semua informasi dalam cache ke dalam memori dan tidak perlu mengkueri ulang database.
Referensi lainnya
Untuk mempelajari cara menyimpan status UI lebih lanjut, lihat referensi berikut.
Blog
- ViewModels: Contoh sederhana
- ViewModel: Persistensi,
onSaveInstanceState()
, Memulihkan Status dan Loader UI - Codelab komponen berbasis siklus proses Android
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Modul Status Tersimpan untuk ViewModel
- Menangani Siklus Proses dengan Komponen Berbasis Siklus Proses
- Ringkasan ViewModel