Topik ini memperkenalkan konsep inti, kategori event, metode penggunaan, dan praktik terbaik sistem event AliPlayerKit.
Konsep
Apa itu event?
Event adalah notifikasi perubahan status atau perilaku yang terjadi selama waktu proses pemutar. Setiap event membawa ID pemutar (playerId) untuk mengidentifikasi sumbernya dan mengisolasi event antar berbagai instans pemutar.
Event dalam AliPlayerKit terbagi menjadi dua kategori utama:
Tipe | Deskripsi | Contoh |
Status event | Notifikasi perubahan status pemutar, dihasilkan secara internal oleh pemutar. |
|
Command event | Instruksi untuk mengontrol perilaku pemutar, dihasilkan oleh komponen UI atau eksternal. |
|
Semua event mewarisi kelas dasar PlayerEvent untuk memastikan struktur event yang konsisten.
Apa itu sistem event?
Sistem event adalah arsitektur komunikasi untuk mengelola langganan dan penerbitan event. Komponen intinya adalah PlayerEventBus.
Sistem ini menyediakan kemampuan berikut:
Langganan aman tipe: Anda dapat berlangganan berdasarkan tipe event. Keamanan tipe dijamin pada waktu kompilasi.
Keamanan thread: Mendukung penerbitan dan langganan event di lingkungan multi-threaded.
Pengelolaan referensi lemah: Menyimpan pendengar menggunakan referensi lemah dan membersihkan pendengar yang telah dikumpulkan oleh garbage collector secara otomatis.
Isolasi event: Mengisolasi event antar berbagai instans pemutar menggunakan
playerId.
Sistem event sepenuhnya menguraikan keterkaitan antara komponen UI (Slots) dan controller. UI hanya mengirim perintah dan berlangganan perubahan status, tanpa perlu mengetahui bagaimana perintah tersebut dieksekusi. Controller hanya menangani perintah dan menerbitkan perubahan status, tanpa peduli siapa yang mendengarkan.
Fitur
Masalah yang diselesaikan
Penguraian keterkaitan komponen: Menghilangkan dependensi langsung antar komponen, sehingga mengurangi keterkaitan dan meningkatkan kemampuan penggantian serta pengujian.
Ketertelusuran event: Mengatasi kebingungan sumber event dalam skenario multi-pemutar.
Pemisahan tanggung jawab: Memisahkan komponen UI dari logika bisnis dan memperjelas batas tanggung jawab.
Nilai inti
Sistem event menstandarkan komunikasi antar komponen. Pengembang dapat menggunakannya dengan cara berikut:
Metode penggunaan | Deskripsi | Keuntungan |
Berlangganan event | Mendengarkan perubahan status pemutar. | Menguraikan keterkaitan UI dari logika bisnis dan memperbarui UI secara reaktif. |
Mengirim perintah | Mengontrol perilaku pemutar. | UI tidak perlu menyimpan referensi controller. Interaksi berbasis perintah. |
Menyesuaikan event | Memperluas tipe event. | Cocok untuk skenario bisnis spesifik sekaligus menjaga konsistensi arsitektur. |
Keunggulan arsitektur:
Penguraian keterkaitan: Komponen berkomunikasi melalui event dan tidak memiliki dependensi langsung.
Kemampuan pengujian: Anda dapat menguji komponen secara independen dengan mensimulasikan interaksi melalui event.
Ekstensibilitas: Anda dapat menambahkan tipe event kustom tanpa mengubah kode framework.
Keamanan thread: Berfungsi di lingkungan multi-threaded tanpa memerlukan sinkronisasi tambahan.
Kemampuan inti
Kemampuan | Deskripsi |
Langganan aman tipe | Berlangganan berdasarkan tipe event. Pemeriksaan tipe dilakukan pada waktu kompilasi. |
Pengelolaan referensi lemah | Membersihkan pendengar yang telah dikumpulkan oleh garbage collector secara otomatis untuk mencegah kebocoran memori. |
Dukungan pewarisan event | Berlangganan tipe event induk akan menerima semua tipe event turunannya. |
Isolasi multi-pemutar | Membedakan event dari pemutar yang berbeda menggunakan |
Kategori event
Event pemutar (PlayerEvents)
Ini adalah event perubahan status yang dihasilkan di dalam pemutar untuk memberi tahu komponen eksternal tentang status pemutar.
Event | Deskripsi | Data yang dibawa |
| Pemutar siap. |
|
| Frame pertama telah dirender. | - |
| Status pemutaran berubah. |
|
| Ukuran video berubah. |
|
| Progres pemutaran diperbarui. |
|
| Terjadi error pemutaran. |
|
| Buffering dimulai. | - |
| Progres buffering. |
|
| Buffering selesai. | - |
| Pengaturan kecepatan pemutaran selesai. |
|
| Pengambilan screenshot selesai. |
|
| Pengaturan loop selesai. |
|
| Pengaturan mute selesai. |
|
| Pengaturan mode isi selesai. |
|
| Pengaturan mirror selesai. |
|
| Pengaturan rotasi selesai. |
|
| Daftar definisi diperbarui. |
|
| Pemilihan definisi selesai. |
|
Perintah pemutar (PlayerCommand)
Ini adalah event perintah yang digunakan untuk mengontrol perilaku pemutar. Event ini dikirim oleh komponen UI atau kode eksternal.
Perintah | Deskripsi | Parameter |
| Memulai pemutaran. | - |
| Jeda pemutaran. | - |
| Beralih antara putar dan jeda. | - |
| Hentikan pemutaran. | - |
| Main ulang. | - |
| Lompat ke posisi tertentu. |
|
| Atur kecepatan pemutaran. |
|
| Ambil screenshot. | - |
| Atur putar ulang berulang. |
|
| Atur mute. |
|
| Atur mode isi. |
|
| Atur mirror. |
|
| Atur rotasi. |
|
| Ganti definisi. |
|
Event gesture (GestureEvents)
Event yang dihasilkan oleh operasi gesture pengguna. Event ini menggambarkan gesture itu sendiri dan tidak mengandung logika bisnis.
Event | Deskripsi | Data yang dibawa |
| Ketuk sekali. |
|
| Ketuk dua kali. |
|
| Tekan dan tahan untuk memulai. |
|
| Tekan lama selesai. | - |
| Seret horizontal dimulai. |
|
| Seret horizontal diperbarui. |
|
| Seret horizontal selesai. | - |
| Seret vertikal kiri dimulai. |
|
| Seret vertikal kiri diperbarui. |
|
| Seret vertikal kiri selesai. | - |
| Seret vertikal kanan dimulai. |
|
| Seret vertikal kanan diperbarui. |
|
| Seret vertikal kanan selesai. | - |
Event bilah kontrol (ControlBarEvents)
Event yang menyinkronkan status tampilan bilah kontrol.
Event | Deskripsi |
| Tampilkan bilah kontrol. |
| Sembunyikan bilah kontrol. |
| Atur ulang timer sembunyi otomatis. |
| Tampilkan antarmuka pengaturan. |
Event layar penuh (FullscreenEvents)
Ini adalah event untuk beralih ke dan dari mode layar penuh.
Event | Deskripsi |
| Beralih mode layar penuh. |
Event siklus hidup (PlayerLifecycleEvents)
Ini adalah event yang terkait dengan kebijakan siklus hidup pemutar. Anda dapat menggunakannya untuk mengamati pembuatan, penggunaan ulang, dan penghapusan instans pemutar.
Event | Deskripsi |
| Buat instans pemutar baru. |
| Hapus instans pemutar. |
| Gunakan ulang instans pemutar yang menganggur. |
| Akses instans pemutar aktif. |
| Instans pemutar dihapus. |
Event slot (SlotEvents)
Ini adalah event yang dihasilkan di dalam slot.
Event | Deskripsi |
| Klik tombol kembali di bilah atas. |
Penggunaan dasar
Sistem event mendukung dua strategi penggunaan:
Strategi | Deskripsi | Kasus penggunaan |
Berlangganan event | Mendengarkan perubahan status pemutar. | Pembaruan UI dan respons logika bisnis. |
Mengirim perintah | Mengontrol perilaku pemutar. | Interaksi pengguna dan kontrol eksternal. |
Strategi satu: Berlangganan event
Berlangganan event memerlukan tiga langkah: mendapatkan event bus, membuat pendengar, dan berlangganan event.
public class MyActivity extends AppCompatActivity {
private PlayerEventBus mEventBus;
private PlayerEventBus.EventListener<PlayerEvents.Info> mInfoListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 1. Dapatkan event bus singleton
mEventBus = PlayerEventBus.getInstance();
// 2. Buat pendengar
mInfoListener = event -> {
// Tangani pembaruan progres pemutaran
long position = event.currentPosition;
long duration = event.duration;
updateProgressUI(position, duration);
};
// 3. Berlangganan event
mEventBus.subscribe(PlayerEvents.Info.class, mInfoListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 4. Berhenti berlangganan untuk menghindari kebocoran memori
if (mEventBus != null && mInfoListener != null) {
mEventBus.unsubscribe(PlayerEvents.Info.class, mInfoListener);
}
}
} Strategi dua: Mengirim perintah
Anda dapat mengirim perintah menggunakan metode post() pada event bus:
// 1. Dapatkan event bus singleton
PlayerEventBus eventBus = PlayerEventBus.getInstance();
// 2. Dapatkan ID pemutar target
// [Skenario A: Di dalam slot] Gunakan mekanisme bawaan
// String playerId = getPlayerId();
// [Skenario B: Di Activity atau Fragment] Dapatkan dari controller Anda
String playerId = mAliPlayerKitController.getPlayer().getPlayerId();
// 3. Kirim perintah (controller yang dipasang dengan playerId ini akan mengeksekusinya)
eventBus.post(new PlayerCommand.Play(playerId));
// Contoh perintah umum lainnya:
eventBus.post(new PlayerCommand.Pause(playerId)); // Jeda pemutaran
eventBus.post(new PlayerCommand.Toggle(playerId)); // Beralih putar/jeda
eventBus.post(new PlayerCommand.Seek(playerId, 30000)); // Lompat ke 30 detik
eventBus.post(new PlayerCommand.SetSpeed(playerId, 1.5f)); // Atur kecepatan pemutaran menjadi 1,5× Penggunaan lanjutan
Cara menggunakan event di dalam slot
Anda dapat menggunakan event di dalam slot dengan dua cara: berlangganan event atau mengirim perintah.
Metode satu: Berlangganan event (disarankan: gunakan observedEvents())
Kelas BaseSlot menyediakan metode observedEvents() dan onEvent(). Framework mengelola siklus hidup langganan secara otomatis.
public class MySlot extends BaseSlot {
// 1. Nyatakan tipe event yang akan dilanggani
@Override
protected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class,
PlayerEvents.Info.class
);
}
// 2. Tangani callback event
@Override
protected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof PlayerEvents.StateChanged) {
PlayerEvents.StateChanged stateChanged = (PlayerEvents.StateChanged) event;
updatePlayPauseIcon(stateChanged.newState);
} else if (event instanceof PlayerEvents.Info) {
PlayerEvents.Info info = (PlayerEvents.Info) event;
updateProgress(info.currentPosition, info.duration);
}
}
} Metode dua: Mengirim perintah
Di dalam slot, Anda dapat mengirim perintah menggunakan metode postEvent():
public class MySlot extends BaseSlot {
private void onPlayPauseClick() {
// Dapatkan playerId (tersedia setelah onAttach)
String playerId = getPlayerId();
if (playerId != null) {
// Kirim perintah beralih putar/jeda
postEvent(new PlayerCommand.Toggle(playerId));
}
}
private void onSeekTo(long position) {
String playerId = getPlayerId();
if (playerId != null) {
// Kirim perintah seek
postEvent(new PlayerCommand.Seek(playerId, position));
}
}
} Untuk informasi lebih lanjut tentang penggunaan slot, lihat Sistem slot.
Cara menggunakan event di dalam strategi
Menggunakan event di dalam strategi mirip dengan penggunaannya di dalam slot. Anda dapat menggunakan observedEvents() dan onEvent() untuk berlangganan dan menangani event. Strategi memiliki karakteristik berikut:
Karakteristik | Deskripsi |
Terutama untuk pemantauan | Strategi biasanya hanya berlangganan event untuk analisis dan tidak mengirim perintah. |
Filter event | Dalam skenario multi-pemutar, Anda harus memfilter event menggunakan |
Akses konteks | Akses status dan data pemutar menggunakan |
Kode contoh:
public class MyAnalyticsStrategy extends BaseStrategy {
@Nullable@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(
PlayerEvents.StateChanged.class,
PlayerEvents.Info.class
);
}
@Overridepublic void onEvent(@NonNull PlayerEvent event) {
// Harus memfilter sumber event untuk menghindari gangguan silang dalam skenario multi-pemutar
if (!isCurrentPlayer(event)) return;
if (event instanceof PlayerEvents.StateChanged) {
// Tangani perubahan status
PlayerEvents.StateChanged stateChanged = (PlayerEvents.StateChanged) event;
logState(stateChanged.newState);
} else if (event instanceof PlayerEvents.Info) {
// Tangani progres pemutaran
PlayerEvents.Info info = (PlayerEvents.Info) event;
trackProgress(info.currentPosition, info.duration);
}
}
}Untuk informasi lebih lanjut tentang penggunaan strategi, lihat Sistem strategi.
Cara mengimplementasikan event kustom
Jika bisnis Anda memerlukan event kustom, Anda dapat memperluas PlayerEvent untuk membuat tipe event baru.
Buat kelas event.
Perluas
PlayerEventdan tambahkan bidang data spesifik bisnis Anda./** * Event pemutaran kustom */public class MyCustomEvent extends PlayerEvent { /** * Bidang data kustom */public final String customData; /** * Konstruktor * * @param playerId ID Pemutar * @param customData Data kustom */public MyCustomEvent(@NonNull String playerId, @NonNull String customData) { super(playerId); this.customData = customData; } }Terbitkan event.
// Buat dan terbitkan event MyCustomEvent event = new MyCustomEvent(playerId, "custom_data"); PlayerEventBus.getInstance().post(event);Berlangganan event kustom.
// Berlangganan event kustom PlayerEventBus.getInstance().subscribe(MyCustomEvent.class, event -> { // Tangani event kustom String data = event.customData; });
Cara mencapai isolasi event multi-pemutar
Setiap event membawa playerId. Anda dapat membandingkan playerId untuk mengisolasi event antar berbagai pemutar.
// Filter playerId saat berlangganan event
mEventBus.subscribe(PlayerEvents.Info.class, event -> {
// Hanya tangani event dari pemutar saat ini
if (mPlayerId.equals(event.playerId)) {
updateProgress(event.currentPosition, event.duration);
}
});Cara berlangganan perintah secara massal
Kelas PlayerCommand menyediakan larik ALL_COMMANDS untuk langganan massal.
// Berlangganan semua perintah pemutaran
for (Class<? extends PlayerCommand> commandClass : PlayerCommand.ALL_COMMANDS) {
mEventBus.subscribe(commandClass, mCommandListener);
}
// Berhenti berlangganan semua perintah pemutaran
for (Class<? extends PlayerCommand> commandClass : PlayerCommand.ALL_COMMANDS) {
mEventBus.unsubscribe(commandClass, mCommandListener);
}Praktik terbaik
Pengelolaan siklus hidup langganan
Skenario | Praktik yang disarankan | Alasan |
Activity atau Fragment | Berhenti berlangganan di | Mencegah kebocoran |
Di dalam slot | Gunakan | Framework menangani siklus hidup secara otomatis. |
Komponen singleton | Gunakan referensi lemah atau kelola secara manual. | Singleton bertahan lebih lama, sehingga pembersihan harus eksplisit. |
Keamanan thread
Event didistribusikan secara sinkron pada thread pemanggil. Perhatikan kasus berikut:
// Berlangganan di thread utama berarti callback juga berjalan di thread utama
runOnUiThread(() -> {
mEventBus.subscribe(PlayerEvents.Info.class, event -> {
// Callback ini berjalan di thread yang memposting event.
// Jika event diposting dari subthread, alihkan ke thread utama untuk memperbarui UI.
runOnUiThread(() -> updateUI(event));
});
});Waktu pengiriman perintah
Perintah | Waktu yang disarankan | Deskripsi |
| Kapan saja. | Perintah aman tanpa efek samping. |
| Setelah pemutar berada dalam status | Memastikan video telah dimuat. |
| Saat pemutaran atau jeda. | Memastikan pemutar telah diinisialisasi. |
| Setelah frame pertama dirender. | Memastikan ada frame yang dapat ditangkap. |
Catatan penting
Risiko konstruktor: Jangan berlangganan atau mengirim event di konstruktor. Objek belum sepenuhnya diinisialisasi, yang dapat menyebabkan exception pointer null.
playerIdbelum diinjeksikan.Pencocokan pendengar: Saat berhenti berlangganan, gunakan instance pendengar asli. Pencocokan bergantung pada kesamaan referensi objek.
Hindari operasi berdurasi panjang: Jangan melakukan operasi berdurasi panjang di callback event. Event didistribusikan secara sinkron dan akan memblokir thread.
Ambang Performa Event Frekuensi Tinggi:
Skenario: Event frekuensi tinggi seperti
PlayerEvents.Info(progres pemutaran),LoadingProgress, danGestureEvents.Update(hingga puluhan per detik).Hindari alokasi memori (seperti
new String()) dengan menggunakan kembali objek. Untuk mencegah refresh layout berat dan mengurangi repaint, gunakan kebijakan throttling dan filter nilai yang sama.
Contoh
Proyek ini mencakup contoh lengkap di playerkit-examples/example-event-system.
Fitur contoh
Fitur | Deskripsi |
Berlangganan progres pemutaran | Menerima event |
Mengirim perintah pemutaran | Mengontrol putar/jeda menggunakan |
Menampilkan log event | Menampilkan 20 pesan event terakhir. |
Jalankan contoh
Di aplikasi demo, Anda dapat memilih contoh Sistem Event untuk melihatnya.
Referensi API
Antarmuka inti
Antarmuka atau kelas | Deskripsi |
| Kelas dasar untuk semua event. |
| Kelas dasar untuk semua perintah. |
| Event bus yang mengelola langganan dan penerbitan. |
| Antarmuka pendengar event. |
Metode PlayerEventBus
Metode | Deskripsi |
| Dapatkan event bus singleton. |
| Berlangganan event tipe tertentu. |
| Berhenti berlangganan event tipe tertentu. |
| Berhenti berlangganan pendengar dari semua tipe event. |
| Terbitkan event. |
| Dapatkan jumlah pelanggan untuk tipe event tertentu. |
| Periksa apakah ada pelanggan untuk tipe event tertentu. |
| Hapus semua langganan (gunakan dengan hati-hati). |
Prinsip teknis
Mekanisme referensi lemah
Event bus menyimpan pendengar menggunakan WeakReference. Ini memberikan keuntungan berikut:
Pembersihan otomatis: Pendengar dihapus dari daftar langganan setelah garbage collection.
Pencegahan kebocoran: Kebocoran memori serius tidak akan terjadi meskipun Anda lupa berhenti berlangganan.
Akses aman: Sebelum distribusi, event bus memeriksa apakah setiap referensi lemah masih valid.
// Simpan pendengar menggunakan referensi lemah
List<WeakReference<EventListener<? extends PlayerEvent>>> listeners;
// Periksa validitas sebelum distribusi
for (WeakReference<EventListener> ref : listeners) {
EventListener listener = ref.get();
if (listener != null) {
listener.onEvent(event); // Panggil jika valid
} else {
listeners.remove(ref); // Bersihkan jika tidak valid
}
}Aliran data unidireksional dan loop sinkronisasi status
Sistem event hanya menangani notifikasi sementara. Ketika dikombinasikan dengan PlayerStateStore untuk persistensi status, sistem ini membentuk arsitektur aliran data unidireksional (UDF) yang ketat.
Aliran data:
Petunjuk | Mekanisme | Deskripsi |
Command up | UI → Controller | Slot mengirim event perintah untuk mengontrol perilaku pemutar. |
Status turun | Controller → UI | Controller menerbitkan event perubahan status untuk memberi tahu slot. |
State pull | UI → StateStore | Slot secara aktif menarik status saat ini (mekanisme cadangan). |
Mengapa diperlukan state pull?
Event bersifat sementara. Slot yang terikat belakangan mungkin melewatkan event historis setelah terikat. Dengan menggunakan getPlayerStateStore(), Anda dapat langsung menyinkronkan status pemutar saat ini tanpa bergantung pada waktu langganan event.
@Overridepublic void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Apakah slot terikat terlambat dan melewatkan Prepared, StateChanged, dll.?
// Tarik status saat ini untuk menginisialisasi UI
IPlayerStateStore stateStore = host.getPlayerStateStore();
PlayerState currentState = stateStore.getPlayState(); // Status pemutaran saat ini
long position = stateStore.getCurrentPosition(); // Posisi pemutaran saat ini
long duration = stateStore.getDuration(); // Durasi total video
// Inisialisasi UI berdasarkan status saat ini
updatePlayPauseIcon(currentState);
updateProgress(position, duration);
}
Arsitektur ini sepenuhnya menguraikan keterkaitan antara UI dan controller. UI tidak menyimpan referensi controller. UI hanya berlangganan event dan mengirim perintah. Controller tidak peduli siapa yang mendengarkan dan hanya menerbitkan event serta menangani perintah.
Dukungan pewarisan event
Jika Anda berlangganan tipe event induk, Anda akan menerima semua tipe event turunannya.
// Berlangganan PlayerEvent untuk menerima semua event
mEventBus.subscribe(PlayerEvent.class, event -> {
// Menerima semua subclass PlayerEvent
});
// Berlangganan PlayerCommand untuk menerima semua perintah
mEventBus.subscribe(PlayerCommand.class, event -> {
// Menerima semua subclass PlayerCommand
});FAQ
Mengapa saya tidak menerima event?
Anda dapat mengikuti langkah-langkah berikut untuk memecahkan masalah:
Status langganan: Pastikan Anda memanggil
subscribe().Pencocokan event: Tipe yang dilanggani harus sesuai dengan tipe yang diterbitkan.
Pemeriksaan identitas:
playerIdevent harus cocok.Pemeriksaan garbage collection: Verifikasi bahwa pendengar belum dikumpulkan oleh garbage collector terlalu dini.
Bagaimana cara men-debug event?
Anda dapat menggunakan LogHub untuk melihat log. Tag-nya adalah PlayerEventBus.
// Lihat log langganan
// I/PlayerEventBus: Subscribed to Info
// Lihat log penerbitan (aktifkan logging verbose)
// I/PlayerEventBus: Posted event: Info to 2 listenersKesalahan crash umum
Berikut adalah masalah paling umum yang dihadapi pengembang. Anda harus menghindarinya.
Kesalahan 1: Lupa berhenti berlangganan menyebabkan kebocoran memori
Kode salah:
public class MyActivity extends AppCompatActivity {
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Dilanggani tetapi tidak pernah berhenti berlangganan
PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, event -> {
updateUI(event.currentPosition); // Kebocoran Activity!
});
}
// Tidak ada onDestroy untuk berhenti berlangganan
}
Kode benar:
public class MyActivity extends AppCompatActivity {
private PlayerEventBus.EventListener<PlayerEvents.Info> mListener;
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListener = event -> updateUI(event.currentPosition);
PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, mListener);
}
@Overrideprotected void onDestroy() {
super.onDestroy();
// Selalu berhenti berlangganan
PlayerEventBus.getInstance().unsubscribe(PlayerEvents.Info.class, mListener);
}
}
Alasan crash: Ekspresi lambda menyimpan referensi ke Activity. Jika Anda tidak berhenti berlangganan, Activity tidak dapat dilepas.
Kesalahan 2: Berlangganan event di konstruktor
Kode salah:
public class MyComponent {
public MyComponent() {
// Berlangganan di konstruktor—objek belum sepenuhnya diinisialisasi
PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, event -> {
// Pada titik ini, 'this' mungkin belum sepenuhnya diinisialisasi
});
}
}
Pendekatan benar:
public class MyComponent {
private PlayerEventBus.EventListener<PlayerEvents.Info> mListener;
public void init() {
// Berlangganan di metode inisialisasi
mListener = event -> handleEvent(event);
PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, mListener);
}
public void destroy() {
// Berhenti berlangganan
PlayerEventBus.getInstance().unsubscribe(PlayerEvents.Info.class, mListener);
}
}
Alasan crash: Objek belum sepenuhnya diinisialisasi selama eksekusi konstruktor, yang dapat menyebabkan exception pointer null atau status tidak konsisten.
Kesalahan 3: Mengirim event di konstruktor slot
Kode salah:
public class MySlot extends BaseSlot {
public MySlot(Context context) {
super(context);
// playerId belum diatur di konstruktor
postEvent(new PlayerCommand.Toggle(getPlayerId())); // getPlayerId() mengembalikan null!
}
}
Pendekatan benar:
public class MySlot extends BaseSlot {
@Overridepublic void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Kirim event setelah onAttach
String playerId = getPlayerId();
if (playerId != null) {
postEvent(new PlayerCommand.Toggle(playerId));
}
}
}
Alasan crash: Metode getPlayerId() mengembalikan nilai valid hanya setelah metode onAttach() dipanggil. Metode ini mengembalikan null di konstruktor.
Kesalahan 4: Melakukan operasi berdurasi panjang di callback event
Kode salah:
mEventBus.subscribe(PlayerEvents.Info.class, event -> {
// Permintaan jaringan di callback memblokir distribusi event
String data = fetchFromNetwork(); // Memblokir!
updateUI(data);
});
Pendekatan benar:
mEventBus.subscribe(PlayerEvents.Info.class, event -> {
// Jalankan operasi berdurasi panjang secara asinkron
executorService.execute(() -> {
String data = fetchFromNetwork();
runOnUiThread(() -> updateUI(data));
});
});
Alasan crash: Distribusi event bersifat sinkron. Operasi berdurasi panjang memblokir semua distribusi event berikutnya, yang menyebabkan UI tersendat.
Kesalahan 5: Tidak menggunakan observedEvents() di slot
Kode salah:
public class MySlot extends BaseSlot {
@Overridepublic void onAttach(@NonNull SlotHost host) {
super.onAttach(host);
// Langganan manual memerlukan pembersihan manual
PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, mListener);
}
@Overridepublic void onDetach() {
// Lupa berhenti berlangganan—kebocoran!
super.onDetach();
}
}
Pendekatan benar:
public class MySlot extends BaseSlot {
// Gunakan observedEvents() untuk pengelolaan otomatis
@Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
return Arrays.asList(PlayerEvents.Info.class);
}
@Overrideprotected void onEvent(@NonNull PlayerEvent event) {
if (event instanceof PlayerEvents.Info) {
// Tangani event
}
}
}Alasan crash: Langganan manual memerlukan pembersihan manual. Jika Anda lupa berhenti berlangganan, akan terjadi kebocoran pendengar.
Jika Anda harus menggunakan langganan manual, Anda harus selalu berhenti berlangganan di metode onDetach():
@Overridepublic void onDetach() {
PlayerEventBus.getInstance().unsubscribe(PlayerEvents.Info.class, mListener);
super.onDetach();
}