一文教你更安全更简单的使用event_bus

710 阅读4分钟

一文教你更简单的使用event_bus,不再写繁杂的代码,手动调用取消订阅的方法。

event_bus 是 Dart 中一个非常实用的事件总线库,它基于发布 - 订阅模式,允许应用程序的不同部分之间进行松耦合的通信。

github地址

基本使用

1. 创建事件类

首先,你需要定义不同类型的事件,每个事件通常是一个简单的类。例如:

// 定义事件类
class UserLoggedInEvent {
  final String username;
  UserLoggedInEvent(this.username);
}

2. 创建 EventBus 实例

在应用程序中创建一个 EventBus 实例,通常将其作为单例使用,以便在整个应用中共享。

import 'package:event_bus/event_bus.dart'; 
// 创建 EventBus 单例 
EventBus eventBus = EventBus();

3. 订阅事件

使用 eventBus.on<T>() 方法来订阅特定类型的事件。这个方法返回一个 Stream,你可以监听这个流来处理事件。

// 订阅 UserLoggedInEvent 事件
var userLoggedInSubscription = eventBus.on<UserLoggedInEvent>().listen((event) {
  print('User ${event.username} logged in.');
});

// 订阅 UserLoggedOutEvent 事件
var userLoggedOutSubscription = eventBus.on<UserLoggedOutEvent>().listen((event) {
  print('User logged out.');
});
 

4. 发布事件

使用 eventBus.fire() 方法来发布事件。

// 发布 UserLoggedInEvent 事件
eventBus.fire(UserLoggedInEvent('JohnDoe'));

// 发布 UserLoggedOutEvent 事件
eventBus.fire(UserLoggedOutEvent());

5. 取消订阅

当你不再需要监听某个事件时,应该取消订阅以避免内存泄漏。

// 取消订阅
userLoggedInSubscription.cancel();
userLoggedOutSubscription.cancel();

思考

封装event_bus的目的可能有几个方面:简化API,添加日志或错误处理,支持单例模式,或者整合其他功能如依赖注入。我们希望有一个更友好的接口,比如通过注解自动订阅事件,或者统一管理事件监听器的生命周期,防止内存泄漏。

event_bus包本身提供了基本的发布和订阅功能,但需要我们手动管理订阅的取消。封装的时候,可以考虑自动取消订阅,比如结合Flutter的Widget生命周期,或者在Dispose时自动取消。或者,我们可能希望支持多种事件类型,或者对事件进行过滤和处理。可能还需要添加日志功能,方便调试事件触发和监听的情况。

考虑错误处理。原生的event_bus可能在事件处理函数抛出异常时导致整个应用崩溃,封装时可以添加try-catch块,捕获异常并记录,避免应用崩溃。同时,允许我们自定义错误处理逻辑。

另外,可能还需要支持不同的执行方式,比如同步和异步事件处理。原生的event_bus可能默认是同步的,但有时候异步处理更合适,封装时可以提供一个选项来选择执行方式。

关于订阅管理,我不希望手动调用取消订阅的方法,而是希望通过某种方式自动管理。例如,在Flutter中,可以在StatefulWidget的dispose方法中自动取消订阅。这时候,封装类可能需要提供一个简便的方法,比如在注册事件监听时返回一个订阅对象,并在dispose时调用取消。

接下来,需要添加一些高级功能,比如事件过滤,只接收特定条件下的事件,或者转换事件数据。或者支持多个不同的EventBus实例,以便在不同模块中使用独立的事件总线。

现在,结合这些思考,我设计一个封装类的结构

  1. 单例模式:确保全局只有一个EventBus实例,或者允许创建多个实例。
  2. 订阅方法:提供一个on方法,用于注册事件监听器,返回订阅对象以便管理。
  3. 发布方法:提供一个sendEvent方法,用于触发事件。
  4. 自动取消订阅:比如在Flutter中,结合Widget生命周期自动取消。
  5. 日志记录:在发布和接收事件时输出日志,方便调试。
  6. 错误处理:捕获监听器中的异常,避免应用崩溃。
  7. 支持泛型:确保能够处理不同类型的事件。

