Tous les produits
Search
Centre de documentation

ApsaraVideo VOD:Système d'événements

Dernière mise à jour :Sep 11, 2026

Cette rubrique présente les concepts fondamentaux, les catégories d'événements, les méthodes d'utilisation et les bonnes pratiques du système d'événements d'AliPlayerKit.

Concepts

Qu'est-ce qu'un événement ?

Un événement est une notification signalant un changement d'état ou un comportement survenant pendant l'exécution du lecteur. Chaque événement contient un ID de lecteur (playerId) permettant d'identifier sa source et d'isoler les événements entre plusieurs instances de lecteur.

Les événements dans AliPlayerKit se répartissent en deux catégories principales :

Type

Description

Exemple

Événement d'état

Notification des changements d'état du lecteur, générée en interne par le lecteur.

StateChanged, Prepared, Error

Événement de commande

Instruction visant à contrôler le comportement du lecteur, générée par l'interface utilisateur ou des composants externes.

Play, Pause, Seek, SetSpeed

Tous les événements héritent de la classe de base PlayerEvent afin de garantir une structure d'événement cohérente.

Qu'est-ce que le système d'événements ?

Le système d'événements est une architecture de communication dédiée à la gestion de l'abonnement et de la publication d'événements. Son composant central est PlayerEventBus.

Il offre les fonctionnalités suivantes :

  • Abonnement sécurisé par type : vous pouvez vous abonner par type d'événement. La sécurité des types est garantie au moment de la compilation.

  • Sécurité des threads : prend en charge la publication et l'abonnement aux événements dans des environnements multithread.

  • Gestion des références faibles : stocke les écouteurs à l'aide de références faibles et nettoie automatiquement les écouteurs qui ont été récupérés par le garbage collector.

  • Isolation des événements : isole les événements entre plusieurs instances de lecteur en utilisant playerId.

Le système d'événements découple entièrement les composants d'interface utilisateur (Slots) des contrôleurs. L'interface utilisateur se contente d'envoyer des commandes et de s'abonner aux changements d'état, sans se soucier de la manière dont les commandes sont exécutées. Les contrôleurs traitent uniquement les commandes et publient les changements d'état, sans se soucier de savoir qui écoute.

Fonctionnalités

Problèmes résolus

  1. Découplage des composants : supprime les dépendances directes entre les composants, ce qui réduit le couplage et améliore la remplaçabilité et la testabilité.

  2. Traçabilité des événements : résout la confusion concernant la source des événements dans les scénarios impliquant plusieurs lecteurs.

  3. Séparation des responsabilités : sépare les composants d'interface utilisateur de la logique métier et clarifie les limites des responsabilités.

Valeur principale

Le système d'événements normalise la communication entre les composants. Les développeurs peuvent l'utiliser de la manière suivante :

Méthode d'utilisation

Description

Avantage

S'abonner aux événements

Écouter les changements d'état du lecteur.

Découple l'interface utilisateur de la logique métier et met à jour l'interface de manière réactive.

Envoyer des commandes

Contrôler le comportement du lecteur.

L'interface utilisateur n'a pas besoin de détenir une référence au contrôleur. L'interaction repose sur les commandes.

Personnaliser les événements

Étendre les types d'événements.

S'adapte à des scénarios métier spécifiques tout en maintenant la cohérence architecturale.

Avantages architecturaux :

  • Découplage : les composants communiquent via des événements et n'ont aucune dépendance directe.

  • Testabilité : vous pouvez tester les composants indépendamment en simulant les interactions avec des événements.

  • Extensibilité : vous pouvez ajouter des types d'événements personnalisés sans modifier le code du framework.

  • Sécurité des threads : fonctionne dans des environnements multithread sans nécessiter de synchronisation supplémentaire.

Capacités principales

Capacité

Description

Abonnement sécurisé par type

Abonnement par type d'événement. La vérification des types s'effectue au moment de la compilation.

Gestion des références faibles

Nettoie automatiquement les écouteurs qui ont été récupérés par le garbage collector pour éviter les fuites de mémoire.

Prise en charge de l'héritage des événements

L'abonnement à un type d'événement parent permet de recevoir tous les types d'événements enfants.

Isolation multi-lecteurs

Distingue les événements provenant de différents lecteurs en utilisant playerId.

