Menangani aplikasi yang di-cache dan dibekukan

Saat menggunakan binder untuk berkomunikasi antar-proses, berhati-hatilah saat proses jarak jauh berada dalam status di-cache atau dibekukan. Panggilan ke aplikasi yang di-cache atau dibekukan dapat menyebabkan aplikasi tersebut error atau menggunakan resource secara tidak perlu.

Status aplikasi yang di-cache dan dibekukan

Android mempertahankan aplikasi dalam berbagai status untuk mengelola resource sistem seperti memori dan CPU.

Status yang di-cache

Jika tidak memiliki komponen yang terlihat oleh pengguna, seperti aktivitas atau layanan, aplikasi dapat dipindahkan ke status di-cache. Lihat Proses dan siklus proses aplikasi untuk mengetahui detailnya. Aplikasi yang di-cache disimpan di memori jika pengguna beralih kembali ke aplikasi tersebut, tetapi aplikasi tersebut tidak diharapkan berfungsi secara aktif.

Saat melakukan binding dari satu proses aplikasi ke proses aplikasi lain, seperti menggunakan bindService, status proses server ditingkatkan agar setidaknya sama pentingnya dengan proses klien (kecuali jika Context#BIND_WAIVE_PRIORITY ditentukan). Misalnya, jika klien tidak dalam status yang di-cache, server juga tidak dalam status yang di-cache.

Sebaliknya, status proses server tidak menentukan status kliennya. Jadi, server mungkin memiliki koneksi binder ke klien, yang paling umum dalam bentuk callback, dan saat proses jarak jauh berada dalam status yang di-cache, server tidak di-cache.

Saat mendesain API tempat callback berasal dari proses yang ditingkatkan dan dikirim ke aplikasi, pertimbangkan untuk menjeda pengiriman callback saat aplikasi memasuki status yang di-cache, dan melanjutkan saat aplikasi keluar dari status ini. Hal ini mencegah tugas yang tidak perlu dalam proses aplikasi yang di-cache.

Untuk melacak kapan aplikasi memasuki atau keluar dari status yang di-cache, gunakan ActivityManager.addOnUidImportanceListener:

// in ActivityManager or Context
activityManager.addOnUidImportanceListener(
    new UidImportanceListener() { ... },
    IMPORTANCE_CACHED);

Status dibekukan

Sistem dapat membekukan aplikasi yang di-cache untuk menghemat resource. Saat dibekukan, aplikasi tidak menerima waktu CPU dan tidak dapat melakukan pekerjaan apa pun. Untuk mengetahui detail selengkapnya, lihat Penghentian aplikasi yang di-cache.

Saat proses mengirim transaksi pengikat sinkron (bukan oneway) ke proses jarak jauh lain yang dibekukan, sistem akan menghentikan proses jarak jauh tersebut. Hal ini mencegah thread panggilan dalam proses panggilan berhenti berfungsi tanpa batas waktu saat menunggu proses jarak jauh diaktifkan kembali, yang dapat menyebabkan kekurangan thread atau deadlock di aplikasi panggilan.

Saat proses mengirim transaksi binder asinkron (oneway) ke aplikasi yang dibekukan (biasanya dengan memberi tahu callback, yang biasanya merupakan metode oneway), transaksi akan di-buffer hingga proses jarak jauh tidak dibekukan. Jika buffer meluap, proses aplikasi penerima dapat mengalami error. Selain itu, transaksi yang di-buffer mungkin menjadi tidak berlaku pada saat proses aplikasi di-unfreeze dan diproses.

Untuk menghindari aplikasi yang kewalahan dengan peristiwa yang sudah tidak berlaku atau buffer yang meluap, Anda harus menjeda pengiriman callback saat proses aplikasi penerima dibekukan.

Untuk melacak kapan aplikasi dibekukan atau tidak dibekukan, gunakan IBinder.addFrozenStateChangeCallback:

// The binder token of the remote process
IBinder binder = service.getBinder();

// Keep track of frozen state
AtomicBoolean remoteFrozen = new AtomicBoolean(false);

// Update remoteFrozen when the remote process freezes or unfreezes
binder.addFrozenStateChangeCallback(
    myExecutor,
    new IBinder.FrozenStateChangeCallback() {
        @Override
        public void onFrozenStateChanged(boolean isFrozen) {
            remoteFrozen.set(isFrozen);
        }
    });

// When dispatching callbacks to the remote process, pause dispatch if frozen:
if (!remoteFrozen.get()) {
    // dispatch callback to remote process
}

Menggunakan RemoteCallbackList

Class RemoteCallbackList adalah helper untuk mengelola daftar callback IInterface yang didaftarkan oleh proses jarak jauh. Class ini secara otomatis menangani notifikasi penutupan binder dan menyediakan opsi untuk menangani panggilan balik ke aplikasi yang dibekukan.

Saat membangun RemoteCallbackList, Anda dapat menentukan kebijakan callee yang dibekukan:

  • FROZEN_CALLEE_POLICY_DROP: Callback ke aplikasi yang dibekukan akan dihentikan secara diam-diam. Gunakan kebijakan ini saat peristiwa yang terjadi saat aplikasi di-cache tidak penting bagi aplikasi, misalnya, peristiwa sensor real-time.
  • FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: Jika beberapa callback disiarkan saat aplikasi dibekukan, hanya callback terbaru yang dimasukkan dalam antrean dan dikirim saat aplikasi tidak dibekukan. Hal ini berguna untuk callback berbasis status yang hanya memperhitungkan update status terbaru, misalnya, callback yang memberi tahu aplikasi tentang volume media saat ini.
  • FROZEN_CALLEE_POLICY_ENQUEUE_ALL: Semua callback yang disiarkan saat aplikasi dibekukan diantrekan dan dikirimkan saat aplikasi tidak dibekukan. Berhati-hatilah dengan kebijakan ini, karena dapat menyebabkan overflow buffer jika terlalu banyak callback yang dimasukkan dalam antrean, atau menyebabkan akumulasi peristiwa yang tidak berlaku.

Contoh berikut menunjukkan cara membuat dan menggunakan instance RemoteCallbackList yang membatalkan callback ke aplikasi yang dibekukan:

RemoteCallbackList<IMyCallbackInterface> callbacks =
        new RemoteCallbackList.Builder<IMyCallbackInterface>(
                        RemoteCallbackList.FROZEN_CALLEE_POLICY_DROP)
                .setExecutor(myExecutor)
                .build();

// Registering a callback:
callbacks.register(callback);

// Broadcasting to all registered callbacks:
callbacks.broadcast((callback) -> callback.onSomeEvent(eventData));

Jika Anda menggunakan FROZEN_CALLEE_POLICY_DROP, sistem akan memanggil callback.onSomeEvent() hanya jika proses yang menghosting callback tidak dibekukan.

Layanan sistem dan interaksi aplikasi

Layanan sistem sering berinteraksi dengan banyak aplikasi berbeda menggunakan binder. Karena aplikasi dapat memasuki status di-cache dan dibekukan, layanan sistem harus berhati-hati dalam menangani interaksi ini dengan baik untuk membantu menjaga stabilitas dan performa sistem.

Layanan sistem sudah perlu menangani situasi saat proses aplikasi dihentikan karena berbagai alasan. Hal ini melibatkan penghentian pekerjaan atas nama mereka dan tidak mencoba untuk terus mengirimkan callback ke proses yang tidak aktif. Pertimbangan aplikasi yang dibekukan adalah perluasan dari tanggung jawab pemantauan yang sudah ada ini.

Melacak status aplikasi dari layanan sistem

Layanan sistem, yang berjalan di system_server atau sebagai daemon native, juga dapat menggunakan API yang dijelaskan sebelumnya untuk melacak status penting dan status dibekukan dari proses aplikasi:

  • ActivityManager.addOnUidImportanceListener: Layanan sistem dapat mendaftarkan pemroses untuk melacak perubahan kepentingan UID. Saat menerima panggilan atau callback binder dari aplikasi, layanan dapat menggunakan Binder.getCallingUid() untuk mendapatkan UID dan mengorelasikannya dengan status kepentingan yang dilacak oleh pemroses. Hal ini memungkinkan layanan sistem mengetahui apakah aplikasi yang memanggil berada dalam status di-cache.

  • IBinder.addFrozenStateChangeCallback: Saat layanan sistem menerima objek binder dari aplikasi (misalnya, sebagai bagian dari pendaftaran untuk callback), layanan tersebut harus mendaftarkan FrozenStateChangeCallback di instance IBinder tertentu tersebut. Hal ini secara langsung memberi tahu layanan sistem saat proses aplikasi yang menghosting binder tersebut dibekukan atau tidak dibekukan.

Rekomendasi untuk layanan sistem

Sebaiknya semua layanan sistem yang mungkin berinteraksi dengan aplikasi melacak status yang di-cache dan dibekukan dari proses aplikasi yang berinteraksi dengannya. Jika tidak dilakukan, hal ini dapat menyebabkan:

  • Penggunaan resource: Melakukan pekerjaan untuk aplikasi yang di-cache dan tidak terlihat oleh pengguna dapat memboroskan resource sistem.
  • Aplikasi error: Panggilan binder sinkron ke aplikasi yang dibekukan akan menyebabkan aplikasi error. Panggilan binder asinkron ke aplikasi yang dibekukan menyebabkan error jika buffer transaksi asinkronnya meluap.
  • Perilaku aplikasi yang tidak terduga: Aplikasi yang tidak dibekukan akan segera menerima transaksi binder asinkron yang di-buffer yang dikirimkan kepadanya saat aplikasi tersebut dibekukan. Aplikasi dapat menghabiskan waktu yang tidak terbatas di freezer, sehingga transaksi yang di-buffer dapat menjadi sangat tidak berlaku.

Layanan sistem sering menggunakan RemoteCallbackList untuk mengelola callback jarak jauh dan menangani proses yang tidak aktif secara otomatis. Untuk menangani aplikasi yang dibekukan, perluas penggunaan RemoteCallbackList yang ada dengan menerapkan kebijakan pemanggil yang dibekukan seperti yang dijelaskan dalam Menggunakan RemoteCallbackList.