以下是针对 event_bus 的封装实现,提供 单例管理、自动取消订阅、日志跟踪、错误处理 等功能,并支持与 Flutter Widget 生命周期无缝集成:


/// 封装后的高级事件总线
class AppEventBus {
  static final EventBus _instance = EventBus();

  // 私有构造,确保单例
  AppEventBus._internal();

  /// 获取单例实例
  static EventBus get instance => _instance;

  /// 发送事件
  static void sendEvent<T>(T event) {
    if (kDebugMode) {
      print('[EventBus] Firing event: ${event.runtimeType}');
    }
    instance.fire(event);
  }

  /// 订阅事件,返回可取消的订阅对象
  static StreamSubscription<T> on<T>(void Function(T event) handler, {
    bool handleError = true,
    ErrorCallback? onError,
  }) {
    final subscription = instance.on<T>().listen((event) {
      if (kDebugMode) {
        print('[EventBus] Received event: ${event.runtimeType}');
      }
      _safeRun(() => handler(event), onError: onError);
    }, onError: handleError ? (error, stack) {
      _safeRun(() => onError?.call(error, stack));
    } : null);

    return subscription;
  }

  static void _safeRun(void Function() action, {ErrorCallback? onError}) {
    try {
      action();
    } catch (e, s) {
      if (kDebugMode) {
        print('[EventBus] Handler error: $e\n$s');
      }
      onError?.call(e, s);
    }
  }
}

/// Flutter Widget 集成扩展
mixin EventBusMixin<T extends StatefulWidget> on State<T> {
  final List<StreamSubscription> _eventSubscriptions = [];

  /// 安全订阅事件,自动管理生命周期
  void subscribe<Event>(void Function(Event event) handler, {
    bool handleError = true,
    ErrorCallback? onError,
  }) {
    _eventSubscriptions.add(
        AppEventBus.on<Event>(handler, handleError: handleError, onError: onError)
    );
  }

  @override
  void dispose() {
    for (final sub in _eventSubscriptions) {
      sub.cancel();
    }
    if (kDebugMode) {
      print('[EventBus] Canceled ${_eventSubscriptions.length} subscriptions');
    }
    super.dispose();
  }
}

typedef ErrorCallback = void Function(Object error, StackTrace stackTrace);

使用示例

定义两个事件

import 'package:flutter/material.dart';
// 定义事件类
class UserLoggedInEvent {
  final String username;
  UserLoggedInEvent(this.username);
}

// 定义另一个事件类
class DataUpdatedEvent {
  final String data;
  DataUpdatedEvent(this.data);
}

写一个page测试


class EventBusPage extends StatefulWidget {
  const EventBusPage({super.key});

  @override
  State<EventBusPage> createState() => _EventBusPageState();
}

class _EventBusPageState extends State<EventBusPage> with EventBusMixin {
  @override
  void initState() {
    super.initState();
    print('initState');
    // 自动管理订阅
    subscribe<UserLoggedInEvent>(handleLogin);
    subscribe<DataUpdatedEvent>(dataUpdated);
  }

  void handleLogin(UserLoggedInEvent event) {
    print('User logged in: ${event.username}');

  }
  void dataUpdated(DataUpdatedEvent event) {
    print("更新数据:${event.data}");
  }

  @override
  void dispose() {
    print('dispose---EventBusPage');
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
          appBar: CustomAppBar(
            title: 'EventBus',
            actions: [
            ],
            onBackPressed: () {
              // Handle back button press, if needed
              Navigator.pop(context);
            },
          ),
          body: Container(
            alignment: Alignment.center,
            child: Column(
              children: [
                SizedBox(height: 50),
                Container(
                    height: 50,
                    width: 200,
                    color: Colors.red,
                    child:
                    TextButton(onPressed: () {
                      // 发送事件
                      AppEventBus.sendEvent(UserLoggedInEvent('Alice'));
                    }, child: Text("用户昵称按钮"))),
                SizedBox(height: 50),
                Container(
                    height: 50,
                    width: 200,
                    color: Colors.red,
                    child:
                    TextButton(onPressed: () {
                      // 发送事件
                      AppEventBus.sendEvent(DataUpdatedEvent("kkkkk"));
                    }, child: Text("普通更新按钮"))),
              ],
            ),
          ),
        );
  }
}