Catégories d'événements

Événements du lecteur (PlayerEvents)

Il s'agit d'événements de changement d'état générés en interne par le lecteur pour notifier les composants externes de l'état du lecteur.

Événement

Description

Données transportées

Prepared

Le lecteur est prêt.

duration : durée totale de la vidéo

FirstFrameRendered

La première image a été rendue.

-

StateChanged

L'état de lecture a changé.

oldState, newState

VideoSizeChanged

La taille de la vidéo a changé.

width, height

Info

La progression de la lecture a été mise à jour.

currentPosition, duration, bufferedPosition

Error

Une erreur de lecture s'est produite.

errorCode, errorMsg

LoadingBegin

La mise en buffer a commencé.

-

LoadingProgress

Progression de la mise en buffer.

percent, netSpeed

LoadingEnd

La mise en buffer est terminée.

-

SetSpeedCompleted

Le paramétrage de la vitesse de lecture est terminé.

speed

SnapshotCompleted

La capture d'écran est terminée.

result, snapshotPath, width, height

SetLoopCompleted

Le paramétrage de la boucle est terminé.

loop

SetMuteCompleted

Le paramétrage du mode muet est terminé.

mute

SetScaleTypeCompleted

Le paramétrage du mode de remplissage est terminé.

scaleType

SetMirrorTypeCompleted

Le paramétrage du miroir est terminé.

mirrorType

SetRotationCompleted

Le paramétrage de la rotation est terminé.

rotation

TrackQualityListUpdated

La liste des définitions a été mise à jour.

trackQualityList

TrackSelected

La sélection de la définition est terminée.

trackIndex

Commandes du lecteur (PlayerCommand)

Il s'agit d'événements de commande utilisés pour contrôler le comportement du lecteur. Ils sont envoyés par les composants d'interface utilisateur ou par du code externe.

Commande

Description

Paramètres

Play

Démarrer la lecture.

-

Pause

Mettre la lecture en pause.

-

Toggle

Basculer entre la lecture et la pause.

-

Stop

Arrêter la lecture.

-

Replay

Rejouer.

-

Seek

Aller à une position spécifiée.

position : position cible en millisecondes

SetSpeed

Définir la vitesse de lecture.

speed : vitesse de lecture (0,3–3,0)

Snapshot

Prendre une capture d'écran.

-

SetLoop

Activer la lecture en boucle.

loop : activer ou non la boucle

SetMute

Activer le mode muet.

mute : activer ou non le mode muet

SetScaleType

Définir le mode de remplissage.

scaleType : mode de remplissage

SetMirrorType

Définir le miroir.

mirrorType : mode miroir

SetRotation

Définir la rotation.

rotationMode : angle de rotation

SelectTrack

Changer de définition.

trackQuality : définition cible

Événements gestuels (GestureEvents)

Événements générés par les gestes de l'utilisateur. Ces événements décrivent le geste lui-même et ne contiennent aucune logique métier.

Événement

Description

Données transportées

SingleTapEvent

Simple tapotement.

x, y : coordonnées du tapotement

DoubleTapEvent

Double tapotement.

x, y : coordonnées du tapotement

LongPressEvent

Appui long démarré.

x, y : coordonnées de l'appui long

LongPressEndEvent

Appui long terminé.

-

HorizontalDragStartEvent

Glissement horizontal démarré.

startX, startY

HorizontalDragUpdateEvent

Glissement horizontal mis à jour.

deltaPercent : incrément en pourcentage

HorizontalDragEndEvent

Glissement horizontal terminé.

-

LeftVerticalDragStartEvent

Glissement vertical gauche démarré.

startX, startY

LeftVerticalDragUpdateEvent

Glissement vertical gauche mis à jour.

deltaPercent, currentPercent

LeftVerticalDragEndEvent

Glissement vertical gauche terminé.

-

RightVerticalDragStartEvent

Glissement vertical droit démarré.

startX, startY

RightVerticalDragUpdateEvent

Glissement vertical droit mis à jour.

deltaPercent, currentPercent

RightVerticalDragEndEvent

Glissement vertical droit terminé.

-

Événements de la barre de contrôle (ControlBarEvents)

Événements synchronisant l'état d'affichage de la barre de contrôle.

Événement

Description

Show

Afficher la barre de contrôle.

Hide

