Bergabunglah bersama kami di ⁠#Android11: The Beta Launch Show pada tanggal 3 Juni!

Menangani Siklus Proses dengan Komponen Berbasis Siklus Proses   Bagian dari Android Jetpack.

Komponen berbasis siklus proses melakukan tindakan sebagai respons terhadap perubahan status proses hidup komponen lain, seperti aktivitas dan fragmen. Komponen-komponen ini membantu Anda menghasilkan kode yang lebih rapi dan sering kali lebih ringan, yang lebih mudah dipelihara.

Pola umumnya yaitu mengimplementasikan tindakan komponen dependen dalam metode siklus hidup milik aktivitas dan fragmen. Akan tetapi, pola ini menyebabkan ketidakteraturan kode dan penyebaran error. Dengan menggunakan komponen berbasis siklus proses, Anda dapat memindahkan kode komponen dependen keluar dari metode siklus proses dan ke dalam komponen itu sendiri.

Paket androidx.lifecycle menyediakan class dan antarmuka yang memungkinkan Anda membuat komponen berbasis siklus proses—yaitu komponen yang dapat otomatis menyesuaikan perilakunya berdasarkan status siklus proses saat ini dari suatu aktivitas atau fragmen.

Sebagian besar komponen aplikasi yang didefinisikan dalam Framework Android telah menyertakan siklus proses. Siklus hidup dikelola oleh sistem operasi atau kode framework yang berjalan di proses Anda. Siklus hidup adalah inti cara kerja Android, dan aplikasi Anda harus mengikutinya. Tidak mengikutinya dapat memicu kebocoran memori atau bahkan menyebabkan aplikasi tidak bekerja.

Bayangkan saat kita memiliki aktivitas yang menampilkan lokasi perangkat di layar. Implementasi yang umum dilakukan adalah sebagai berikut:

Kotlin

    internal class MyLocationListener(
            private val context: Context,
            private val callback: (Location) -> Unit
    ) {

        fun start() {
            // connect to system location service
        }

        fun stop() {
            // disconnect from system location service
        }
    }

    class MyActivity : AppCompatActivity() {
        private lateinit var myLocationListener: MyLocationListener

        override fun onCreate(...) {
            myLocationListener = MyLocationListener(this) { location ->
                // update UI
            }
        }

        public override fun onStart() {
            super.onStart()
            myLocationListener.start()
            // manage other components that need to respond
            // to the activity lifecycle
        }

        public override fun onStop() {
            super.onStop()
            myLocationListener.stop()
            // manage other components that need to respond
            // to the activity lifecycle
        }
    }
    

Java

    class MyLocationListener {
        public MyLocationListener(Context context, Callback callback) {
            // ...
        }

        void start() {
            // connect to system location service
        }

        void stop() {
            // disconnect from system location service
        }
    }

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;

        @Override
        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, (location) -> {
                // update UI
            });
        }

        @Override
        public void onStart() {
            super.onStart();
            myLocationListener.start();
            // manage other components that need to respond
            // to the activity lifecycle
        }

        @Override
        public void onStop() {
            super.onStop();
            myLocationListener.stop();
            // manage other components that need to respond
            // to the activity lifecycle
        }
    }
    

Meskipun contoh ini terlihat baik-baik saja, pada aplikasi yang sebenarnya, Anda akan mendapati terlalu banyak panggilan yang mengelola UI dan komponen lain sebagai respons atas status saat ini dari siklus proses tersebut. Pengelolaan banyak komponen akan menempatkan sejumlah besar kode pada metode siklus proses, seperti onStart() dan onStop(), sehingga sulit dikelola.

Selain itu, tidak ada jaminan bawa komponen akan dimulai sebelum aktivitas atau fragmen dihentikan. Ini terjadi terutama jika kita perlu menjalankan operasi yang berdurasi lama, seperti pemeriksaan konfigurasi di onStart(). Hal ini dapat menyebabkan kondisi race, di mana metode onStop() selesai sebelum onStart(), sehingga komponen aktif lebih lama dari yang dibutuhkan.

Kotlin

    class MyActivity : AppCompatActivity() {
        private lateinit var myLocationListener: MyLocationListener

        override fun onCreate(...) {
            myLocationListener = MyLocationListener(this) { location ->
                // update UI
            }
        }

        public override fun onStart() {
            super.onStart()
            Util.checkUserStatus { result ->
                // what if this callback is invoked AFTER activity is stopped?
                if (result) {
                    myLocationListener.start()
                }
            }
        }

        public override fun onStop() {
            super.onStop()
            myLocationListener.stop()
        }

    }
    

