18 KiB
18 KiB
Basic Push - 推送通知模块文档
模块概述
basic_push 是 OneApp 基础工具模块群中的推送通知核心模块,提供统一的推送消息接收、处理和分发功能。该模块集成了极光推送SDK、本地通知、事件总线、心跳管理等功能,支持iOS和Android平台的推送服务。
基本信息
- 模块名称: basic_push
- 模块路径: oneapp_basic_utils/basic_push
- 类型: Flutter Package Module
- 主要功能: 推送消息接收、事件总线、本地通知、心跳管理
核心特性
- 推送服务集成: 集成极光推送SDK,支持远程推送消息
- 事件总线系统: 基于RxDart的事件发布订阅机制
- 本地通知: 支持本地通知的发送和管理
- 心跳管理: 前后台切换时的心跳上报机制
- 用户绑定: 用户登录后的推送设备绑定和解绑
- 权限管理: 推送权限检查和设置跳转
- 多平台支持: iOS和Android平台适配
目录结构
basic_push/
├── lib/
│ ├── basic_push.dart # 模块入口文件
│ └── src/
│ ├── push_facade.dart # 推送门面服务
│ ├── push_topic.dart # 主题定义
│ ├── push_config.dart # 配置管理
│ ├── constant.dart # 常量定义
│ ├── connector.dart # 连接器
│ ├── channel/ # 通道实现
│ │ ├── core/
│ │ │ └── i_push_channel.dart
│ │ ├── push_channel.dart
│ │ └── local_channel.dart
│ ├── domain/ # 领域对象
│ │ ├── do/
│ │ │ ├── push/
│ │ │ │ ├── push_init_params.dart
│ │ │ │ └── query/
│ │ │ │ └── setting_push_query_do.dart
│ │ │ └── setting_has_success_do.dart
│ │ ├── errors/
│ │ │ └── setting_errors.dart
│ │ └── push_device_info.dart
│ ├── eventbus/ # 事件总线
│ │ ├── event_bus.dart
│ │ ├── event_bus.freezed.dart
│ │ └── event_bus.g.dart
│ ├── heart_beat/ # 心跳管理
│ │ └── heart_beat_mgmt.dart
│ ├── infrastructure/ # 基础设施层
│ │ ├── push_repository.dart
│ │ ├── remote_api.dart
│ │ └── remoteapi/
│ │ └── push_repository_remote.dart
│ └── push_parser/ # 消息解析
│ └── push_message_parser.dart
├── basic_push_uml.puml # UML图定义
├── basic_push_uml.svg # UML图
└── pubspec.yaml # 依赖配置
核心架构组件
1. 推送初始化参数 (PushInitParams)
定义推送服务初始化所需的参数:
/// 初始化需要的参数
class PushInitParams {
const PushInitParams({
this.privateCloud = true,
this.isProduction = false,
this.isDebug = false,
this.appKey = '',
this.channel = '',
this.connIp = '',
this.connHost,
this.connPort = 0,
this.reportUrl = '',
this.badgeUrl = '',
this.heartbeatInterval,
});
/// iOS环境:生产环境设置
/// TestFlight/In-house/Ad-hoc/AppStore为生产环境(true)
/// Xcode直接编译为开发环境(false)
final bool isProduction;
/// 是否是私有云
final bool privateCloud;
/// debug开关
final bool isDebug;
/// 应用Key
final String appKey;
/// 渠道标识
final String channel;
/// 连接IP地址
final String connIp;
/// 连接主机
final String? connHost;
/// 连接端口
final int connPort;
/// 上报URL
final String reportUrl;
/// 角标URL
final String badgeUrl;
/// 推送心跳间隔(毫秒),默认4分50秒
final int? heartbeatInterval;
}
2. 事件总线系统 (EventBus)
基于RxDart实现的事件发布订阅系统:
/// 事件总线接口
abstract class IEventBus {
/// 监听多个主题的事件
Stream<Event> on(List<Topic> topics);
/// 发布新事件
void fire(Event event);
/// 销毁事件总线
void destroy();
/// 获取发送端口用于连接器连接
SendPort get sendPort;
}
/// 事件总线实现
class EventBus implements IEventBus {
factory EventBus({bool sync = false}) => EventBus._(
PublishSubject<Event>(
onListen: () => Logger.d('Event Bus has a listener', tag),
onCancel: () => Logger.d('Event Bus has no listener', tag),
sync: sync,
),
);
@override
void fire(Event event) {
Logger.d('Fire a event, $event');
if (_checkEvent(event)) {
streamController.add(event);
}
}
@override
Stream<Event> on(List<Topic> topics) =>
streamController.stream.where((event) {
final firstWhere = topics.firstWhere(
(element) => (element & event.topic).isHit,
orElse: Topic.zero,
);
return firstWhere != Topic.zero();
});
}
3. 事件和主题定义 (Event & Topic)
使用Freezed定义的不可变数据类:
/// 事件对象
@freezed
class Event with _$Event {
const factory Event({
required Topic topic,
required DateTime timestamp,
dynamic payload,
@Default('') String description,
@Default(EventSource.local) EventSource sources,
@Default(false) bool notificationInApp,
}) = _Event;
factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);
}
/// 主题对象
@freezed
class Topic with _$Topic {
const factory Topic({
@Default(maxInt) int scope,
@Default(maxInt) int product,
@Default(maxInt) int index,
@Default(maxInt) int subIndex,
}) = _Topic;
const Topic._();
factory Topic.zero() => const Topic(scope: 0, product: 0, index: 0, subIndex: 0);
/// 主题合并操作
Topic operator |(Topic other) => Topic(
scope: scope | other.scope,
product: product | other.product,
index: index | other.index,
subIndex: subIndex | other.subIndex,
);
/// 主题匹配操作
Topic operator &(Topic other) => Topic(
scope: scope & other.scope,
product: product & other.product,
index: index & other.index,
subIndex: subIndex & other.subIndex,
);
/// 是否命中
bool get isHit => scope > 0 && product > 0 && index > 0 && subIndex > 0;
}
/// 事件来源
enum EventSource {
/// 远程推送
push,
/// 本地
local,
}
4. 推送门面服务 (IPushFacade)
推送模块的核心接口定义:
/// push模块的接口
abstract class IPushFacade {
/// 初始化模块
/// 开启心跳上报
void initPush({
ILoginDeps loginDeps = const DefaultLoginDeps(),
PushInitParams pushInitParams = const PushInitParams(),
});
/// 系统通知权限是否授予
bool get isNotificationEnabled;
/// 返回推送SDK里的regId
Future<String?> get pushRegId;
/// 调用此API跳转至系统设置中应用设置界面
void openSettingsForNotification();
/// 将bizId与推送regId传至服务端绑定
Future<bool> bindPush();
/// 将bizId与推送RegId传至服务端解绑
Future<bool> unbindPush();
/// 发布一个事件
void postEvent({
required Topic topic,
dynamic payload,
String description = '',
});
/// 订阅对应topic的事件消息
Stream<Event> subscribeOn({required List<Topic> topics});
/// 取消订阅topic的事件消息
void unsubscribe(StreamSubscription<dynamic> stream);
/// 获取服务器通知开关列表
Future<Either<SettingError, SettingPushQueryModel>> fetchPushQuery();
/// 设置服务器通知开关列表
Future<Either<SettingError, SettingHasSuccessModel>> fetchPushSet({
List<SettingDetailSwitchModel> detailSwitchList,
});
/// 告知当前在前台
void enableHearBeatForeground();
/// 告知当前在后台
void enableHearBeatBackground();
/// 发送本地通知
Future<String> sendLocalNotification(LocalNotification notification);
/// 设置应用角标
Future<dynamic> setBadgeNumber(int badgeNumber);
/// 清除缓存
Future<bool> clearCache();
}
/// 全局推送门面实例
IPushFacade get pushFacade => _pushFacade ??= biuldPushFacade();
5. 预定义主题 (Push Topics)
/// 通知点击主题
const Topic notificationOnClickTopic =
Topic(scope: shift2, product: shift0, index: shift0);
/// 点击事件里payload里的跳转协议
const keyLaunchScheme = 'launchScheme';
/// 全部主题
const Topic allTopic = Topic();
/// 零主题
const Topic zeroTopic = Topic(scope: 0, product: 0, index: 0, subIndex: 0);
使用指南
1. 推送服务初始化
import 'package:basic_push/basic_push.dart';
// 配置推送初始化参数
final pushInitParams = PushInitParams(
isProduction: true, // iOS生产环境
privateCloud: true, // 使用私有云
isDebug: false, // 关闭调试模式
appKey: 'your_app_key', // 应用密钥
channel: 'official', // 渠道标识
connHost: 'push.example.com', // 推送服务器
connPort: 8080, // 连接端口
heartbeatInterval: 290000, // 心跳间隔(4分50秒)
);
// 初始化推送服务
pushFacade.initPush(
loginDeps: MyLoginDeps(),
pushInitParams: pushInitParams,
);
2. 用户绑定和解绑
// 用户登录后绑定推送
try {
final success = await pushFacade.bindPush();
if (success) {
print('推送绑定成功');
} else {
print('推送绑定失败');
}
} catch (e) {
print('推送绑定异常: $e');
}
// 用户登出时解绑推送
try {
final success = await pushFacade.unbindPush();
if (success) {
print('推送解绑成功');
} else {
print('推送解绑失败');
}
} catch (e) {
print('推送解绑异常: $e');
}
3. 权限管理
// 检查通知权限
if (pushFacade.isNotificationEnabled) {
print('通知权限已授予');
} else {
print('通知权限未授予,请手动开启');
// 跳转到系统设置页面
pushFacade.openSettingsForNotification();
}
// 获取推送注册ID
final regId = await pushFacade.pushRegId;
print('推送注册ID: $regId');
4. 事件发布和订阅
// 发布事件
pushFacade.postEvent(
topic: notificationOnClickTopic,
payload: {
keyLaunchScheme: 'oneapp://car/control',
'userId': '12345',
'action': 'open_door',
},
description: '用户点击推送通知',
);
// 订阅事件
final subscription = pushFacade.subscribeOn(
topics: [notificationOnClickTopic],
).listen((event) {
print('接收到事件: ${event.description}');
// 处理跳转协议
final scheme = event.payload[keyLaunchScheme] as String?;
if (scheme != null) {
handleDeepLink(scheme);
}
});
// 取消订阅
pushFacade.unsubscribe(subscription);
5. 本地通知
// 发送本地通知
final notification = LocalNotification(
title: '车辆提醒',
body: '您的车辆充电已完成',
payload: jsonEncode({
'type': 'charging_complete',
'vehicleId': 'VIN123456',
}),
);
final notificationId = await pushFacade.sendLocalNotification(notification);
print('本地通知已发送,ID: $notificationId');
// 设置应用角标
await pushFacade.setBadgeNumber(5);
6. 应用生命周期管理
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
// 应用进入前台
pushFacade.enableHearBeatForeground();
break;
case AppLifecycleState.paused:
// 应用进入后台
pushFacade.enableHearBeatBackground();
break;
default:
break;
}
}
}
7. 推送设置管理
// 获取推送设置
final result = await pushFacade.fetchPushQuery();
result.fold(
(error) => print('获取推送设置失败: ${error.message}'),
(settings) => {
print('当前推送设置: ${settings.detailSwitchList}'),
// 显示推送设置界面
},
);
// 更新推送设置
final updateResult = await pushFacade.fetchPushSet(
detailSwitchList: [
SettingDetailSwitchModel(
key: 'vehicle_notification',
enabled: true,
name: '车辆通知',
),
SettingDetailSwitchModel(
key: 'charging_notification',
enabled: false,
name: '充电通知',
),
],
);
updateResult.fold(
(error) => print('更新推送设置失败: ${error.message}'),
(success) => print('推送设置更新成功'),
);
依赖配置
pubspec.yaml 关键依赖
dependencies:
flutter:
sdk: flutter
# 函数式编程
dartz: ^0.10.1
# 响应式编程
rxdart: ^0.27.0
# 不可变数据类
freezed_annotation: ^2.0.0
# JSON序列化
json_annotation: ^4.0.0
# 基础日志
basic_logger:
path: ../basic_logger
# 基础平台
basic_platform:
path: ../basic_platform
dev_dependencies:
# 代码生成
freezed: ^2.0.0
json_serializable: ^6.0.0
build_runner: ^2.0.0
高级功能
1. 自定义登录依赖
class MyLoginDeps with ILoginDeps {
@override
bool get isLogin {
// 实现自定义的登录状态检查逻辑
return UserManager.instance.isLoggedIn;
}
}
// 使用自定义登录依赖
pushFacade.initPush(
loginDeps: MyLoginDeps(),
pushInitParams: pushInitParams,
);
2. 复杂主题组合
// 定义自定义主题
const Topic carNotificationTopic = Topic(
scope: shift1,
product: shift2,
index: shift1,
subIndex: shift1,
);
const Topic chargingNotificationTopic = Topic(
scope: shift1,
product: shift2,
index: shift2,
subIndex: shift1,
);
// 组合多个主题
final combinedTopic = carNotificationTopic | chargingNotificationTopic;
// 订阅组合主题
final subscription = pushFacade.subscribeOn(
topics: [combinedTopic],
).listen((event) {
// 处理车辆或充电相关的通知
});
3. 推送消息解析
// 自定义推送消息解析器
class MyPushMessageParser {
static Map<String, dynamic> parse(Map<String, dynamic> rawMessage) {
// 解析推送消息的自定义格式
return {
'type': rawMessage['msg_type'],
'data': jsonDecode(rawMessage['data']),
'timestamp': DateTime.parse(rawMessage['timestamp']),
};
}
}
性能优化建议
1. 事件订阅管理
- 及时取消不需要的事件订阅,避免内存泄漏
- 使用合适的主题过滤,减少不必要的事件处理
- 在Widget销毁时记得取消订阅
2. 心跳优化
- 根据应用特性调整心跳间隔
- 在后台时降低心跳频率
- 监听网络状态变化,暂停心跳服务
3. 本地通知限制
- 避免频繁发送本地通知
- 合理设置通知的声音和震动
- 控制通知的数量和频率
最佳实践
1. 推送权限处理
// 推荐:友好的权限请求
void requestNotificationPermission() {
if (!pushFacade.isNotificationEnabled) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('开启通知权限'),
content: Text('为了及时收到重要消息,请开启通知权限'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('稍后设置'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
pushFacade.openSettingsForNotification();
},
child: Text('去设置'),
),
],
),
);
}
}
2. 错误处理
// 推荐:完整的错误处理
Future<void> handlePushBinding() async {
try {
final success = await pushFacade.bindPush();
if (!success) {
// 绑定失败的用户友好提示
showSnackBar('推送服务暂时不可用,请稍后重试');
}
} catch (e) {
// 记录错误日志
Logger.e('推送绑定异常: $e', tagPush);
// 用户友好的错误提示
showSnackBar('网络连接异常,请检查网络后重试');
}
}
3. 事件处理优化
// 推荐:使用StreamBuilder处理事件
class NotificationHandler extends StatefulWidget {
@override
_NotificationHandlerState createState() => _NotificationHandlerState();
}
class _NotificationHandlerState extends State<NotificationHandler> {
late StreamSubscription _subscription;
@override
void initState() {
super.initState();
_subscription = pushFacade.subscribeOn(
topics: [notificationOnClickTopic],
).listen(_handleNotificationClick);
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _handleNotificationClick(Event event) {
// 安全的事件处理
if (mounted && event.payload != null) {
final scheme = event.payload[keyLaunchScheme] as String?;
if (scheme != null) {
NavigationService.handleDeepLink(scheme);
}
}
}
}
问题排查
常见问题
- 推送收不到: 检查应用权限、网络连接和推送配置
- 事件订阅失效: 确认订阅没有被意外取消,检查主题匹配逻辑
- 心跳断开: 检查网络稳定性和服务器配置
调试技巧
- 启用debug模式查看详细日志
- 使用推送测试工具验证配置
- 监控应用生命周期事件