Masquer la barre de contrôle.

ResetTimer

Réinitialiser le minuteur de masquage automatique.

ShowSettings

Afficher l'interface des paramètres.

Événements plein écran (FullscreenEvents)

Il s'agit d'événements permettant de basculer vers et depuis le mode plein écran.

Événement

Description

Toggle

Basculer le mode plein écran.

Événements de cycle de vie (PlayerLifecycleEvents)

Il s'agit d'événements liés aux politiques de cycle de vie du lecteur. Vous pouvez les utiliser pour observer la création, la réutilisation et la destruction des instances de lecteur.

Événement

Description

PlayerCreated

Créer une nouvelle instance de lecteur.

PlayerDestroyed

Détruire une instance de lecteur.

PlayerReused

Réutiliser une instance de lecteur inactive.

PlayerHit

Trouver une instance de lecteur active.

PlayerEvicted

Une instance de lecteur a été évincée.

Événements de slot (SlotEvents)

Il s'agit d'événements générés à l'intérieur des slots.

Événement

Description

TopBarBackClicked

Cliquer sur le bouton Retour dans la barre supérieure.

Utilisation de base

Le système d'événements prend en charge deux stratégies d'utilisation :

Stratégie

Description

Cas d'utilisation

S'abonner aux événements

Écouter les changements d'état du lecteur.

Mises à jour de l'interface utilisateur et réponses de la logique métier.

Envoyer des commandes

Contrôler le comportement du lecteur.

Interaction utilisateur et contrôle externe.

Stratégie un : s'abonner aux événements

L'abonnement aux événements nécessite trois étapes : obtenir le bus d'événements, créer un écouteur et s'abonner aux événements.

public class MyActivity extends AppCompatActivity {

    private PlayerEventBus mEventBus;
    private PlayerEventBus.EventListener<PlayerEvents.Info> mInfoListener;

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

        // 1. Get the singleton event bus
        mEventBus = PlayerEventBus.getInstance();

        // 2. Create a listener
        mInfoListener = event -> {
            // Handle playback progress updates
            long position = event.currentPosition;
            long duration = event.duration;
            updateProgressUI(position, duration);
        };

        // 3. Subscribe to events
        mEventBus.subscribe(PlayerEvents.Info.class, mInfoListener);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 4. Unsubscribe to avoid memory leaks
        if (mEventBus != null && mInfoListener != null) {
            mEventBus.unsubscribe(PlayerEvents.Info.class, mInfoListener);
        }
    }
}     

Stratégie deux : envoyer des commandes

Vous pouvez envoyer des commandes en utilisant la méthode post() sur le bus d'événements :

// 1. Get the singleton event bus
PlayerEventBus eventBus = PlayerEventBus.getInstance();

// 2. Get the target player ID
// [Scenario A: Inside a slot] Use the built-in mechanism
// String playerId = getPlayerId();
// [Scenario B: In an Activity or Fragment] Get it from your controller
String playerId = mAliPlayerKitController.getPlayer().getPlayerId();

// 3. Post a command (the controller mounted with this playerId will execute it)
eventBus.post(new PlayerCommand.Play(playerId));

// Other common command examples:
eventBus.post(new PlayerCommand.Pause(playerId));               // Pause playback
eventBus.post(new PlayerCommand.Toggle(playerId));              // Toggle play/pause
eventBus.post(new PlayerCommand.Seek(playerId, 30000));         // Jump to 30 seconds
eventBus.post(new PlayerCommand.SetSpeed(playerId, 1.5f));      // Set playback speed to 1.5×          

Utilisation avancée

Comment utiliser les événements dans un slot

Vous pouvez utiliser les événements au sein d'un slot de deux manières : en vous abonnant aux événements ou en envoyant des commandes.

Méthode 1 : S'abonner aux événements (recommandé : utilisez observedEvents())

La classe BaseSlot fournit les méthodes observedEvents() et onEvent(). Le framework gère automatiquement le cycle de vie de l'abonnement.

public class MySlot extends BaseSlot {

    // 1. Declare the event types to subscribe to
    @Override
    protected List<Class<? extends PlayerEvent>> observedEvents() {
        return Arrays.asList(
            PlayerEvents.StateChanged.class,
            PlayerEvents.Info.class
        );
    }

