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. |
|
|
Événement de commande |
Instruction visant à contrôler le comportement du lecteur, générée par l'interface utilisateur ou des composants externes. |
|
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
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é.
Traçabilité des événements : résout la confusion concernant la source des événements dans les scénarios impliquant plusieurs lecteurs.
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 |
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 |
|
|
Le lecteur est prêt. |
|
|
|
La première image a été rendue. |
- |
|
|
L'état de lecture a changé. |
|
|
|
La taille de la vidéo a changé. |
|
|
|
La progression de la lecture a été mise à jour. |
|
|
|
Une erreur de lecture s'est produite. |
|
|
|
La mise en buffer a commencé. |
- |
|
|
Progression de la mise en buffer. |
|
|
|
La mise en buffer est terminée. |
- |
|
|
Le paramétrage de la vitesse de lecture est terminé. |
|
|
|
La capture d'écran est terminée. |
|
|
|
Le paramétrage de la boucle est terminé. |
|
|
|
Le paramétrage du mode muet est terminé. |
|
|
|
Le paramétrage du mode de remplissage est terminé. |
|
|
|
Le paramétrage du miroir est terminé. |
|
|
|
Le paramétrage de la rotation est terminé. |
|
|
|
La liste des définitions a été mise à jour. |
|
|
|
La sélection de la définition est terminée. |
|
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 |
|
|
Démarrer la lecture. |
- |
|
|
Mettre la lecture en pause. |
- |
|
|
Basculer entre la lecture et la pause. |
- |
|
|
Arrêter la lecture. |
- |
|
|
Rejouer. |
- |
|
|
Aller à une position spécifiée. |
|
|
|
Définir la vitesse de lecture. |
|
|
|
Prendre une capture d'écran. |
- |
|
|
Activer la lecture en boucle. |
|
|
|
Activer le mode muet. |
|
|
|
Définir le mode de remplissage. |
|
|
|
Définir le miroir. |
|
|
|
Définir la rotation. |
|
|
|
Changer de définition. |
|
É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 |
|
|
Simple tapotement. |
|
|
|
Double tapotement. |
|
|
|
Appui long démarré. |
|
|
|
Appui long terminé. |
- |
|
|
Glissement horizontal démarré. |
|
|
|
Glissement horizontal mis à jour. |
|
|
|
Glissement horizontal terminé. |
- |
|
|
Glissement vertical gauche démarré. |
|
|
|
Glissement vertical gauche mis à jour. |
|
|
|
Glissement vertical gauche terminé. |
- |
|
|
Glissement vertical droit démarré. |
|
|
|
Glissement vertical droit mis à jour. |
|
|
|
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 |
|
|
Afficher la barre de contrôle. |
|
|
Masquer la barre de contrôle. |
|
|
Réinitialiser le minuteur de masquage automatique. |
|
|
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 |
|
|
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 |
|
|
Créer une nouvelle instance de lecteur. |
|
|
Détruire une instance de lecteur. |
|
|
Réutiliser une instance de lecteur inactive. |
|
|
Trouver une instance de lecteur active. |
|
|
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 |
|
|
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 |
|
Accéder au contexte |
Accédez à l'état et aux données du lecteur via |
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.
-
Créez une classe d'événement.
Étendez
PlayerEventet 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; } } -
Publiez l'événement.
// Create and post the event MyCustomEvent event = new MyCustomEvent(playerId, "custom_data"); PlayerEventBus.getInstance().post(event); -
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 |
Empêche les fuites de mémoire liées à l' |
|
Au sein d'un slot |
Utilisez |
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 |
|
|
À tout moment. |
Commandes sûres sans effets secondaires. |
|
|
Après que le lecteur soit |
Garantit que la vidéo est chargée. |
|
|
Pendant la lecture ou la pause. |
Garantit que le lecteur est initialisé. |
|
|
Après le rendu de la première image. |
Garantit qu'une image est disponible pour la capture. |
Remarques importantes
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
playerIdn'est pas encore injecté.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.
É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),LoadingProgressetGestureEvents.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 |
|
Envoyer des commandes de lecture |
Contrôlez la lecture/pause à l'aide de |
|
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 |
|
|
Classe de base pour tous les événements. |
|
|
Classe de base pour toutes les commandes. |
|
|
Bus d'événements qui gère les abonnements et la publication. |
|
|
Interface d'écouteur d'événements. |
Méthodes PlayerEventBus
|
Méthode |
Description |
|
|
Obtient le bus d'événements singleton. |
|
|
S'abonne aux événements d'un type spécifié. |
|
|
Annule l'abonnement aux événements d'un type spécifié. |
|
|
Annule l'abonnement d'un écouteur à tous les types d'événements. |
|
|
Publie un événement. |
|
|
Obtient le nombre d'abonnés pour un type d'événement spécifié. |
|
|
Vérifie s'il existe des abonnés pour un type d'événement spécifié. |
|
|
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 :
Statut de l'abonnement : Confirmez que vous avez appelé
subscribe().Correspondance des événements : Le type auquel vous êtes abonné doit correspondre au type publié.
Vérification d'identité : Le
playerIdde l'événement doit correspondre.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();
}