Java

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;

        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, location -> {
                // update UI
            });
        }

        @Override
        public void onStart() {
            super.onStart();
            Util.checkUserStatus(result -> {
                // what if this callback is invoked AFTER activity is stopped?
                if (result) {
                    myLocationListener.start();
                }
            });
        }

        @Override
        public void onStop() {
            super.onStop();
            myLocationListener.stop();
        }
    }
    

Paket androidx.lifecycle menyediakan class dan antarmuka yang memudahkan Anda mengatasi masalah ini dengan cara yang andal dan terisolasi.

Lifecycle

Lifecycle adalah class yang menyimpan informasi tentang status siklus proses suatu komponen (seperti aktivitas atau fragmen) dan memungkinkan objek lain untuk mengamati status ini.

Lifecycle menggunakan dua enumerasi utama untuk melacak status siklus proses komponen terkaitnya:

Peristiwa
Peristiwa siklus proses yang dikirim dari framework dan class Lifecycle. Peristiwa ini memetakan ke peristiwa callback dalam aktivitas dan fragmen.
Status
Status saat ini dari komponen yang dilacak oleh objek Lifecycle.
Diagram status siklus proses
Gambar 1. Status dan peristiwa yang membentuk siklus proses aktivitas Android

Bayangkan status sebagai node di grafik dan peristiwa sebagai sudut node-node ini.

Suatu class dapat mengamati status siklus proses komponen dengan cara menambahkan anotasi ke metodenya. Kemudian, Anda dapat menambahkan pengamat dengan memanggil metode addObserver() dari class Lifecycle dan meneruskan instance pengamat, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

    class MyObserver : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        fun connectListener() {
            ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        fun disconnectListener() {
            ...
        }
    }

    myLifecycleOwner.getLifecycle().addObserver(MyObserver())
    

Java

    public class MyObserver implements LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
        public void connectListener() {
            ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
        public void disconnectListener() {
            ...
        }
    }

    myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
    

Pada contoh di atas, objek myLifecycleOwner mengimplementasikan antarmuka LifecycleOwner, seperti yang dijelaskan di bagian berikut.

LifecycleOwner

LifecycleOwner adalah antarmuka metode tunggal yang menunjukkan bahwa class memiliki Lifecycle. Ini memiliki satu metode, getLifecycle(), yang harus diimplementasikan oleh class tersebut. Jika Anda mencoba mengelola siklus proses dari keseluruhan proses aplikasi, lihat ProcessLifecycleOwner.

Antarmuka ini memisahkan kepemilikan Lifecycle dari class individu, seperti Fragment dan AppCompatActivity, dan memungkinkan penulisan komponen yang dapat digunakan dengan class individual tersebut. Semua class aplikasi kustom dapat mengimplementasikan antarmuka LifecycleOwner.

Komponen yang mengimplementasikan LifecycleObserver bekerja lancar dengan komponen yang mengimplementasikan LifecycleOwner karena pemilik dapat menyediakan siklus proses, yang dapat didaftarkan pengamat untuk mengamati.

Untuk contoh pelacakan lokasi, kita dapat membuat class MyLocationListener mengimplementasikan LifecycleObserver, lalu menginisiasinya dengan Lifecycle aktivitas pada metode onCreate(). Tindakan ini memungkinkan class MyLocationListener menjadi mandiri, yang berarti bahwa logika untuk bereaksi terhadap perubahan status siklus proses dideklarasikan di MyLocationListener, bukan di aktivitas. Mengizinkan komponen individu untuk menyimpan logika mereka sendiri membuat logika aktivitas dan fragmen lebih mudah dikelola.

Kotlin

    class MyActivity : AppCompatActivity() {
        private lateinit var myLocationListener: MyLocationListener

        override fun onCreate(...) {
            myLocationListener = MyLocationListener(this, lifecycle) { location ->
                // update UI
            }
            Util.checkUserStatus { result ->
                if (result) {
                    myLocationListener.enable()
                }
            }
        }
    }
    

Java

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;

        public void onCreate(...) {
            myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
                // update UI
            });
            Util.checkUserStatus(result -> {
                if (result) {
                    myLocationListener.enable();
                }
            });
      }
    }
    

Kasus penggunaan yang umum adalah menghindari pemanggilan callback tertentu jika Lifecycle tidak sedang berstatus baik. Misalnya, jika callback menjalankan transaksi fragmen setelah status aktivitas disimpan, maka callback ini akan memicu error, dan kita tidak ingin memicu callback itu lagi.

Untuk mempermudah kasus penggunaan ini, class Lifecycle mengizinkan objek lain mengkueri status saat ini.