    // 2. Handle event callbacks
    @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);
        }
    }
}          

Méthode 2 : Envoyer des commandes

Au sein d'un slot, vous pouvez envoyer des commandes à l'aide de la méthode postEvent() :

public class MySlot extends BaseSlot {

    private void onPlayPauseClick() {
        // Get the playerId (available after onAttach)
        String playerId = getPlayerId();
        if (playerId != null) {
            // Send the toggle play/pause command
            postEvent(new PlayerCommand.Toggle(playerId));
        }
    }

    private void onSeekTo(long position) {
        String playerId = getPlayerId();
        if (playerId != null) {
            // Send the seek command
            postEvent(new PlayerCommand.Seek(playerId, position));
        }
    }
}      

Pour plus d'informations sur l'utilisation des slots, consultez la rubrique Système de slots.

Comment utiliser les événements dans une stratégie

L'utilisation des événements dans une stratégie est similaire à leur utilisation dans un slot. Vous pouvez utiliser observedEvents() et onEvent() pour vous abonner aux événements et les gérer. Les stratégies présentent les caractéristiques suivantes :

Caractéristique

Description

Principalement pour la surveillance

Les stratégies s'abonnent généralement aux événements uniquement à des fins d'analyse et n'envoient pas de commandes.

Filtrer les événements

Dans les scénarios multi-lecteurs, vous devez filtrer les événements à l'aide de isCurrentPlayer(event).

Accéder au contexte

Accédez à l'état et aux données du lecteur via StrategyContext.

Exemple de code :

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) {
        // Must filter event source to avoid cross-talk in multi-player scenarios
        if (!isCurrentPlayer(event)) return;

        if (event instanceof PlayerEvents.StateChanged) {
            // Handle state changes
            PlayerEvents.StateChanged stateChanged = (PlayerEvents.StateChanged) event;
            logState(stateChanged.newState);
        } else if (event instanceof PlayerEvents.Info) {
            // Handle playback progress
            PlayerEvents.Info info = (PlayerEvents.Info) event;
            trackProgress(info.currentPosition, info.duration);
        }
    }
}

Pour plus d'informations sur l'utilisation des stratégies, consultez la rubrique Système de stratégies.

Comment implémenter des événements personnalisés

Si votre activité métier nécessite des événements personnalisés, vous pouvez étendre PlayerEvent pour créer un nouveau type d'événement.

  1. Créez une classe d'événement.

    Étendez PlayerEvent et ajoutez vos champs de données spécifiques à l'activité métier.

    /**
     * Custom playback event
     */public class MyCustomEvent extends PlayerEvent {
    
        /**
         * Custom data field
         */public final String customData;
    
        /**
         * Constructor
         *
         * @param playerId Player ID
         * @param customData Custom data
         */public MyCustomEvent(@NonNull String playerId, @NonNull String customData) {
            super(playerId);
            this.customData = customData;
        }
    }
  2. Publiez l'événement.

    // Create and post the event
    MyCustomEvent event = new MyCustomEvent(playerId, "custom_data");
    PlayerEventBus.getInstance().post(event);
  3. Abonnez-vous à l'événement personnalisé.

    // Subscribe to the custom event
    PlayerEventBus.getInstance().subscribe(MyCustomEvent.class, event -> {
        // Handle the custom event
        String data = event.customData;
    });

Comment assurer l'isolation des événements pour plusieurs lecteurs

Chaque événement contient un playerId. Vous pouvez comparer le playerId pour isoler les événements entre plusieurs lecteurs.

// Filter playerId when subscribing to events
mEventBus.subscribe(PlayerEvents.Info.class, event -> {
    // Only handle events from the current player
    if (mPlayerId.equals(event.playerId)) {
        updateProgress(event.currentPosition, event.duration);
    }
});

Comment s'abonner aux commandes en masse

La classe PlayerCommand fournit le tableau ALL_COMMANDS pour les abonnements groupés.

// Subscribe to all playback commands
for (Class<? extends PlayerCommand> commandClass : PlayerCommand.ALL_COMMANDS) {
    mEventBus.subscribe(commandClass, mCommandListener);
}

// Unsubscribe from all playback commands
for (Class<? extends PlayerCommand> commandClass : PlayerCommand.ALL_COMMANDS) {
    mEventBus.unsubscribe(commandClass, mCommandListener);
}