功能亮点

  • 1、单例模式 全局唯一事件总线实例,通过 AppEventBus.instance 访问核心功能
  • 2、类型安全 强类型事件处理,编译时类型检查
  • 3、生命周期管理
    • 通过 EventBusMixin 自动取消订阅
    • 手动订阅返回 StreamSubscription 便于管理
  • 4、安全防护
    • 异常捕获机制防止事件处理崩溃
    • 错误处理回调支持
  • 5、调试支持
    • 开发模式下的详细事件日志
    • 清晰的错误堆栈打印
  • 6、灵活扩展
    • 支持同步/异步事件处理
    • 可配置的错误处理策略

原理分析

image.png

class EventBus {
  StreamController _streamController; 
  StreamController get streamController => _streamController;
  EventBus({bool sync = false})
      : _streamController = StreamController.broadcast(sync: sync);
  EventBus.customController(StreamController controller)
      : _streamController = controller;
  Stream<T> on<T>() {
    if (T == dynamic) {
      return streamController.stream as Stream<T>;
    } else {
      return streamController.stream.where((event) => event is T).cast<T>();
    }
  }
  void fire(event) {
    streamController.add(event);
  }
  void destroy() {
    _streamController.close();
  }
}

EventBus的源码非常少,原理也非常的简单

1、类定义和成员变量

class EventBus {
  StreamController _streamController;

  /// Controller for the event bus stream.
  StreamController get streamController => _streamController;
}
  • _streamController:私有成员变量,用于管理事件的发布和订阅。
  • streamController:一个 getter 方法,用于获取 _streamController

2、构造函数

EventBus({bool sync = false})
    : _streamController = StreamController.broadcast(sync: sync);
EventBus.customController(StreamController controller)
    : _streamController = controller;
  • 默认构造函数 EventBus({bool sync = false})

    • sync 参数:如果为 true,事件会在调用 fire 方法时直接传递给监听器;如果为 false(默认值),事件会在创建事件的代码执行完成后再传递给监听器。
    • 使用 StreamController.broadcast(sync: sync) 创建一个广播流控制器,允许有多个订阅者监听同一个流。
  • 自定义控制器构造函数 EventBus.customController(StreamController controller)

    • 允许用户传入自定义的 StreamController,例如使用 RxDart 的 Subject 作为控制器。

3、订阅事件方法 on<T>()

Stream<T> on<T>() {
  if (T == dynamic) {
    return streamController.stream as Stream<T>;
  } else {
    return streamController.stream.where((event) => event is T).cast<T>();
  }
}
  • 该方法用于订阅特定类型 T 的事件。
  • 如果 T 为 dynamic,则返回流控制器的原始流,包含所有类型的事件。
  • 如果 T 为其他类型,使用 where 方法过滤出类型为 T 的事件,然后使用 cast 方法将事件转换为 T 类型。
  • 返回的 Stream 是广播流,允许多个订阅者同时监听。

4、发布事件方法 fire(event)

void fire(event) {
  streamController.add(event);
}

该方法用于发布一个事件,将事件添加到流控制器中,流控制器会将事件广播给所有订阅者。

5、销毁事件总线方法 destroy()

void destroy() {
  _streamController.close();
}

该方法用于销毁事件总线,关闭流控制器,释放资源

实现原理细节

  • StreamControllerEventBus 类内部使用 StreamController 来管理事件的发布和订阅。StreamController.broadcast() 创建一个广播流控制器,允许有多个订阅者监听同一个流。
  • Streamon 方法返回一个 Stream 对象,订阅者可以通过 listen 方法监听这个流。Stream 是 Dart 中用于处理异步事件序列的抽象,它提供了一系列的操作符,如 where 和 cast,用于过滤和转换事件。
  • 事件过滤:在 on 方法中,如果指定了事件类型 T,则会使用 where 方法过滤出类型为 T 的事件,然后使用 cast 方法将事件转换为 T 类型。

总结

event_bus 库通过 StreamController 和 Stream 实现了事件总线模式,允许不同部分的代码之间进行松耦合的通信。发布者通过 fire 方法发布事件,订阅者通过 on 方法订阅事件,事件总线负责将事件广播给所有订阅者。