Kotlin

    internal class MyLocationListener(
            private val context: Context,
            private val lifecycle: Lifecycle,
            private val callback: (Location) -> Unit
    ) {

        private var enabled = false

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun start() {
            if (enabled) {
                // connect
            }
        }

        fun enable() {
            enabled = true
            if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
                // connect if not connected
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun stop() {
            // disconnect if connected
        }
    }
    

Java

    class MyLocationListener implements LifecycleObserver {
        private boolean enabled = false;
        public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
           ...
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        void start() {
            if (enabled) {
               // connect
            }
        }

        public void enable() {
            enabled = true;
            if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
                // connect if not connected
            }
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        void stop() {
            // disconnect if connected
        }
    }
    

Dengan implementasi ini, class LocationListener menjadi sepenuhnya berbasis siklus proses. Jika kita perlu menggunakan LocationListener dari aktivitas atau fragmen lain, kita hanya perlu menginisiasinya. Semua operasi penyiapan dan pemutusan dikelola oleh class-nya sendiri.

Jika library menyediakan class yang perlu berfungsi dengan siklus hidup Android, kami merekomendasikan agar Anda menggunakan komponen berbasis siklus hidup. Klien library dapat mengintegrasikan komponen-komponen itu dengan mudah tanpa pengelolaan siklus hidup secara manual di sisi klien.

Mengimplementasikan LifecycleOwner kustom

Fragmen dan Aktivitas pada Support Library 26.1.0 dan yang lebih baru telah mengimplementasikan antarmuka LifecycleOwner.

Jika Anda memiliki class kustom yang ingin Anda jadikan sebagai LifecycleOwner, Anda dapat menggunakan class LifecycleRegistry, tetapi Anda perlu meneruskan peristiwa ke class tersebut, seperti yang ditunjukkan dalam contoh kode berikut:

Kotlin

    class MyActivity : Activity(), LifecycleOwner {

        private lateinit var lifecycleRegistry: LifecycleRegistry

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            lifecycleRegistry = LifecycleRegistry(this)
            lifecycleRegistry.markState(Lifecycle.State.CREATED)
        }

        public override fun onStart() {
            super.onStart()
            lifecycleRegistry.markState(Lifecycle.State.STARTED)
        }

        override fun getLifecycle(): Lifecycle {
            return lifecycleRegistry
        }
    }
    

Java

    public class MyActivity extends Activity implements LifecycleOwner {
        private LifecycleRegistry lifecycleRegistry;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            lifecycleRegistry = new LifecycleRegistry(this);
            lifecycleRegistry.markState(Lifecycle.State.CREATED);
        }

        @Override
        public void onStart() {
            super.onStart();
            lifecycleRegistry.markState(Lifecycle.State.STARTED);
        }

        @NonNull
        @Override
        public Lifecycle getLifecycle() {
            return lifecycleRegistry;
        }
    }
    

Praktik terbaik untuk komponen berbasis siklus proses

  • Buatlah pengontrol UI (aktivitas dan fragmen) yang seramping mungkin. Pengontrol UI tidak boleh mencoba memperoleh datanya sendiri; sebagai gantinya, gunakan ViewModel untuk tujuan tersebut, dan amati objek LiveData untuk mencerminkan perubahan ke tampilan.
  • Cobalah untuk menulis UI berbasis data, di mana tanggung jawab pengontrol UI Anda adalah memperbarui tampilan saat terjadi perubahan data, atau memberitahukan kembali tindakan pengguna ke ViewModel.
  • Tempatkan logika data Anda di class ViewModel. ViewModel harus berfungsi sebagai penghubung antara pengontrol UI dan seluruh aplikasi Anda. Namun perlu diperhatikan, ViewModel tidak bertanggung jawab untuk mengambil data (misalnya, dari jaringan). Sebagai gantinya, ViewModel harus memanggil komponen yang sesuai untuk mengambil data, kemudian menyediakan kembali hasilnya ke pengontrol UI.
  • Gunakan Data Binding untuk memelihara antarmuka yang bersih antara tampilan dan pengontrol UI. Tindakan ini memungkinkan Anda untuk membuat tampilan lebih deklaratif dan meminimalkan kode update yang perlu ditulis dalam aktivitas dan fragmen Anda. Jika Anda lebih nyaman melakukan hal ini menggunakan bahasa pemrograman Java, gunakanlah library seperti Butter Knife untuk menghindari kode boilerplate dan menghasilkan pemisahan yang lebih baik.
  • Jika UI Anda kompleks, pertimbangkan membuat class presenter untuk menangani modifikasi UI. Tugas ini bisa jadi melelahkan, tetapi hasilnya komponen UI Anda lebih mudah diuji.
  • Hindari mereferensikan konteks View atau Activity dalam ViewModel Anda. Jika ViewModel aktif lebih lama dibandingkan aktivitas (dalam kasus perubahan konfigurasi), artinya aktivitas Anda bocor dan tidak dibuang dengan benar oleh pembersih sampah memori.
  • Gunakan coroutine Kotlin untuk mengelola tugas yang berjalan lama dan operasi lain yang berjalan secara asinkron.