Bonnes pratiques

Gestion du cycle de vie des abonnements

Scénario

Pratique recommandée

Raison

Activity ou Fragment

Annulez l'abonnement dans onDestroy().

Empêche les fuites de mémoire liées à l'Activity.

Au sein d'un slot

Utilisez observedEvents() pour une gestion automatique.

Le framework gère automatiquement le cycle de vie.

Composant singleton

Utilisez des références faibles ou gérez manuellement.

Les singletons ayant une durée de vie plus longue, le nettoyage doit être explicite.

Sécurité des threads

Les événements sont distribués de manière synchrone sur le thread appelant. Tenez compte des cas suivants :

// Subscribing on the main thread means callbacks also run on the main thread
runOnUiThread(() -> {
    mEventBus.subscribe(PlayerEvents.Info.class, event -> {
        // This callback runs on the thread that posted the event.
        // If the event was posted from a subthread, switch to the main thread to update the UI.
        runOnUiThread(() -> updateUI(event));
    });
});

Moment d'envoi des commandes

Commande

Moment recommandé

Description

Play/Pause/Toggle

À tout moment.

Commandes sûres sans effets secondaires.

Seek

Après que le lecteur soit Prepared.

Garantit que la vidéo est chargée.

SetSpeed

Pendant la lecture ou la pause.

Garantit que le lecteur est initialisé.

Snapshot

Après le rendu de la première image.

Garantit qu'une image est disponible pour la capture.

Remarques importantes

  1. Risque lié au constructeur : Ne vous abonnez pas aux événements et n'en envoyez pas dans les constructeurs. L'objet n'est pas entièrement initialisé, ce qui peut provoquer des exceptions de pointeur nul. Le playerId n'est pas encore injecté.

  2. Correspondance des écouteurs : Lors de la désinscription, utilisez l'instance d'écouteur d'origine. La correspondance repose sur l'égalité des références d'objets.

  3. Évitez les opérations longues : N'effectuez pas d'opérations longues dans les rappels d'événements. Les événements sont distribués de manière synchrone et bloqueront le thread.

