All Products
Search
Document Center

ApsaraVideo VOD:Sistem event

Last Updated:Sep 11, 2026

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.

StateChanged, Prepared, Error

Command event

Instruksi untuk mengontrol perilaku pemutar, dihasilkan oleh komponen UI atau eksternal.

Play, Pause, Seek, SetSpeed

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

  1. Penguraian keterkaitan komponen: Menghilangkan dependensi langsung antar komponen, sehingga mengurangi keterkaitan dan meningkatkan kemampuan penggantian serta pengujian.

  2. Ketertelusuran event: Mengatasi kebingungan sumber event dalam skenario multi-pemutar.

  3. 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 playerId.

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

Prepared

Pemutar siap.

duration: durasi total video

FirstFrameRendered

Frame pertama telah dirender.

-

StateChanged

Status pemutaran berubah.

oldState, newState

VideoSizeChanged

Ukuran video berubah.

width, height

Info

Progres pemutaran diperbarui.

currentPosition, duration, bufferedPosition

Error

Terjadi error pemutaran.

errorCode, errorMsg

LoadingBegin

Buffering dimulai.

-

LoadingProgress

Progres buffering.

percent, netSpeed

LoadingEnd

Buffering selesai.

-

SetSpeedCompleted

Pengaturan kecepatan pemutaran selesai.

speed

SnapshotCompleted

Pengambilan screenshot selesai.

result, snapshotPath, width, height

SetLoopCompleted

Pengaturan loop selesai.

loop

SetMuteCompleted

Pengaturan mute selesai.

mute

SetScaleTypeCompleted

Pengaturan mode isi selesai.

scaleType

SetMirrorTypeCompleted

Pengaturan mirror selesai.

mirrorType

SetRotationCompleted

Pengaturan rotasi selesai.

rotation

TrackQualityListUpdated

Daftar definisi diperbarui.

trackQualityList

TrackSelected

Pemilihan definisi selesai.

trackIndex

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

Play

Memulai pemutaran.

-

Pause

Jeda pemutaran.

-

Toggle

Beralih antara putar dan jeda.

-

Stop

Hentikan pemutaran.

-

Replay

Main ulang.

-

Seek

Lompat ke posisi tertentu.

position: posisi target dalam milidetik

SetSpeed

Atur kecepatan pemutaran.

speed: kecepatan pemutaran (0,3–3,0)

Snapshot

Ambil screenshot.

-

SetLoop

Atur putar ulang berulang.

loop: apakah akan diputar berulang

SetMute

Atur mute.

mute: apakah akan dimute

SetScaleType

Atur mode isi.

scaleType: mode isi

SetMirrorType

Atur mirror.

mirrorType: mode mirror

SetRotation

Atur rotasi.

rotationMode: sudut rotasi

SelectTrack

Ganti definisi.

trackQuality: definisi target

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

SingleTapEvent

Ketuk sekali.

x, y: koordinat ketukan

DoubleTapEvent

Ketuk dua kali.

x, y: koordinat ketukan

LongPressEvent

Tekan dan tahan untuk memulai.

x, y: koordinat tekan lama

LongPressEndEvent

Tekan lama selesai.

-

HorizontalDragStartEvent

Seret horizontal dimulai.

startX, startY

HorizontalDragUpdateEvent

Seret horizontal diperbarui.

deltaPercent: penambahan persentase

HorizontalDragEndEvent

Seret horizontal selesai.

-

LeftVerticalDragStartEvent

Seret vertikal kiri dimulai.

startX, startY

LeftVerticalDragUpdateEvent

Seret vertikal kiri diperbarui.

deltaPercent, currentPercent

LeftVerticalDragEndEvent

Seret vertikal kiri selesai.

-

RightVerticalDragStartEvent

Seret vertikal kanan dimulai.

startX, startY

RightVerticalDragUpdateEvent

Seret vertikal kanan diperbarui.

deltaPercent, currentPercent

RightVerticalDragEndEvent

Seret vertikal kanan selesai.

-

Event bilah kontrol (ControlBarEvents)

Event yang menyinkronkan status tampilan bilah kontrol.

Event

Deskripsi

Show

Tampilkan bilah kontrol.

Hide

Sembunyikan bilah kontrol.

ResetTimer

Atur ulang timer sembunyi otomatis.

ShowSettings

Tampilkan antarmuka pengaturan.

Event layar penuh (FullscreenEvents)

Ini adalah event untuk beralih ke dan dari mode layar penuh.

Event

Deskripsi

Toggle

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

PlayerCreated

Buat instans pemutar baru.

PlayerDestroyed

Hapus instans pemutar.

PlayerReused

Gunakan ulang instans pemutar yang menganggur.

PlayerHit

Akses instans pemutar aktif.

PlayerEvicted

Instans pemutar dihapus.

Event slot (SlotEvents)

Ini adalah event yang dihasilkan di dalam slot.

Event

Deskripsi

TopBarBackClicked

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 isCurrentPlayer(event).

Akses konteks

Akses status dan data pemutar menggunakan StrategyContext.

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.

  1. Buat kelas event.

    Perluas PlayerEvent dan 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;
        }
    }
  2. Terbitkan event.

    // Buat dan terbitkan event
    MyCustomEvent event = new MyCustomEvent(playerId, "custom_data");
    PlayerEventBus.getInstance().post(event);
  3. 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 onDestroy().

Mencegah kebocoran Activity.

Di dalam slot

Gunakan observedEvents() untuk pengelolaan otomatis.

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

Play/Pause/Toggle

Kapan saja.

Perintah aman tanpa efek samping.

Seek

Setelah pemutar berada dalam status Prepared.

Memastikan video telah dimuat.

SetSpeed

Saat pemutaran atau jeda.

Memastikan pemutar telah diinisialisasi.

Snapshot

Setelah frame pertama dirender.

Memastikan ada frame yang dapat ditangkap.

Catatan penting

  1. Risiko konstruktor: Jangan berlangganan atau mengirim event di konstruktor. Objek belum sepenuhnya diinisialisasi, yang dapat menyebabkan exception pointer null. playerId belum diinjeksikan.

  2. Pencocokan pendengar: Saat berhenti berlangganan, gunakan instance pendengar asli. Pencocokan bergantung pada kesamaan referensi objek.

  3. 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, dan GestureEvents.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 PlayerEvents.Info secara real time.

Mengirim perintah pemutaran

Mengontrol putar/jeda menggunakan PlayerCommand.Toggle.

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

PlayerEvent

Kelas dasar untuk semua event.

PlayerCommand

Kelas dasar untuk semua perintah.

PlayerEventBus

Event bus yang mengelola langganan dan penerbitan.

EventListener<T>

Antarmuka pendengar event.

Metode PlayerEventBus

Metode

Deskripsi

getInstance()

Dapatkan event bus singleton.

subscribe(eventType, listener)

Berlangganan event tipe tertentu.

unsubscribe(eventType, listener)

Berhenti berlangganan event tipe tertentu.

unsubscribe(listener)

Berhenti berlangganan pendengar dari semua tipe event.

post(event)

Terbitkan event.

getSubscriberCount(eventType)

Dapatkan jumlah pelanggan untuk tipe event tertentu.

hasSubscribers(eventType)

Periksa apakah ada pelanggan untuk tipe event tertentu.

unsubscribeAll()

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:

  1. Status langganan: Pastikan Anda memanggil subscribe().

  2. Pencocokan event: Tipe yang dilanggani harus sesuai dengan tipe yang diterbitkan.

  3. Pemeriksaan identitas: playerId event harus cocok.

  4. 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 listeners

Kesalahan 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();
}