Kasus penggunaan komponen berbasis siklus hidup

Komponen berbasis siklus hidup dapat mempermudah Anda dalam mengelola siklus hidup dalam berbagai kasus. Beberapa contohnya adalah:

  • Beralih antara update lokasi yang samar dan akurat. Gunakan komponen berbasis siklus proses untuk mengaktifkan pembaruan lokasi akurat saat aplikasi lokasi Anda terlihat dan beralih ke pembaruan lokasi sementara saat aplikasi berada di latar belakang. LiveData, komponen berbasis siklus proses, memungkinkan aplikasi Anda untuk otomatis mengupdate UI saat lokasi pengguna berubah.
  • Menghentikan atau memulai buffering video. Gunakan komponen berbasis siklus hidup untuk segera memulai buffering video, tetapi menunda pemutaran hingga aplikasi sepenuhnya dimulai. Anda juga dapat menggunakan komponen berbasis siklus hidup untuk menghentikan buffering saat aplikasi dimusnahkan.
  • Memulai dan menghentikan konektivitas jaringan. Gunakan komponen berbasis siklus hidup untuk mengaktifkan update live (streaming) data jaringan saat aplikasi sedang digunakan di latar depan dan juga secara otomatis menjeda saat aplikasi berada di latar belakang.
  • Menjeda dan melanjutkan animasi yang dapat digambar. Gunakan komponen berbasis siklus hidup untuk menangani penjedaan animasi yang dapat digambar saat aplikasi berada di latar belakang dan melanjutkan gambar saat aplikasi kembali digunakan di latar depan.

Menangani peristiwa penghentian

Saat Lifecycle berada di AppCompatActivity atau Fragment, status Lifecycle berubah menjadi CREATED dan peristiwa ON_STOP dikirim saat AppCompatActivity atau onSaveInstanceState() pada Fragment dipanggil.

Saat status Fragment atau AppCompatActivity disimpan melalui onSaveInstanceState(), UI-nya dianggap tidak dapat diubah hingga ON_START dipanggil. Mencoba memodifikasi UI setelah status disimpan dapat menyebabkan inkonsistensi dalam status navigasi aplikasi Anda, dan karena alasan itulah FragmentManager menampilkan pengecualian jika aplikasi menjalankan FragmentTransaction setelah status disimpan. Untuk lebih detailnya, lihat commit().

LiveData mencegah kasus ekstrem ini secara mandiri dengan tidak memanggil pengamatnya jika Lifecycle yang terkait pengamat tersebut tidak berstatus setidaknya STARTED. Di balik layar, objek ini memanggil isAtLeast() sebelum memutuskan untuk memanggil pengamatnya.

Sayangnya, metode onStop() pada AppCompatActivity dipanggil setelah onSaveInstanceState(), dan ini menyisakan celah di mana perubahan status UI tidak diperbolehkan, tetapi Lifecycle belum dipindahkan ke status CREATED.

Untuk mencegah masalah ini, class Lifecycle pada versi beta2 dan yang lebih rendah menandai status ini sebagai CREATED tanpa mengirim peristiwanya, sehingga kode apa pun yang memeriksa status saat ini akan mendapatkan nilai sebenarnya, meskipun peristiwanya tidak dikirimkan hingga onStop() dipanggil oleh sistem.

Sayangnya, solusi ini menimbulkan dua masalah utama:

  • Pada API level 23 atau lebih rendah, sistem Android sebenarnya menyimpan status aktivitas meski sebagian status aktivitas tersebut dilakukan oleh aktivitas lain. Dengan kata lain, sistem Android memanggil onSaveInstanceState() tetapi tidak selalu memanggil onStop(). Hal ini berpotensi menimbulkan interval panjang, yang membuat pengamat berpikir bahwa siklus proses masih aktif meski status UI-nya tidak dapat diubah.
  • Class apa pun yang ingin memperlihatkan perilaku serupa ke class LiveData harus mengimplementasikan solusi yang disediakan oleh Lifecycle versi beta 2 dan yang lebih rendah.

Referensi lainnya

Untuk mempelajari lebih lanjut cara menangani siklus hidup dengan komponen berbasis siklus hidup, lihat referensi tambahan berikut.

Contoh

Codelab

Blog