Seuil de performance pour les événements à haute fréquence :

  • Scénarios : Événements à haute fréquence tels que PlayerEvents.Info (progression de la lecture), LoadingProgress et GestureEvents.Update (jusqu'à plusieurs dizaines par seconde).

  • Évitez l'allocation de mémoire (comme new String()) en réutilisant les objets. Pour éviter les actualisations lourdes de la mise en page et réduire les repaints, utilisez une politique de limitation du débit (throttling) et un filtrage des valeurs identiques.

Exemples

Le projet inclut un exemple complet dans playerkit-examples/example-event-system.

Fonctionnalités de l'exemple

Fonctionnalité

Description

S'abonner à la progression de la lecture

Recevez les événements PlayerEvents.Info en temps réel.

Envoyer des commandes de lecture

Contrôlez la lecture/pause à l'aide de PlayerCommand.Toggle.

Afficher les journaux d'événements

Affichez les 20 derniers messages d'événements.

Exécuter l'exemple

Dans l'application de démonstration, vous pouvez sélectionner l'exemple Event System pour le consulter.

Référence API

Interfaces principales

Interface ou classe

Description

PlayerEvent

Classe de base pour tous les événements.

PlayerCommand

Classe de base pour toutes les commandes.

PlayerEventBus

Bus d'événements qui gère les abonnements et la publication.

EventListener<T>

Interface d'écouteur d'événements.

Méthodes PlayerEventBus

Méthode

Description

getInstance()

Obtient le bus d'événements singleton.

subscribe(eventType, listener)

S'abonne aux événements d'un type spécifié.

unsubscribe(eventType, listener)

Annule l'abonnement aux événements d'un type spécifié.

unsubscribe(listener)

Annule l'abonnement d'un écouteur à tous les types d'événements.

post(event)

Publie un événement.

getSubscriberCount(eventType)

Obtient le nombre d'abonnés pour un type d'événement spécifié.

hasSubscribers(eventType)

Vérifie s'il existe des abonnés pour un type d'événement spécifié.

unsubscribeAll()

Efface tous les abonnements (à utiliser avec prudence).

Principes techniques

Mécanisme de référence faible

Le bus d'événements stocke les écouteurs à l'aide de WeakReference. Cela offre les avantages suivants :

  • Nettoyage automatique : Les écouteurs sont supprimés de la liste d'abonnements après le garbage collection.

  • Prévention des fuites : Aucune fuite de mémoire grave ne se produira même si vous oubliez d'annuler l'abonnement.

  • Accès sécurisé : Avant la distribution, le bus d'événements vérifie si chaque référence faible est toujours valide.

// Store listeners using weak references
List<WeakReference<EventListener<? extends PlayerEvent>>> listeners;

// Check validity before dispatching
for (WeakReference<EventListener> ref : listeners) {
    EventListener listener = ref.get();
    if (listener != null) {
        listener.onEvent(event);  // Call if valid
    } else {
        listeners.remove(ref);  // Clean up if invalid
    }
}

Flux de données unidirectionnel et boucle de synchronisation d'état

Le système d'événements gère uniquement les notifications transitoires. Combiné au PlayerStateStore pour la persistance de l'état, il forme une architecture stricte de flux de données unidirectionnel (UDF).

Flux de données :

Direction

Mécanisme

Description

Commande ascendante

UI → Controller

Un slot envoie des événements de commande pour contrôler le comportement du lecteur.

État descendant

Controller → UI

Le contrôleur publie des événements de changement d'état pour notifier les slots.

Extraction d'état

UI → StateStore

Un slot extrait activement l'état actuel (mécanisme de secours).

Pourquoi l'extraction d'état est-elle nécessaire ?

Les événements sont transitoires. Un slot lié tardivement peut manquer les événements historiques après son attachement. En utilisant getPlayerStateStore(), vous pouvez synchroniser instantanément l'état actuel du lecteur sans dépendre du moment de l'abonnement aux événements.

@Overridepublic void onAttach(@NonNull SlotHost host) {
    super.onAttach(host);

    // Did the slot attach late and miss Prepared, StateChanged, etc.?
    // Pull the current state to initialize the UI
    IPlayerStateStore stateStore = host.getPlayerStateStore();
    PlayerState currentState = stateStore.getPlayState();  // Current playback state
    long position = stateStore.getCurrentPosition();       // Current playback position
    long duration = stateStore.getDuration();              // Total video duration

    // Initialize the UI based on the current state
    updatePlayPauseIcon(currentState);
    updateProgress(position, duration);
}

Cette architecture découple complètement l'interface utilisateur du contrôleur. L'interface utilisateur ne conserve pas de référence au contrôleur. Elle s'abonne uniquement aux événements et envoie des commandes. Le contrôleur ne se soucie pas de savoir qui écoute et se contente de publier des événements et de traiter les commandes.

Prise en charge de l'héritage des événements

Si vous vous abonnez à un type d'événement parent, vous recevrez tous les types d'événements enfants.

// Subscribe to PlayerEvent to receive all events
mEventBus.subscribe(PlayerEvent.class, event -> {
    // Receive all PlayerEvent subclasses
});

// Subscribe to PlayerCommand to receive all commands
mEventBus.subscribe(PlayerCommand.class, event -> {
    // Receive all PlayerCommand subclasses
});

FAQ

Pourquoi ne recevais-je pas d'événements ?

Vous pouvez suivre ces étapes pour résoudre le problème :

  1. Statut de l'abonnement : Confirmez que vous avez appelé subscribe().

  2. Correspondance des événements : Le type auquel vous êtes abonné doit correspondre au type publié.

  3. Vérification d'identité : Le playerId de l'événement doit correspondre.

  4. Vérification du garbage collection : Vérifiez que l'écouteur n'a pas été prématurément collecté par le garbage collector.

Comment déboguer les événements ?

Vous pouvez utiliser LogHub pour afficher les journaux. Le tag est PlayerEventBus.

// View subscription logs
// I/PlayerEventBus: Subscribed to Info
// View publication logs (enable verbose logging)
// I/PlayerEventBus: Posted event: Info to 2 listeners

Erreurs courantes provoquant des plantages

Voici les problèmes les plus fréquemment rencontrés par les développeurs. Vous devez les éviter.

Erreur 1 : Oubli de désabonnement provoquant des fuites de mémoire

Code incorrect :

public class MyActivity extends AppCompatActivity {

    @Overrideprotected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Subscribed but never unsubscribed
        PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, event -> {
            updateUI(event.currentPosition);  // Activity leak!
        });
    }

    // No onDestroy to unsubscribe
}

