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

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

Pola umumnya yaitu mengimplementasikan tindakan komponen dependen dalam metode siklus proses milik aktivitas dan fragmen. Namun, 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 proses dikelola oleh sistem operasi atau kode framework yang bekerja di proses Anda. Siklus proses adalah inti cara kerja Android, dan aplikasi Anda harus mengikutinya. Jika tidak dilakukan, mungkin terjadi kebocoran memori atau error pada aplikasi.

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 benar, 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. Mengelola banyak komponen menempatkan banyak kode pada metode siklus proses, seperti onStart() dan onStop(), yang membuatnya sulit dikelola.

Selain itu, tidak ada jaminan bahwa komponen akan mulai sebelum aktivitas atau fragmen dihentikan. Ini terjadi terutama jika kita perlu menjalankan operasi yang berjalan lama, seperti pemeriksaan konfigurasi di onStart(). Hal ini dapat menyebabkan kondisi race saat 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.

Siklus Proses

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

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

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 grafik dan peristiwa sebagai sudut node-node ini.

Class dapat memantau status siklus proses komponen dengan mengimplementasikan DefaultLifecycleObserver dan mengganti metode yang terkait seperti onCreate, onStart, dll. 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 : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        connect()
    }

    override fun onPause(owner: LifecycleOwner) {
        disconnect()
    }
}

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

Java

public class MyObserver implements DefaultLifecycleObserver {
    @Override
    public void onResume(LifecycleOwner owner) {
        connect()
    }

    @Override
    public void onPause(LifecycleOwner owner) {
        disconnect()
    }
}

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 individual, 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 DefaultLifecycleObserver bekerja lancar dengan komponen yang mengimplementasikan LifecycleOwner karena pemilik dapat menyediakan siklus proses, yang dapat didaftarkan oleh pengamat untuk diamati.

Untuk contoh pelacakan lokasi, kita dapat membuat class MyLocationListener mengimplementasikan DefaultLifecycleObserver, lalu menginisialisasinya 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 setiap komponen untuk menyimpan logika mereka sendiri membuat logika aktivitas dan fragmen lebih mudah dipelihara.

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 callback tertentu jika Lifecycle tidak sedang berstatus baik. Misalnya, jika callback menjalankan transaksi fragmen setelah status aktivitas disimpan, 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
): DefaultLifecycleObserver {

    private var enabled = false

    override fun onStart(owner: LifecycleOwner) {
        if (enabled) {
            // connect
        }
    }

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

    override fun onStop(owner: LifecycleOwner) {
        // disconnect if connected
    }
}

Java

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

    @Override
    public void onStart(LifecycleOwner owner) {
        if (enabled) {
           // connect
        }
    }

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

    @Override
    public void onStop(LifecycleOwner owner) {
        // 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 menginisialisasinya. Semua operasi penyiapan dan pemutusan dikelola oleh class-nya sendiri.

Jika library menyediakan kelas yang perlu bekerja dengan siklus proses Android, kami menyarankan agar Anda menggunakan komponen berbasis siklus proses. Klien library dapat mengintegrasikan komponen-komponen itu dengan mudah tanpa pengelolaan siklus proses 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 memiliki class kustom yang ingin dijadikan 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 ikuti objek LiveData untuk mencerminkan perubahan ke tampilan.
  • Cobalah untuk menulis UI berbasis data, yang tanggung jawab pengontrol UI-nya 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 rapi 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, gunakan library seperti Butter Knife untuk menghindari kode boilerplate dan menghasilkan abstraksi yang lebih baik.
  • Jika UI Anda kompleks, pertimbangkan membuat kelas presenter untuk menangani modifikasi UI. Tugas ini bisa jadi melelahkan, tetapi hasilnya membuat 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 proses

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

  • Beralih antara update lokasi yang umum dan akurat. Gunakan komponen berbasis siklus proses untuk mengaktifkan update lokasi akurat saat aplikasi lokasi Anda terlihat dan beralih ke update umum 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 proses untuk segera memulai buffering video, namun menunda pemutaran hingga aplikasi sepenuhnya dimulai. Anda dapat juga menggunakan komponen berbasis siklus proses untuk menghentikan buffering saat aplikasi Anda dimusnahkan.
  • Memulai dan menghentikan konektivitas jaringan. Gunakan komponen berbasis siklus proses untuk mengaktifkan update live (streaming) data jaringan saat aplikasi sedang digunakan dan juga secara otomatis berhenti sementara saat aplikasi berada di latar belakang.
  • Menjeda dan melanjutkan animasi drawable. Gunakan komponen berbasis siklus proses untuk menangani penjedaan animasi drawable saat aplikasi berada di latar belakang dan melanjutkan drawable saat aplikasi kembali digunakan.

Menangani peristiwa penghentian

Saat Lifecycle berada di AppCompatActivity atau Fragment, status Lifecycle berubah menjadi CREATED dan peristiwa ON_STOP dikirim saat AppCompatActivity atau onSaveInstanceState() dari 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 detailnya, lihat commit().

LiveData mencegah kasus ekstrem ini secara mandiri dengan tidak memanggil pengamatnya jika Lifecycle teratribusi 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 yang tidak memperbolehkan perubahan status UI, 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 level API 23 atau yang lebih lama, sistem Android sebenarnya menyimpan status aktivitas walaupun 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 walaupun 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 lama.

Referensi lainnya

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

Contoh

  • Sunflower, sebuah aplikasi demo yang menunjukkan praktik terbaik dengan Komponen Arsitektur

Codelab

Blog