Code correct :

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();
        // Always unsubscribe
        PlayerEventBus.getInstance().unsubscribe(PlayerEvents.Info.class, mListener);
    }
}

Raison du plantage : Les expressions lambda conservent une référence à l'Activity. Si vous n'annulez pas l'abonnement, l'Activity ne peut pas être libérée.

Erreur 2 : Abonnement aux événements dans les constructeurs

Code incorrect :

public class MyComponent {

    public MyComponent() {
        // Subscribing in constructor—object not fully initialized
        PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, event -> {
            // At this point, 'this' may not be fully initialized
        });
    }
}

Approche correcte :

public class MyComponent {

    private PlayerEventBus.EventListener<PlayerEvents.Info> mListener;

    public void init() {
        // Subscribe in an initialization method
        mListener = event -> handleEvent(event);
        PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, mListener);
    }

    public void destroy() {
        // Unsubscribe
        PlayerEventBus.getInstance().unsubscribe(PlayerEvents.Info.class, mListener);
    }
}

Raison du plantage : L'objet n'est pas entièrement initialisé lors de l'exécution du constructeur, ce qui peut provoquer des exceptions de pointeur nul ou un état incohérent.

Erreur 3 : Envoi d'événements dans les constructeurs de slots

Code incorrect :

public class MySlot extends BaseSlot {

    public MySlot(Context context) {
        super(context);
        // playerId not set in constructor
        postEvent(new PlayerCommand.Toggle(getPlayerId()));  // getPlayerId() returns null!
    }
}

Approche correcte :

public class MySlot extends BaseSlot {

    @Overridepublic void onAttach(@NonNull SlotHost host) {
        super.onAttach(host);
        // Send events after onAttach
        String playerId = getPlayerId();
        if (playerId != null) {
            postEvent(new PlayerCommand.Toggle(playerId));
        }
    }
}

Raison du plantage : La méthode getPlayerId() renvoie une valeur valide uniquement après l'appel de la méthode onAttach(). Elle renvoie null dans le constructeur.

Erreur 4 : Exécution d'opérations longues dans les rappels d'événements

Code incorrect :

mEventBus.subscribe(PlayerEvents.Info.class, event -> {
    // Network request in callback blocks event dispatch
    String data = fetchFromNetwork();  // Blocks!
    updateUI(data);
});

Approche correcte :

mEventBus.subscribe(PlayerEvents.Info.class, event -> {
    // Run long-running operations asynchronously
    executorService.execute(() -> {
        String data = fetchFromNetwork();
        runOnUiThread(() -> updateUI(data));
    });
});

Raison du plantage : La distribution des événements est synchrone. Les opérations longues bloquent toutes les distributions d'événements suivantes, ce qui provoque des saccades de l'interface utilisateur.

Erreur 5 : Non-utilisation de observedEvents() dans les slots

Code incorrect :

public class MySlot extends BaseSlot {

    @Overridepublic void onAttach(@NonNull SlotHost host) {
        super.onAttach(host);
        // Manual subscription requires manual cleanup
        PlayerEventBus.getInstance().subscribe(PlayerEvents.Info.class, mListener);
    }

    @Overridepublic void onDetach() {
        // Forgot to unsubscribe—leak!
        super.onDetach();
    }
}

Approche correcte :

public class MySlot extends BaseSlot {

    // Use observedEvents() for automatic management
    @Overrideprotected List<Class<? extends PlayerEvent>> observedEvents() {
        return Arrays.asList(PlayerEvents.Info.class);
    }

    @Overrideprotected void onEvent(@NonNull PlayerEvent event) {
        if (event instanceof PlayerEvents.Info) {
            // Handle event
        }
    }
}

Raison du plantage : L'abonnement manuel nécessite un nettoyage manuel. Si vous oubliez d'annuler l'abonnement, des fuites d'écouteurs se produiront.

Si vous devez utiliser un abonnement manuel, vous devez toujours annuler l'abonnement dans la méthode onDetach() :

@Overridepublic void onDetach() {
    PlayerEventBus.getInstance().unsubscribe(PlayerEvents.Info.class, mListener);
    super.onDetach();
}