first commit
This commit is contained in:
70
basic_utils/README.md
Normal file
70
basic_utils/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Basic Utils 基础工具模块群
|
||||
|
||||
## 模块群概述
|
||||
|
||||
Basic Utils 模块群是 OneApp 的基础工具集合,提供了应用开发中必需的底层工具和通用组件。该模块群包含了网络通信、日志系统、配置管理、平台适配、推送服务等核心基础设施。
|
||||
|
||||
## 子模块列表
|
||||
|
||||
### 核心基础模块
|
||||
1. **[basic_network](./basic_network.md)** - 网络通信模块
|
||||
2. **[basic_logger](./basic_logger.md)** - 日志系统模块
|
||||
3. **[basic_config](./basic_config.md)** - 配置管理模块
|
||||
4. **[basic_platform](./basic_platform.md)** - 平台适配模块
|
||||
5. **[basic_push](./basic_push.md)** - 推送服务模块
|
||||
6. **[basic_utils](./basic_utils.md)** - 基础工具模块
|
||||
|
||||
### 架构框架模块
|
||||
7. **[base_mvvm](./base_mvvm.md)** - MVVM架构模块
|
||||
|
||||
### 下载和监控模块
|
||||
8. **[flutter_downloader](./flutter_downloader.md)** - 文件下载器模块
|
||||
9. **[kit_app_monitor](./kit_app_monitor.md)** - 应用监控工具包
|
||||
|
||||
### 第三方集成模块
|
||||
10. **[flutter_plugin_mtpush_private](./flutter_plugin_mtpush_private.md)** - 美团云推送插件
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 基础依赖配置
|
||||
```yaml
|
||||
dependencies:
|
||||
basic_utils:
|
||||
path: ../oneapp_basic_utils/basic_utils
|
||||
basic_config:
|
||||
path: ../oneapp_basic_utils/basic_config
|
||||
basic_logger:
|
||||
path: ../oneapp_basic_utils/basic_logger
|
||||
basic_network:
|
||||
path: ../oneapp_basic_utils/basic_network
|
||||
basic_platform:
|
||||
path: ../oneapp_basic_utils/basic_platform
|
||||
```
|
||||
|
||||
### 初始化配置
|
||||
```dart
|
||||
// 应用初始化时配置基础模块
|
||||
await BasicUtils.initialize();
|
||||
await BasicConfig.initialize();
|
||||
await BasicLogger.initialize();
|
||||
await BasicNetwork.initialize();
|
||||
await BasicPlatform.initialize();
|
||||
```
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 模块设计原则
|
||||
1. **单一职责**: 每个模块只负责特定的功能领域
|
||||
2. **接口抽象**: 提供清晰的接口定义,隐藏实现细节
|
||||
3. **配置驱动**: 通过配置控制模块行为,提高灵活性
|
||||
4. **错误处理**: 统一的错误处理和日志记录机制
|
||||
|
||||
### 代码规范
|
||||
- 遵循 Dart 官方编码规范
|
||||
- 使用静态分析工具检查代码质量
|
||||
- 编写完整的单元测试和文档
|
||||
- 版本化管理和变更日志
|
||||
|
||||
## 总结
|
||||
|
||||
`oneapp_basic_utils` 模块群为 OneApp 提供了坚实的技术基础,通过模块化设计和统一抽象,实现了基础功能的复用和标准化。这些模块不仅支撑了当前的业务需求,也为未来的功能扩展提供了良好的基础架构。
|
||||
504
basic_utils/base_mvvm.md
Normal file
504
basic_utils/base_mvvm.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# Base MVVM - 基础MVVM架构模块文档
|
||||
|
||||
## 模块概述
|
||||
|
||||
`base_mvvm` 是 OneApp 基础工具模块群中的MVVM架构基础模块,提供了完整的Model-View-ViewModel架构模式实现。该模块封装了状态管理、数据绑定、Provider模式等核心功能,为应用提供了统一的架构规范和基础组件。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: base_mvvm
|
||||
- **模块路径**: oneapp_basic_utils/base_mvvm
|
||||
- **类型**: Flutter Package Module
|
||||
- **主要功能**: MVVM架构基础、状态管理、数据绑定
|
||||
|
||||
### 核心特性
|
||||
- **状态管理**: 基于Provider的响应式状态管理
|
||||
- **视图状态**: 统一的页面状态管理(idle/busy/empty/error)
|
||||
- **列表刷新**: 封装下拉刷新和上拉加载更多
|
||||
- **数据绑定**: 双向数据绑定支持
|
||||
- **错误处理**: 统一的错误类型和处理机制
|
||||
- **日志工具**: 集成日志记录功能
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
base_mvvm/
|
||||
├── lib/
|
||||
│ ├── base_mvvm.dart # 模块入口文件
|
||||
│ ├── provider/ # Provider相关
|
||||
│ │ ├── provider_widget.dart # Provider封装组件
|
||||
│ │ ├── view_state_model.dart # 视图状态模型
|
||||
│ │ ├── view_state_list_model.dart # 列表状态模型
|
||||
│ │ ├── view_state_refresh_list_model.dart # 刷新列表模型
|
||||
│ │ └── view_state.dart # 状态枚举定义
|
||||
│ ├── utils/ # 工具类
|
||||
│ │ ├── logs/ # 日志工具
|
||||
│ │ │ └── log_utils.dart
|
||||
│ │ └── rxbus.dart # 事件总线
|
||||
│ └── widgets/ # 基础组件
|
||||
│ ├── glides/ # 图片组件
|
||||
│ │ └── glide_image_view.dart
|
||||
│ └── refresh/ # 刷新组件
|
||||
│ └── base_easy_refresh.dart
|
||||
└── pubspec.yaml # 依赖配置
|
||||
```
|
||||
|
||||
## 核心架构组件
|
||||
|
||||
### 1. 视图状态枚举 (ViewState)
|
||||
|
||||
定义了应用中通用的页面状态:
|
||||
|
||||
```dart
|
||||
/// 页面状态类型
|
||||
enum ViewState {
|
||||
idle, // 空闲状态
|
||||
busy, // 加载中
|
||||
empty, // 无数据
|
||||
error, // 加载失败
|
||||
}
|
||||
|
||||
/// 错误类型
|
||||
enum ViewStateErrorType {
|
||||
none,
|
||||
defaultError,
|
||||
networkTimeOutError, // 网络超时
|
||||
disconnectException, // 断网
|
||||
noNetworkSignal, // 无网络信号
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 基础视图状态模型 (ViewStateModel)
|
||||
|
||||
所有ViewModel的基础类,提供统一的状态管理:
|
||||
|
||||
```dart
|
||||
class ViewStateModel with ChangeNotifier {
|
||||
/// 防止页面销毁后异步任务才完成导致报错
|
||||
bool _disposed = false;
|
||||
|
||||
/// 当前页面状态,默认为idle
|
||||
ViewState _viewState = ViewState.idle;
|
||||
|
||||
/// 错误类型
|
||||
ViewStateErrorType _viewStateError = ViewStateErrorType.none;
|
||||
|
||||
/// 构造函数,可指定初始状态
|
||||
ViewStateModel({ViewState? viewState})
|
||||
: _viewState = viewState ?? ViewState.idle;
|
||||
|
||||
// 状态获取器
|
||||
ViewState get viewState => _viewState;
|
||||
ViewStateErrorType get viewStateError => _viewStateError;
|
||||
|
||||
// 状态判断方法
|
||||
bool get isBusy => viewState == ViewState.busy;
|
||||
bool get isIdle => viewState == ViewState.idle;
|
||||
bool get isEmpty => viewState == ViewState.empty;
|
||||
bool get isError => viewState == ViewState.error;
|
||||
|
||||
// 状态设置方法
|
||||
void setIdle() => viewState = ViewState.idle;
|
||||
void setBusy() => viewState = ViewState.busy;
|
||||
void setEmpty() => viewState = ViewState.empty;
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
if (!_disposed) {
|
||||
super.notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Provider封装组件 (ProviderWidget)
|
||||
|
||||
简化Provider使用的封装组件:
|
||||
|
||||
```dart
|
||||
class ProviderWidget<T extends ChangeNotifier> extends StatefulWidget {
|
||||
final ValueWidgetBuilder<T> builder;
|
||||
final T model;
|
||||
final Widget? child;
|
||||
final Function(T model)? onModelReady;
|
||||
final bool autoDispose;
|
||||
|
||||
const ProviderWidget({
|
||||
super.key,
|
||||
required this.builder,
|
||||
required this.model,
|
||||
this.child,
|
||||
this.onModelReady,
|
||||
this.autoDispose = true
|
||||
});
|
||||
|
||||
@override
|
||||
_ProviderWidgetState<T> createState() => _ProviderWidgetState<T>();
|
||||
}
|
||||
|
||||
class _ProviderWidgetState<T extends ChangeNotifier>
|
||||
extends State<ProviderWidget<T>> {
|
||||
late T model;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
model = widget.model;
|
||||
widget.onModelReady?.call(model);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.autoDispose) {
|
||||
model.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider<T>.value(
|
||||
value: model,
|
||||
child: Consumer<T>(builder: widget.builder, child: widget.child)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 多Provider支持 (ProviderWidget2)
|
||||
|
||||
支持同时管理两个Model的组件:
|
||||
|
||||
```dart
|
||||
class ProviderWidget2<A extends ChangeNotifier, B extends ChangeNotifier>
|
||||
extends StatefulWidget {
|
||||
final Widget Function(BuildContext context, A model1, B model2, Widget? child) builder;
|
||||
final A model1;
|
||||
final B model2;
|
||||
final Widget? child;
|
||||
final Function(A model1, B model2)? onModelReady;
|
||||
final bool autoDispose;
|
||||
|
||||
// ... 实现与ProviderWidget类似,支持双Model管理
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 刷新列表状态模型 (ViewStateRefreshListModel)
|
||||
|
||||
专门用于处理列表数据的刷新和加载更多功能:
|
||||
|
||||
```dart
|
||||
abstract class ViewStateRefreshListModel<T> extends ViewStateListModel<T> {
|
||||
/// 分页配置
|
||||
static const int pageNumFirst = 1;
|
||||
static const int pageSize = 10;
|
||||
|
||||
/// 列表刷新控制器
|
||||
final EasyRefreshController _refreshController;
|
||||
EasyRefreshController get refreshController => _refreshController;
|
||||
|
||||
/// 当前页码
|
||||
int _currentPageNum = pageNumFirst;
|
||||
|
||||
ViewStateRefreshListModel({super.viewState, bool isMore = true})
|
||||
: _refreshController = EasyRefreshController(
|
||||
controlFinishLoad: isMore,
|
||||
controlFinishRefresh: true);
|
||||
|
||||
/// 下拉刷新
|
||||
@override
|
||||
Future<List<T>> refresh({bool init = false}) async {
|
||||
try {
|
||||
_currentPageNum = pageNumFirst;
|
||||
var data = await loadData(pageNum: pageNumFirst);
|
||||
refreshController.finishRefresh();
|
||||
|
||||
if (data.isEmpty) {
|
||||
refreshController.finishLoad(IndicatorResult.none);
|
||||
list.clear();
|
||||
setEmpty();
|
||||
} else {
|
||||
onCompleted(data);
|
||||
list.clear();
|
||||
list.addAll(data);
|
||||
|
||||
// 小于分页数量时禁止上拉加载更多
|
||||
if (data.length < pageSize) {
|
||||
Future.delayed(const Duration(milliseconds: 100)).then((value) {
|
||||
refreshController.finishLoad(IndicatorResult.noMore);
|
||||
});
|
||||
}
|
||||
setIdle();
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
if (init) list.clear();
|
||||
refreshController.finishLoad(IndicatorResult.fail);
|
||||
setEmpty();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 上拉加载更多
|
||||
Future<List<T>> loadMore() async {
|
||||
try {
|
||||
var data = await loadData(pageNum: ++_currentPageNum);
|
||||
refreshController.finishRefresh();
|
||||
|
||||
if (data.isEmpty) {
|
||||
_currentPageNum--;
|
||||
refreshController.finishLoad(IndicatorResult.noMore);
|
||||
} else {
|
||||
onCompleted(data);
|
||||
list.addAll(data);
|
||||
|
||||
if (data.length < pageSize) {
|
||||
refreshController.finishLoad(IndicatorResult.noMore);
|
||||
} else {
|
||||
refreshController.finishLoad();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
_currentPageNum--;
|
||||
refreshController.finishLoad(IndicatorResult.fail);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 抽象方法:加载数据
|
||||
@override
|
||||
Future<List<T>> loadData({int? pageNum});
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_refreshController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 1. 基础ViewModel示例
|
||||
|
||||
创建一个继承ViewStateModel的ViewModel:
|
||||
|
||||
```dart
|
||||
class UserProfileViewModel extends ViewStateModel {
|
||||
UserInfo? _userInfo;
|
||||
UserInfo? get userInfo => _userInfo;
|
||||
|
||||
Future<void> loadUserProfile(String userId) async {
|
||||
setBusy(); // 设置加载状态
|
||||
|
||||
try {
|
||||
_userInfo = await userRepository.getUserProfile(userId);
|
||||
setIdle(); // 设置空闲状态
|
||||
} catch (e) {
|
||||
setError(); // 设置错误状态
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 在UI中使用ProviderWidget
|
||||
|
||||
```dart
|
||||
class UserProfilePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderWidget<UserProfileViewModel>(
|
||||
model: UserProfileViewModel(),
|
||||
onModelReady: (model) => model.loadUserProfile("123"),
|
||||
builder: (context, model, child) {
|
||||
if (model.isBusy) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (model.isEmpty) {
|
||||
return Center(child: Text('暂无数据'));
|
||||
}
|
||||
|
||||
if (model.isError) {
|
||||
return Center(child: Text('加载失败'));
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Text(model.userInfo?.name ?? ''),
|
||||
Text(model.userInfo?.email ?? ''),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 列表刷新示例
|
||||
|
||||
```dart
|
||||
class NewsListViewModel extends ViewStateRefreshListModel<NewsItem> {
|
||||
final NewsRepository _repository = NewsRepository();
|
||||
|
||||
@override
|
||||
Future<List<NewsItem>> loadData({int? pageNum}) async {
|
||||
return await _repository.getNewsList(
|
||||
page: pageNum ?? 1,
|
||||
pageSize: ViewStateRefreshListModel.pageSize,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// UI使用
|
||||
class NewsListPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderWidget<NewsListViewModel>(
|
||||
model: NewsListViewModel(),
|
||||
onModelReady: (model) => model.refresh(init: true),
|
||||
builder: (context, model, child) {
|
||||
return EasyRefresh(
|
||||
controller: model.refreshController,
|
||||
onRefresh: () => model.refresh(),
|
||||
onLoad: () => model.loadMore(),
|
||||
child: ListView.builder(
|
||||
itemCount: model.list.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = model.list[index];
|
||||
return ListTile(
|
||||
title: Text(item.title),
|
||||
subtitle: Text(item.summary),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 双Model管理示例
|
||||
|
||||
```dart
|
||||
class DashboardPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderWidget2<UserViewModel, NotificationViewModel>(
|
||||
model1: UserViewModel(),
|
||||
model2: NotificationViewModel(),
|
||||
onModelReady: (userModel, notificationModel) {
|
||||
userModel.loadUserInfo();
|
||||
notificationModel.loadNotifications();
|
||||
},
|
||||
builder: (context, userModel, notificationModel, child) {
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
// 用户信息区域
|
||||
if (userModel.isBusy)
|
||||
CircularProgressIndicator()
|
||||
else
|
||||
UserInfoWidget(user: userModel.currentUser),
|
||||
|
||||
// 通知区域
|
||||
if (notificationModel.isBusy)
|
||||
CircularProgressIndicator()
|
||||
else
|
||||
NotificationListWidget(notifications: notificationModel.notifications),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖配置
|
||||
|
||||
### pubspec.yaml 关键依赖
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# 状态管理
|
||||
provider: ^6.0.0
|
||||
|
||||
# 列表刷新
|
||||
easy_refresh: ^3.0.0
|
||||
|
||||
# 日志记录
|
||||
# (自定义日志工具类)
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. ViewModel设计原则
|
||||
- 继承ViewStateModel获得基础状态管理能力
|
||||
- 将业务逻辑封装在ViewModel中,保持View的简洁
|
||||
- 合理使用状态枚举,提供良好的用户体验反馈
|
||||
- 及时释放资源,避免内存泄漏
|
||||
|
||||
### 2. 错误处理策略
|
||||
- 使用ViewStateErrorType枚举区分不同错误类型
|
||||
- 在UI层根据错误类型提供相应的用户提示
|
||||
- 网络错误提供重试机制
|
||||
|
||||
### 3. 列表优化建议
|
||||
- 使用ViewStateRefreshListModel处理列表数据
|
||||
- 合理设置分页大小,平衡性能和用户体验
|
||||
- 实现适当的缓存策略减少不必要的网络请求
|
||||
|
||||
### 4. 内存管理
|
||||
- ProviderWidget默认开启autoDispose,自动管理Model生命周期
|
||||
- 在Model的dispose方法中清理定时器、流订阅等资源
|
||||
- 避免在已销毁的Model上调用notifyListeners
|
||||
|
||||
## 扩展开发
|
||||
|
||||
### 1. 自定义状态类型
|
||||
可以扩展ViewState枚举添加业务特定的状态:
|
||||
|
||||
```dart
|
||||
enum CustomViewState {
|
||||
idle,
|
||||
busy,
|
||||
empty,
|
||||
error,
|
||||
networkError, // 自定义网络错误状态
|
||||
authRequired, // 自定义需要认证状态
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 自定义刷新组件
|
||||
基于base_mvvm的基础组件,可以创建适合特定业务场景的刷新组件。
|
||||
|
||||
### 3. 状态持久化
|
||||
结合SharedPreferences等持久化方案,实现ViewState的持久化存储。
|
||||
|
||||
## 问题排查
|
||||
|
||||
### 常见问题
|
||||
1. **Model未正确释放**: 检查ProviderWidget的autoDispose设置
|
||||
2. **状态更新无效**: 确认notifyListeners调用时机
|
||||
3. **列表刷新异常**: 检查EasyRefreshController的状态管理
|
||||
|
||||
### 调试技巧
|
||||
- 使用LogUtils记录关键状态变更
|
||||
- 通过ViewState枚举值判断当前页面状态
|
||||
- 利用Flutter Inspector查看Provider状态
|
||||
513
basic_utils/basic_config.md
Normal file
513
basic_utils/basic_config.md
Normal file
@@ -0,0 +1,513 @@
|
||||
# Basic Config - 基础配置管理模块文档
|
||||
|
||||
## 模块概述
|
||||
|
||||
`basic_config` 是 OneApp 基础工具模块群中的配置管理模块,提供了应用配置的统一管理、API服务管控、版本控制等功能。该模块采用领域驱动设计(DDD)架构,支持动态配置更新、服务白名单管理和API访问控制。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: basic_config
|
||||
- **模块路径**: oneapp_basic_utils/basic_config
|
||||
- **类型**: Flutter Package Module
|
||||
- **架构模式**: DDD (Domain Driven Design)
|
||||
- **主要功能**: 配置管理、API服务管控、版本管理
|
||||
|
||||
### 核心特性
|
||||
- **API服务管控**: 基于正则表达式的API路径匹配和访问控制
|
||||
- **服务白名单**: 支持白名单机制,允许特定服务绕过管控
|
||||
- **动态配置更新**: 支持运行时更新服务规则列表
|
||||
- **版本管理**: 内置版本比较和管理功能
|
||||
- **缓存优化**: 使用LRU缓存提升查询性能
|
||||
- **项目隔离**: 支持多项目代码隔离的配置管理
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
basic_config/
|
||||
├── lib/
|
||||
│ ├── basic_config.dart # 模块入口文件
|
||||
│ └── src/
|
||||
│ ├── constants/ # 常量定义
|
||||
│ │ └── module_constant.dart
|
||||
│ ├── dependency/ # 依赖注入
|
||||
│ │ └── i_config_deps.dart
|
||||
│ ├── domains/ # 领域层
|
||||
│ │ ├── basic_config_facade.dart # 配置门面服务
|
||||
│ │ ├── entities/ # 实体对象
|
||||
│ │ │ ├── config_service_failures.dart
|
||||
│ │ │ ├── config_versions.dart
|
||||
│ │ │ └── project_services.dart
|
||||
│ │ ├── interfaces/ # 接口定义
|
||||
│ │ │ └── app_api_services_interface.dart
|
||||
│ │ └── value_objects/ # 值对象
|
||||
│ │ └── project_code.dart
|
||||
│ └── infrastructure/ # 基础设施层
|
||||
│ └── repositories/
|
||||
│ └── app_api_seervices_repository.dart
|
||||
└── pubspec.yaml # 依赖配置
|
||||
```
|
||||
|
||||
## 核心架构组件
|
||||
|
||||
### 1. 版本管理 (Version)
|
||||
|
||||
提供语义化版本号的解析和比较功能:
|
||||
|
||||
```dart
|
||||
mixin Version {
|
||||
/// 转成字符串格式
|
||||
String get toStr => '$major.$minor.$revision';
|
||||
|
||||
/// 版本比较 - 大于
|
||||
bool greaterThan(Version other) {
|
||||
if (major > other.major) return true;
|
||||
if (minor > other.minor && major == other.major) return true;
|
||||
if (revision > other.revision &&
|
||||
major == other.major &&
|
||||
minor == other.minor) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 主版本号
|
||||
int get major;
|
||||
/// 次版本号
|
||||
int get minor;
|
||||
/// 修订版本号
|
||||
int get revision;
|
||||
|
||||
@override
|
||||
String toString() => toStr;
|
||||
|
||||
/// 解析版本字符串 (格式: x.x.x)
|
||||
static Version parseFrom(String versionStr) {
|
||||
final split = versionStr.split('.');
|
||||
if (split.length != 3) {
|
||||
throw UnsupportedError('parse version From $versionStr failed');
|
||||
}
|
||||
|
||||
final int major = int.parse(split[0]);
|
||||
final int minor = int.parse(split[1]);
|
||||
final int revision = int.parse(split[2]);
|
||||
|
||||
return _VersionImpl(major, minor, revision);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 项目服务实体 (ProjectServicesDo)
|
||||
|
||||
定义单个项目的服务配置信息:
|
||||
|
||||
```dart
|
||||
class ProjectServicesDo {
|
||||
/// 构造函数
|
||||
/// [projectCode] 项目编号
|
||||
/// [ruleList] api path 正则规则
|
||||
/// [disableServiceList] 下架的服务列表
|
||||
ProjectServicesDo({
|
||||
required this.projectCode,
|
||||
required this.ruleList,
|
||||
required this.disableServiceList,
|
||||
}) : _ruleListRegex = ruleList.map(RegExp.new).toList(growable: false);
|
||||
|
||||
/// 项目编号
|
||||
final ProjectCodeVo projectCode;
|
||||
|
||||
/// 该项目对应的规则列表
|
||||
final List<String> ruleList;
|
||||
|
||||
/// 对应正则表达式
|
||||
final List<RegExp> _ruleListRegex;
|
||||
|
||||
/// 下架的服务
|
||||
final List<String> disableServiceList;
|
||||
|
||||
/// 检查路径是否匹配规则
|
||||
bool isMatchBy(String s) {
|
||||
bool match = false;
|
||||
for (final rule in _ruleListRegex) {
|
||||
match = rule.hasMatch(s);
|
||||
if (match) break;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 应用项目管理 (AppProjectsDo)
|
||||
|
||||
管理所有项目的服务配置:
|
||||
|
||||
```dart
|
||||
class AppProjectsDo {
|
||||
/// 构造函数
|
||||
AppProjectsDo(this.version, this.projects) {
|
||||
for (final project in projects) {
|
||||
_projectsMap[project.projectCode.id] = project;
|
||||
}
|
||||
}
|
||||
|
||||
/// 版本号
|
||||
final int version;
|
||||
|
||||
/// app所有项目的信息
|
||||
List<ProjectServicesDo> projects;
|
||||
|
||||
/// 项目映射表
|
||||
final Map<String, ProjectServicesDo> _projectsMap = {};
|
||||
|
||||
/// 根据项目代码查找项目
|
||||
ProjectServicesDo? findBy(String projectCode) => _projectsMap[projectCode];
|
||||
|
||||
/// 检查服务是否已下架
|
||||
bool isServiceDisabled({
|
||||
required String projectCode,
|
||||
required String service,
|
||||
}) {
|
||||
try {
|
||||
final project = _projectsMap[projectCode];
|
||||
project!.disableServiceList.firstWhere((e) => e == service);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取所有项目编号
|
||||
List<String> getProjectCodes() => _projectsMap.keys.toList(growable: false);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 基础配置门面 (BasicConfigFacade)
|
||||
|
||||
核心配置管理服务,采用单例模式:
|
||||
|
||||
```dart
|
||||
abstract class IBasicConfigFacade {
|
||||
/// 初始化配置
|
||||
Future<Either<ConfigServiceFailures, Unit>> initialize({
|
||||
required String versionOfConnectivity,
|
||||
String jsonServiceList = '',
|
||||
List<String> whiteServiceList = const [],
|
||||
IBasicConfigDeps? deps,
|
||||
});
|
||||
|
||||
/// 检查API是否命中管控规则
|
||||
Either<ConfigServiceFailures, bool> queryApiIfHit({
|
||||
String projectCode = '',
|
||||
String url = '',
|
||||
});
|
||||
|
||||
/// 检查API是否在白名单
|
||||
bool queryApiIfInWhiteList({required String url});
|
||||
|
||||
/// 主动更新服务规则列表
|
||||
Future<bool> updateServiceList({List<String> projectCodes = const []});
|
||||
|
||||
/// 检查服务是否下架
|
||||
bool isServiceDisabled({
|
||||
required String projectCode,
|
||||
required String service,
|
||||
});
|
||||
|
||||
/// 根据项目代码查询项目信息
|
||||
Either<ConfigServiceFailures, ProjectServicesDo> queryProjectBy({
|
||||
String projectCode = '',
|
||||
});
|
||||
|
||||
/// 获取当前连接版本
|
||||
Version get currConnVersion;
|
||||
}
|
||||
|
||||
/// 全局配置对象
|
||||
IBasicConfigFacade basicConfigFacade = BasicConfigFacadeImpl();
|
||||
```
|
||||
|
||||
### 5. 具体实现 (BasicConfigFacadeImpl)
|
||||
|
||||
```dart
|
||||
class BasicConfigFacadeImpl implements IBasicConfigFacade {
|
||||
BasicConfigFacadeImpl({IAppApiServiceRepo? appApiServiceRepo})
|
||||
: _apiServiceRepo = appApiServiceRepo ?? ApiServiceListRepository();
|
||||
|
||||
static const String _tag = 'BasicConfigFacadeImpl';
|
||||
|
||||
final IAppApiServiceRepo _apiServiceRepo;
|
||||
IBasicConfigDeps _deps = const DefaultConfigDeps();
|
||||
final _cache = LruCache<String, bool>(storage: InMemoryStorage(20));
|
||||
late Version _connVersion;
|
||||
|
||||
@override
|
||||
Future<Either<ConfigServiceFailures, Unit>> initialize({
|
||||
required String versionOfConnectivity,
|
||||
String jsonServiceList = '',
|
||||
List<String> whiteServiceList = const [],
|
||||
IBasicConfigDeps? deps,
|
||||
}) async {
|
||||
if (deps != null) _deps = deps;
|
||||
_connVersion = Version.parseFrom(versionOfConnectivity);
|
||||
|
||||
// 初始化api管控配置列表
|
||||
final r = await _apiServiceRepo.initialize(
|
||||
deps: _deps,
|
||||
jsonServiceList: jsonServiceList,
|
||||
whiteServiceList: whiteServiceList,
|
||||
);
|
||||
|
||||
return r ? right(unit) : left(ConfigServiceFailures(
|
||||
errorCodeConfigServiceInvalidLocalServiceList,
|
||||
'initialize failed',
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Either<ConfigServiceFailures, bool> queryApiIfHit({
|
||||
String projectCode = '',
|
||||
String url = '',
|
||||
}) {
|
||||
final appProject = _apiServiceRepo.appProject;
|
||||
if (appProject == null) {
|
||||
return left(ConfigServiceFailures(
|
||||
errorCodeConfigServiceEmptyServiceList,
|
||||
'empty service list',
|
||||
));
|
||||
}
|
||||
|
||||
final findBy = appProject.findBy(projectCode);
|
||||
if (findBy == null) return right(false);
|
||||
|
||||
// 使用缓存优化查询性能
|
||||
final hitCache = _cache.get(url);
|
||||
if (hitCache == null) {
|
||||
final matchBy = findBy.isMatchBy(url);
|
||||
_cache.set(url, matchBy);
|
||||
return right(matchBy);
|
||||
}
|
||||
|
||||
return right(hitCache);
|
||||
}
|
||||
|
||||
// 其他方法实现...
|
||||
}
|
||||
```
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 1. 初始化配置
|
||||
|
||||
```dart
|
||||
import 'package:basic_config/basic_config.dart';
|
||||
|
||||
// 初始化基础配置
|
||||
await basicConfigFacade.initialize(
|
||||
versionOfConnectivity: '1.0.0',
|
||||
jsonServiceList: jsonConfigData,
|
||||
whiteServiceList: ['api/health', 'api/version'],
|
||||
);
|
||||
```
|
||||
|
||||
### 2. API访问控制
|
||||
|
||||
```dart
|
||||
// 检查API是否命中管控规则
|
||||
final result = basicConfigFacade.queryApiIfHit(
|
||||
projectCode: 'oneapp_main',
|
||||
url: '/api/user/profile',
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => print('查询失败: ${failure.message}'),
|
||||
(isHit) => {
|
||||
if (isHit) {
|
||||
print('API被管控,需要特殊处理')
|
||||
} else {
|
||||
print('API正常访问')
|
||||
}
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### 3. 白名单检查
|
||||
|
||||
```dart
|
||||
// 检查API是否在白名单
|
||||
bool inWhiteList = basicConfigFacade.queryApiIfInWhiteList(
|
||||
url: '/api/health'
|
||||
);
|
||||
|
||||
if (inWhiteList) {
|
||||
// 白名单API,直接放行
|
||||
print('白名单API,允许访问');
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 服务状态检查
|
||||
|
||||
```dart
|
||||
// 检查服务是否下架
|
||||
bool isDisabled = basicConfigFacade.isServiceDisabled(
|
||||
projectCode: 'oneapp_main',
|
||||
service: 'user_profile',
|
||||
);
|
||||
|
||||
if (isDisabled) {
|
||||
// 服务已下架,显示维护页面
|
||||
showMaintenancePage();
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 动态更新配置
|
||||
|
||||
```dart
|
||||
// 更新服务规则列表
|
||||
bool updateSuccess = await basicConfigFacade.updateServiceList(
|
||||
projectCodes: ['oneapp_main', 'oneapp_car'],
|
||||
);
|
||||
|
||||
if (updateSuccess) {
|
||||
print('配置更新成功');
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 版本管理
|
||||
|
||||
```dart
|
||||
// 版本解析和比较
|
||||
Version currentVersion = Version.parseFrom('1.2.3');
|
||||
Version newVersion = Version.parseFrom('1.2.4');
|
||||
|
||||
if (newVersion.greaterThan(currentVersion)) {
|
||||
print('发现新版本: ${newVersion.toStr}');
|
||||
// 执行更新逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 配置文件格式
|
||||
|
||||
### 服务配置JSON格式
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"projects": [
|
||||
{
|
||||
"projectCode": "oneapp_main",
|
||||
"ruleList": [
|
||||
"^/api/user/.*",
|
||||
"^/api/payment/.*"
|
||||
],
|
||||
"disableServiceList": [
|
||||
"old_payment_service",
|
||||
"deprecated_user_api"
|
||||
]
|
||||
},
|
||||
{
|
||||
"projectCode": "oneapp_car",
|
||||
"ruleList": [
|
||||
"^/api/vehicle/.*",
|
||||
"^/api/charging/.*"
|
||||
],
|
||||
"disableServiceList": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖配置
|
||||
|
||||
### pubspec.yaml 关键依赖
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# 函数式编程支持
|
||||
dartz: ^0.10.1
|
||||
|
||||
# 基础日志模块
|
||||
basic_logger:
|
||||
path: ../basic_logger
|
||||
|
||||
# 基础存储模块
|
||||
basic_storage:
|
||||
path: ../basic_storage
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
```
|
||||
|
||||
## 架构设计原则
|
||||
|
||||
### 1. DDD分层架构
|
||||
- **Domain层**: 包含业务实体、值对象和领域服务
|
||||
- **Infrastructure层**: 处理数据持久化和外部服务调用
|
||||
- **Application层**: 通过Facade模式提供应用服务
|
||||
|
||||
### 2. 函数式编程
|
||||
- 使用`dartz`包提供的`Either`类型处理错误
|
||||
- 避免异常抛出,通过类型系统表达可能的失败情况
|
||||
|
||||
### 3. 依赖注入
|
||||
- 通过接口定义依赖,支持测试替换
|
||||
- 使用抽象类定义服务边界
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 缓存策略
|
||||
- 使用LRU缓存存储API匹配结果
|
||||
- 缓存大小限制为20个条目,避免内存过度使用
|
||||
|
||||
### 2. 正则表达式优化
|
||||
- 预编译正则表达式,避免重复编译开销
|
||||
- 使用不可变列表存储编译后的正则
|
||||
|
||||
### 3. 查询优化
|
||||
- 使用Map结构优化项目查找性能
|
||||
- 短路求值减少不必要的匹配操作
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 错误处理
|
||||
```dart
|
||||
// 推荐:使用Either处理可能的错误
|
||||
final result = basicConfigFacade.queryApiIfHit(
|
||||
projectCode: projectCode,
|
||||
url: url,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => handleFailure(failure),
|
||||
(success) => handleSuccess(success),
|
||||
);
|
||||
|
||||
// 不推荐:使用try-catch
|
||||
try {
|
||||
final result = riskyOperation();
|
||||
handleSuccess(result);
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 配置管理
|
||||
- 在应用启动时初始化配置
|
||||
- 定期检查和更新远程配置
|
||||
- 为关键服务提供降级策略
|
||||
|
||||
### 3. 测试策略
|
||||
- 使用依赖注入进行单元测试
|
||||
- 模拟网络请求测试异常情况
|
||||
- 验证缓存行为的正确性
|
||||
|
||||
## 问题排查
|
||||
|
||||
### 常见问题
|
||||
1. **初始化失败**: 检查配置JSON格式和依赖注入设置
|
||||
2. **正则匹配异常**: 验证规则列表中的正则表达式语法
|
||||
3. **缓存不生效**: 确认URL格式一致性
|
||||
|
||||
### 调试技巧
|
||||
- 启用详细日志查看配置加载过程
|
||||
- 使用`basicConfigTag`过滤相关日志
|
||||
- 检查版本解析是否符合x.x.x格式
|
||||
445
basic_utils/basic_logger.md
Normal file
445
basic_utils/basic_logger.md
Normal file
@@ -0,0 +1,445 @@
|
||||
# Basic Logger - 日志系统模块文档
|
||||
|
||||
## 模块概述
|
||||
|
||||
`basic_logger` 是 OneApp 基础工具模块群中的日志系统核心模块,提供统一的日志记录、管理和分析功能。该模块支持多级别日志、文件存储、网络上传、事件打点等功能,并提供了 Android 和 iOS 的原生日志监控能力。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: basic_logger
|
||||
- **模块路径**: oneapp_basic_utils/basic_logger
|
||||
- **类型**: Flutter Plugin Module
|
||||
- **主要功能**: 日志记录、事件打点、文件上传、原生日志监控
|
||||
|
||||
### 核心特性
|
||||
- **多级别日志**: 支持debug、info、warn、error四个级别
|
||||
- **业务标签**: 预定义车联网、账户、充电等业务场景标签
|
||||
- **文件记录**: 支持日志文件记录和轮转机制
|
||||
- **网络上传**: 支持日志文件网络上传功能
|
||||
- **事件打点**: 专门的事件日志记录能力
|
||||
- **原生监控**: Android/iOS原生日志监控和采集
|
||||
- **性能优化**: 支持日志级别过滤和调试开关
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
basic_logger/
|
||||
├── lib/
|
||||
│ ├── basic_logger.dart # 模块入口文件
|
||||
│ ├── logcat_monitor.dart # 原生日志监控
|
||||
│ ├── kit_logger_ios.dart # iOS特定实现
|
||||
│ └── src/ # 源代码目录
|
||||
│ ├── event_log/ # 事件日志
|
||||
│ │ └── one_event_log.dart
|
||||
│ ├── function/ # 核心功能
|
||||
│ │ └── one_app_log.dart # 主日志类
|
||||
│ ├── model/ # 数据模型
|
||||
│ │ ├── upload_config.dart
|
||||
│ │ └── upload_file_info.dart
|
||||
│ ├── record/ # 记录功能
|
||||
│ │ ├── core.dart
|
||||
│ │ ├── record_delegate.dart
|
||||
│ │ └── impl_dart/
|
||||
│ │ ├── dart_record.dart
|
||||
│ │ └── dart_file_record_.dart
|
||||
│ └── upload/ # 上传功能
|
||||
│ ├── core.dart
|
||||
│ └── upload_handler.dart
|
||||
├── android/ # Android原生实现
|
||||
├── ios/ # iOS原生实现
|
||||
└── pubspec.yaml # 依赖配置
|
||||
```
|
||||
|
||||
## 核心架构组件
|
||||
|
||||
### 1. 日志级别枚举 (Level)
|
||||
|
||||
定义应用日志的级别:
|
||||
|
||||
```dart
|
||||
/// Log Level
|
||||
enum Level {
|
||||
/// none - 无日志
|
||||
none,
|
||||
/// info - 信息日志
|
||||
info,
|
||||
/// debug - 调试日志
|
||||
debug,
|
||||
/// warn - 警告日志
|
||||
warn,
|
||||
/// error - 错误日志
|
||||
error,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 主日志类 (OneAppLog)
|
||||
|
||||
应用日志的核心实现类:
|
||||
|
||||
```dart
|
||||
/// application层:日志
|
||||
class OneAppLog {
|
||||
OneAppLog._();
|
||||
|
||||
/// logger flag
|
||||
static const int loggerFlag = 1;
|
||||
static const String _defaultTag = 'default';
|
||||
|
||||
static bool _debuggable = false;
|
||||
static Level _filterLevel = Level.none;
|
||||
|
||||
/// 配置日志系统
|
||||
/// [debuggable] 调试开关
|
||||
/// [filterLevel] 日志过滤级别
|
||||
static void config({
|
||||
bool debuggable = false,
|
||||
Level filterLevel = Level.none,
|
||||
}) {
|
||||
_debuggable = debuggable;
|
||||
_filterLevel = filterLevel;
|
||||
}
|
||||
|
||||
/// info级别日志
|
||||
/// [msg] 日志内容
|
||||
/// [tag] 日志标签
|
||||
static void i(String msg, [String tag = _defaultTag]) {
|
||||
if (Level.info.index > _filterLevel.index) {
|
||||
_log(Level.info, msg, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// debug级别日志
|
||||
/// [msg] 日志内容
|
||||
/// [tag] 日志标签
|
||||
static void d(String msg, [String tag = _defaultTag]) {
|
||||
if (_debuggable && Level.debug.index > _filterLevel.index) {
|
||||
_log(Level.debug, msg, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// warn级别日志
|
||||
/// [msg] 日志内容
|
||||
/// [tag] 日志标签
|
||||
static void w(String msg, [String tag = _defaultTag]) {
|
||||
if (Level.warn.index > _filterLevel.index) {
|
||||
_log(Level.warn, msg, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// error级别日志
|
||||
/// [msg] 日志内容
|
||||
/// [tag] 日志标签
|
||||
static void e(String msg, [String tag = _defaultTag]) {
|
||||
_log(Level.error, msg, tag);
|
||||
}
|
||||
|
||||
/// 上传日志文件
|
||||
/// [fileName] 文件名,如果为空内部自动生成
|
||||
/// [handler] 上传处理器
|
||||
/// [config] 上传配置
|
||||
static Future<UploadFileResult> upload(
|
||||
String? fileName,
|
||||
UploadHandler handler,
|
||||
UploadConfig config,
|
||||
) => UploadManager().upload(fileName, handler, config);
|
||||
|
||||
// 内部日志记录实现
|
||||
static void _log(Level level, String msg, String tag) {
|
||||
final time = DateTime.now();
|
||||
final emoji = _levelEmojis[level];
|
||||
recordIns.record(loggerFlag, '$time: $emoji[$tag] $msg');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 业务标签定义
|
||||
|
||||
为不同业务场景预定义的日志标签:
|
||||
|
||||
```dart
|
||||
/// App通用标签
|
||||
const tagApp = 'App'; // App全局日志
|
||||
const tagRoute = 'Route'; // 路由跳转
|
||||
const tagNetwork = 'Network'; // 网络请求
|
||||
const tagWebView = 'WebView'; // WebView相关
|
||||
|
||||
/// 业务标签
|
||||
const tagCommunity = 'Community'; // 社区功能
|
||||
const tagCarSale = 'CarSale'; // 汽车销售
|
||||
const tagAfterSale = 'AfterSale'; // 售后服务
|
||||
const tagMall = 'Mall'; // 商城
|
||||
const tagOrder = 'Order'; // 订单
|
||||
const tagMaintenance = 'Maintenance'; // 保养维护
|
||||
|
||||
/// 车联网标签
|
||||
const tagVehicleSDK = 'VehicleSDK'; // CEA/MM SDK
|
||||
const tagAccount = 'Account'; // 账户系统
|
||||
const tagCarHome = 'CarHome'; // 爱车首页
|
||||
const tagMDK = 'MDK'; // MDK相关
|
||||
const tagRemoteControl = 'RemoteControl'; // 远程控制
|
||||
const tagHVAC = 'HVAC'; // 空调系统
|
||||
const tagCarFind = 'CarFind'; // 寻车功能
|
||||
const tag3DModel = '3DModel'; // 3D模型
|
||||
const tagRPA = 'RPA'; // RPA功能
|
||||
const tagCamera = 'Camera'; // 摄像头
|
||||
const tagIntelligentScene = 'IntelligentScene'; // 智能场景
|
||||
const tagRVS = 'RVS'; // 远程车辆状态
|
||||
const tagVUR = 'VUR'; // 用车报告
|
||||
const tagAvatar = 'Avatar'; // 虚拟形象
|
||||
const tagTouchGo = 'TouchGo'; // 小组件
|
||||
const tagFridge = 'Fridge'; // 冰箱
|
||||
const tagWallbox = 'Wallbox'; // 壁挂充电盒
|
||||
const tagOTA = 'OTA'; // 空中升级
|
||||
const tagCharging = 'Charging'; // 充电功能
|
||||
const tagMessage = 'Message'; // 通知消息
|
||||
```
|
||||
|
||||
### 4. 日志级别表情符号映射
|
||||
|
||||
```dart
|
||||
final Map<Level, String> _levelEmojis = {
|
||||
Level.debug: '🐛',
|
||||
Level.info: '💡💡',
|
||||
Level.warn: '⚠️⚠️⚠️',
|
||||
Level.error: '❌❌❌',
|
||||
};
|
||||
```
|
||||
|
||||
### 5. Logger类型别名
|
||||
|
||||
```dart
|
||||
typedef Logger = OneAppLog;
|
||||
```
|
||||
|
||||
## 使用指南
|
||||
|
||||
### 1. 日志系统初始化
|
||||
|
||||
```dart
|
||||
import 'package:basic_logger/basic_logger.dart';
|
||||
|
||||
// 配置日志系统
|
||||
OneAppLog.config(
|
||||
debuggable: true, // 开启调试模式
|
||||
filterLevel: Level.debug, // 设置过滤级别
|
||||
);
|
||||
```
|
||||
|
||||
### 2. 基础日志记录
|
||||
|
||||
```dart
|
||||
// 使用预定义标签
|
||||
OneAppLog.i('用户登录成功', tagAccount);
|
||||
OneAppLog.d('调试信息:用户ID = 12345', tagAccount);
|
||||
OneAppLog.w('网络请求超时,正在重试', tagNetwork);
|
||||
OneAppLog.e('登录失败:用户名或密码错误', tagAccount);
|
||||
|
||||
// 使用默认标签
|
||||
OneAppLog.i('应用启动完成');
|
||||
OneAppLog.e('未知错误发生');
|
||||
```
|
||||
|
||||
### 3. 车联网业务日志
|
||||
|
||||
```dart
|
||||
// 车辆控制相关
|
||||
OneAppLog.i('开始远程启动车辆', tagRemoteControl);
|
||||
OneAppLog.w('车辆锁定状态异常', tagVehicleSDK);
|
||||
OneAppLog.e('空调控制指令失败', tagHVAC);
|
||||
|
||||
// 充电相关
|
||||
OneAppLog.i('开始充电', tagCharging);
|
||||
OneAppLog.w('充电桩连接不稳定', tagCharging);
|
||||
OneAppLog.e('充电异常停止', tagCharging);
|
||||
|
||||
// 3D模型相关
|
||||
OneAppLog.d('3D模型加载中', tag3DModel);
|
||||
OneAppLog.i('3D模型渲染完成', tag3DModel);
|
||||
|
||||
// 虚拟形象相关
|
||||
OneAppLog.i('虚拟形象初始化', tagAvatar);
|
||||
OneAppLog.w('虚拟形象动画加载超时', tagAvatar);
|
||||
```
|
||||
|
||||
### 4. 日志文件上传
|
||||
|
||||
```dart
|
||||
// 创建上传配置
|
||||
final uploadConfig = UploadConfig(
|
||||
serverUrl: 'https://api.example.com/logs',
|
||||
apiKey: 'your_api_key',
|
||||
timeout: Duration(seconds: 30),
|
||||
);
|
||||
|
||||
// 创建上传处理器
|
||||
final uploadHandler = CustomUploadHandler();
|
||||
|
||||
// 上传日志文件
|
||||
try {
|
||||
final result = await OneAppLog.upload(
|
||||
'app_logs_20231201.log',
|
||||
uploadHandler,
|
||||
uploadConfig,
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
OneAppLog.i('日志上传成功', tagApp);
|
||||
} else {
|
||||
OneAppLog.e('日志上传失败: ${result.error}', tagApp);
|
||||
}
|
||||
} catch (e) {
|
||||
OneAppLog.e('日志上传异常: $e', tagApp);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 条件日志记录
|
||||
|
||||
```dart
|
||||
// 仅在调试模式下记录
|
||||
if (kDebugMode) {
|
||||
OneAppLog.d('这是调试信息,仅开发时可见', tagApp);
|
||||
}
|
||||
|
||||
// 根据业务条件记录
|
||||
void onUserAction(String action) {
|
||||
OneAppLog.i('用户执行操作: $action', tagApp);
|
||||
|
||||
if (action == 'high_risk_operation') {
|
||||
OneAppLog.w('用户执行高风险操作', tagApp);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖配置
|
||||
|
||||
### pubspec.yaml 配置
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# 基础日志模块
|
||||
basic_logger:
|
||||
path: ../oneapp_basic_utils/basic_logger
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
```
|
||||
|
||||
## 高级功能
|
||||
|
||||
### 1. 自定义上传处理器
|
||||
|
||||
```dart
|
||||
class CustomUploadHandler extends UploadHandler {
|
||||
@override
|
||||
Future<UploadFileResult> upload(
|
||||
String filePath,
|
||||
UploadConfig config
|
||||
) async {
|
||||
// 实现自定义上传逻辑
|
||||
try {
|
||||
// 发送HTTP请求上传文件
|
||||
final response = await http.post(
|
||||
Uri.parse(config.serverUrl),
|
||||
headers: {'Authorization': 'Bearer ${config.apiKey}'},
|
||||
body: await File(filePath).readAsBytes(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return UploadFileResult.success();
|
||||
} else {
|
||||
return UploadFileResult.failure('上传失败: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
return UploadFileResult.failure('上传异常: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 事件日志记录
|
||||
|
||||
```dart
|
||||
import 'package:basic_logger/basic_logger.dart';
|
||||
|
||||
// 记录事件日志
|
||||
OneEventLog.record({
|
||||
'event_type': 'user_click',
|
||||
'element_id': 'login_button',
|
||||
'timestamp': DateTime.now().millisecondsSinceEpoch,
|
||||
'user_id': '12345',
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 原生日志监控
|
||||
|
||||
```dart
|
||||
import 'package:basic_logger/logcat_monitor.dart';
|
||||
|
||||
// 开启原生日志监控
|
||||
final monitor = LogcatMonitor();
|
||||
await monitor.startMonitoring();
|
||||
|
||||
// 停止监控
|
||||
await monitor.stopMonitoring();
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 日志级别管理
|
||||
- 生产环境关闭debug日志:`debuggable: false`
|
||||
- 设置合适的过滤级别:`filterLevel: Level.warn`
|
||||
- 避免在循环中大量打印日志
|
||||
|
||||
### 2. 文件管理
|
||||
- 定期清理过期日志文件
|
||||
- 控制日志文件大小,避免占用过多存储空间
|
||||
- 使用日志轮转机制
|
||||
|
||||
### 3. 网络上传优化
|
||||
- 在WiFi环境下上传日志
|
||||
- 压缩日志文件减少网络开销
|
||||
- 实现上传失败重试机制
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 标签使用规范
|
||||
```dart
|
||||
// 推荐:使用预定义业务标签
|
||||
OneAppLog.i('充电状态更新', tagCharging);
|
||||
|
||||
// 避免:使用无意义的标签
|
||||
OneAppLog.i('充电状态更新', 'test');
|
||||
```
|
||||
|
||||
### 2. 敏感信息保护
|
||||
```dart
|
||||
// 推荐:脱敏处理
|
||||
OneAppLog.i('用户登录: ${userId.substring(0, 3)}***', tagAccount);
|
||||
|
||||
// 避免:直接记录敏感信息
|
||||
OneAppLog.i('用户登录: $userPassword', tagAccount);
|
||||
```
|
||||
|
||||
### 3. 错误日志详细性
|
||||
```dart
|
||||
// 推荐:提供详细上下文
|
||||
OneAppLog.e('网络请求失败: $url, 状态码: $statusCode, 错误: $error', tagNetwork);
|
||||
|
||||
// 避免:信息不足的错误日志
|
||||
OneAppLog.e('请求失败', tagNetwork);
|
||||
```
|
||||
|
||||
## 问题排查
|
||||
|
||||
### 常见问题
|
||||
1. **日志不显示**: 检查debuggable配置和filterLevel设置
|
||||
2. **文件上传失败**: 确认网络权限和上传配置
|
||||
3. **性能影响**: 避免高频日志输出,合理设置日志级别
|
||||
|
||||
### 调试技巧
|
||||
- 使用Android Studio/Xcode查看原生日志
|
||||
- 通过文件系统检查日志文件生成
|
||||
- 监控应用内存使用避免日志系统影响性能
|
||||
854
basic_utils/basic_network.md
Normal file
854
basic_utils/basic_network.md
Normal file
@@ -0,0 +1,854 @@
|
||||
# Basic Network - 网络通信模块文档
|
||||
|
||||
## 模块概述
|
||||
|
||||
`basic_network` 是 OneApp 基础工具模块群中的网络通信核心模块,基于 Dio 框架封装,提供统一的 HTTP 请求接口、拦截器机制、错误处理和日志记录功能。该模块为整个应用提供标准化的网络通信能力。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: basic_network
|
||||
- **版本**: 0.2.3+4
|
||||
- **仓库**: https://gitlab-rd0.maezia.com/eziahz/oneapp/ezia-oneapp-basic-network
|
||||
- **Dart 版本**: >=2.17.0 <4.0.0
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
basic_network/
|
||||
├── lib/
|
||||
│ ├── basic_network.dart # 主导出文件
|
||||
│ ├── common_dos.dart # 通用数据对象
|
||||
│ ├── common_dtos.dart # 通用数据传输对象
|
||||
│ └── src/ # 源代码目录
|
||||
│ ├── client/ # HTTP 客户端实现
|
||||
│ ├── interceptors/ # 请求拦截器
|
||||
│ ├── models/ # 数据模型
|
||||
│ ├── exceptions/ # 异常定义
|
||||
│ └── utils/ # 工具类
|
||||
├── pubspec.yaml # 依赖配置
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 核心功能模块
|
||||
|
||||
### 1. HTTP 客户端封装
|
||||
|
||||
基于真实项目的网络引擎架构,使用工厂模式和依赖注入。
|
||||
|
||||
#### 网络引擎初始化 (`facade.dart`)
|
||||
```dart
|
||||
// 实际的网络引擎初始化
|
||||
bool initNetwork({NetworkEngineOption? option, NetworkLogOutput? networkLog}) =>
|
||||
NetworkEngineContext().init(option: option, networkLog: networkLog);
|
||||
|
||||
/// 根据自定义option获取NetworkEngine
|
||||
RequestApi customNetworkEngine(NetworkEngineOption option) =>
|
||||
RequestApi(NetworkEngineFactory.createBy(option));
|
||||
|
||||
/// 统一的请求接口
|
||||
Future<T> request<T>(RequestOptions requestOptions) async =>
|
||||
NetworkEngineContext.networkEngine.request<T>(requestOptions);
|
||||
|
||||
/// 带回调的请求方法
|
||||
Future<void> requestWithCallback<T>(
|
||||
RequestOptions requestOptions,
|
||||
RequestOnSuccessCallback<T> onSuccess,
|
||||
RequestOnErrorCallback onError,
|
||||
) async {
|
||||
try {
|
||||
final res = await NetworkEngineContext.networkEngine.request<T>(
|
||||
requestOptions,
|
||||
);
|
||||
onSuccess(res);
|
||||
} on ErrorBase catch (e) {
|
||||
onError(e);
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
onError(ErrorGlobalCommon(GlobalCommonErrorType.other, e));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 默认网络引擎配置 (`common_options.dart`)
|
||||
```dart
|
||||
// 实际的默认网络引擎选项
|
||||
class DefaultNetworkEngineOption extends NetworkEngineOption {
|
||||
final Headers _headers = Headers();
|
||||
|
||||
final BaseDtoResponseConvertor _baseDtoResponseConvertor =
|
||||
const DefaultBaseJsonResponseConvertor();
|
||||
|
||||
final GlobalErrorBusinessFactory _globalBusinessErrorFactory =
|
||||
defaultGlobalBusinessErrorFactory;
|
||||
|
||||
@override
|
||||
BaseDtoResponseConvertor get baseDtoConvertor => _baseDtoResponseConvertor;
|
||||
|
||||
@override
|
||||
String get baseUrl => '';
|
||||
|
||||
@override
|
||||
Headers get headers => _headers;
|
||||
|
||||
@override
|
||||
List<Interceptor> get interceptors => [];
|
||||
|
||||
@override
|
||||
ProxyConfig? get proxyConfig => null;
|
||||
|
||||
@override
|
||||
int get receiveTimeout => 10000;
|
||||
|
||||
@override
|
||||
int get retryTime => 3;
|
||||
|
||||
@override
|
||||
int get sendTimeout => 10000;
|
||||
|
||||
@override
|
||||
int get connectTimeout => 10000;
|
||||
|
||||
@override
|
||||
SslConfig? get sslConfig => null;
|
||||
|
||||
@override
|
||||
List<GlobalErrorHandler> get globalErrorHandlers => [];
|
||||
|
||||
@override
|
||||
GlobalErrorBusinessFactory get globalErrorBusinessFactory =>
|
||||
_globalBusinessErrorFactory;
|
||||
|
||||
@override
|
||||
bool get debuggable => false;
|
||||
|
||||
@override
|
||||
String get environment => 'sit';
|
||||
|
||||
@override
|
||||
List<PreRequestInterceptor> get preRequestInterceptors => [];
|
||||
}
|
||||
```
|
||||
|
||||
#### RequestApi 类实现
|
||||
```dart
|
||||
// 实际的请求API封装类
|
||||
class RequestApi {
|
||||
RequestApi(this._engine);
|
||||
|
||||
final NetworkEngine _engine;
|
||||
|
||||
/// 异步请求,等待结果或抛出异常
|
||||
Future<T> request<T>(RequestOptions requestOptions) async =>
|
||||
_engine.request(requestOptions);
|
||||
|
||||
/// 带回调的请求方法
|
||||
Future<void> requestWithCallback<T>(
|
||||
RequestOptions requestOptions,
|
||||
RequestOnSuccessCallback<T> onSuccess,
|
||||
RequestOnErrorCallback onError,
|
||||
) async {
|
||||
try {
|
||||
final res = await _engine.request<T>(requestOptions);
|
||||
onSuccess(res);
|
||||
} on ErrorBase catch (e) {
|
||||
onError(e);
|
||||
} catch (e) {
|
||||
logger.e(e);
|
||||
onError(ErrorGlobalCommon(GlobalCommonErrorType.other, e));
|
||||
}
|
||||
}
|
||||
|
||||
/// 文件下载,带进度回调
|
||||
Future<HttpResponse> download({
|
||||
required dynamic savePath,
|
||||
required RequestOptions options,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
CancelToken? cancelToken,
|
||||
}) =>
|
||||
_engine.download(
|
||||
savePath: savePath,
|
||||
options: options,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// 文件上传,带进度回调
|
||||
Future<HttpResponse> upload({
|
||||
required RequestOptions options,
|
||||
CancelToken? cancelToken,
|
||||
ProgressCallback? onSendProgress,
|
||||
}) =>
|
||||
_engine.upload(
|
||||
options: options,
|
||||
onSendProgress: onSendProgress,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// 获取当前引擎配置
|
||||
NetworkEngineOption get option => _engine.option;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 请求拦截器系统
|
||||
|
||||
#### 请求拦截器
|
||||
```dart
|
||||
// 请求拦截器
|
||||
class RequestInterceptor extends Interceptor {
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
// 添加公共请求头
|
||||
_addCommonHeaders(options);
|
||||
|
||||
// 添加认证信息
|
||||
_addAuthenticationHeaders(options);
|
||||
|
||||
// 添加设备信息
|
||||
_addDeviceInfo(options);
|
||||
|
||||
// 请求签名
|
||||
_signRequest(options);
|
||||
|
||||
super.onRequest(options, handler);
|
||||
}
|
||||
|
||||
void _addCommonHeaders(RequestOptions options) {
|
||||
options.headers.addAll({
|
||||
'User-Agent': _getUserAgent(),
|
||||
'Accept-Language': _getAcceptLanguage(),
|
||||
'X-Request-ID': _generateRequestId(),
|
||||
'X-Timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
void _addAuthenticationHeaders(RequestOptions options) {
|
||||
final token = AuthManager.instance.accessToken;
|
||||
if (token != null) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
}
|
||||
|
||||
void _addDeviceInfo(RequestOptions options) {
|
||||
options.headers.addAll({
|
||||
'X-Device-ID': DeviceInfo.instance.deviceId,
|
||||
'X-App-Version': AppInfo.instance.version,
|
||||
'X-Platform': Platform.isAndroid ? 'android' : 'ios',
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应拦截器
|
||||
```dart
|
||||
// 响应拦截器
|
||||
class ResponseInterceptor extends Interceptor {
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
// 统一响应格式处理
|
||||
_processCommonResponse(response);
|
||||
|
||||
// 更新认证状态
|
||||
_updateAuthenticationStatus(response);
|
||||
|
||||
// 缓存响应数据
|
||||
_cacheResponseIfNeeded(response);
|
||||
|
||||
super.onResponse(response, handler);
|
||||
}
|
||||
|
||||
void _processCommonResponse(Response response) {
|
||||
if (response.data is Map<String, dynamic>) {
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
|
||||
// 检查业务状态码
|
||||
final code = data['code'] as int?;
|
||||
final message = data['message'] as String?;
|
||||
|
||||
if (code != null && code != 0) {
|
||||
throw BusinessException(code, message ?? '业务处理失败');
|
||||
}
|
||||
|
||||
// 提取实际数据
|
||||
if (data.containsKey('data')) {
|
||||
response.data = data['data'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误拦截器
|
||||
```dart
|
||||
// 错误拦截器
|
||||
class ErrorInterceptor extends Interceptor {
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
// 网络错误处理
|
||||
if (_isNetworkError(err)) {
|
||||
_handleNetworkError(err);
|
||||
}
|
||||
|
||||
// 认证错误处理
|
||||
if (_isAuthenticationError(err)) {
|
||||
_handleAuthenticationError(err);
|
||||
}
|
||||
|
||||
// 服务器错误处理
|
||||
if (_isServerError(err)) {
|
||||
_handleServerError(err);
|
||||
}
|
||||
|
||||
// 请求重试机制
|
||||
if (_shouldRetry(err)) {
|
||||
_retryRequest(err, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
bool _shouldRetry(DioException err) {
|
||||
// 网络超时重试
|
||||
if (err.type == DioExceptionType.connectionTimeout ||
|
||||
err.type == DioExceptionType.receiveTimeout) {
|
||||
return _getRetryCount(err) < 3;
|
||||
}
|
||||
|
||||
// 5xx 服务器错误重试
|
||||
if (err.response?.statusCode != null &&
|
||||
err.response!.statusCode! >= 500) {
|
||||
return _getRetryCount(err) < 2;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _retryRequest(
|
||||
DioException err,
|
||||
ErrorInterceptorHandler handler,
|
||||
) async {
|
||||
final retryCount = _getRetryCount(err) + 1;
|
||||
final delay = Duration(seconds: retryCount * 2);
|
||||
|
||||
await Future.delayed(delay);
|
||||
|
||||
try {
|
||||
final options = err.requestOptions;
|
||||
options.extra['retry_count'] = retryCount;
|
||||
|
||||
final response = await Dio().request(
|
||||
options.path,
|
||||
data: options.data,
|
||||
queryParameters: options.queryParameters,
|
||||
options: Options(
|
||||
method: options.method,
|
||||
headers: options.headers,
|
||||
extra: options.extra,
|
||||
),
|
||||
);
|
||||
|
||||
handler.resolve(response);
|
||||
} catch (e) {
|
||||
handler.next(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据传输对象 (DTOs)
|
||||
|
||||
#### 通用响应模型
|
||||
```dart
|
||||
// 通用 API 响应模型
|
||||
@freezed
|
||||
class ApiResponse<T> with _$ApiResponse<T> {
|
||||
const factory ApiResponse({
|
||||
required int code,
|
||||
required String message,
|
||||
T? data,
|
||||
@JsonKey(name: 'request_id') String? requestId,
|
||||
@JsonKey(name: 'timestamp') int? timestamp,
|
||||
}) = _ApiResponse<T>;
|
||||
|
||||
factory ApiResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
T Function(Object?) fromJsonT,
|
||||
) => _$ApiResponseFromJson(json, fromJsonT);
|
||||
}
|
||||
|
||||
// 分页响应模型
|
||||
@freezed
|
||||
class PagedResponse<T> with _$PagedResponse<T> {
|
||||
const factory PagedResponse({
|
||||
required List<T> items,
|
||||
required int total,
|
||||
required int page,
|
||||
required int pageSize,
|
||||
@JsonKey(name: 'has_more') required bool hasMore,
|
||||
}) = _PagedResponse<T>;
|
||||
|
||||
factory PagedResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
T Function(Object?) fromJsonT,
|
||||
) => _$PagedResponseFromJson(json, fromJsonT);
|
||||
}
|
||||
```
|
||||
|
||||
#### 请求参数模型
|
||||
```dart
|
||||
// 分页请求参数
|
||||
@freezed
|
||||
class PageRequest with _$PageRequest {
|
||||
const factory PageRequest({
|
||||
@Default(1) int page,
|
||||
@Default(20) int pageSize,
|
||||
String? sortBy,
|
||||
@Default('desc') String sortOrder,
|
||||
}) = _PageRequest;
|
||||
|
||||
factory PageRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$PageRequestFromJson(json);
|
||||
}
|
||||
|
||||
// 搜索请求参数
|
||||
@freezed
|
||||
class SearchRequest with _$SearchRequest {
|
||||
const factory SearchRequest({
|
||||
required String keyword,
|
||||
List<String>? filters,
|
||||
@Default(1) int page,
|
||||
@Default(20) int pageSize,
|
||||
}) = _SearchRequest;
|
||||
|
||||
factory SearchRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$SearchRequestFromJson(json);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 错误处理系统
|
||||
|
||||
基于真实项目的多层次错误处理架构,包含全局错误、业务错误和自定义错误。
|
||||
|
||||
#### 错误基类定义 (`model/error.dart`)
|
||||
```dart
|
||||
// 实际的错误基类
|
||||
abstract class ErrorBase implements Exception {
|
||||
/// 调用栈
|
||||
StackTrace? stackTrace;
|
||||
|
||||
/// 错误消息
|
||||
String get errorMessage;
|
||||
}
|
||||
|
||||
/// 全局的错误基类
|
||||
abstract class ErrorGlobal extends ErrorBase {}
|
||||
|
||||
/// 通用全局错误
|
||||
class ErrorGlobalCommon extends ErrorGlobal {
|
||||
ErrorGlobalCommon(
|
||||
this.errorType,
|
||||
this._originalCause, {
|
||||
this.httpCode,
|
||||
this.response,
|
||||
this.message,
|
||||
}) {
|
||||
stackTrace = StackTrace.current;
|
||||
}
|
||||
|
||||
/// 封装的原始error
|
||||
final dynamic _originalCause;
|
||||
|
||||
/// 错误类型
|
||||
final GlobalCommonErrorType errorType;
|
||||
|
||||
/// http返回的值
|
||||
final int? httpCode;
|
||||
|
||||
/// 返回的response
|
||||
final HttpResponse? response;
|
||||
|
||||
/// 返回的消息
|
||||
late String? message;
|
||||
|
||||
/// 返回原始的错误原因,如DioError
|
||||
dynamic get originalCause => _originalCause;
|
||||
|
||||
@override
|
||||
String get errorMessage => message ?? response?.statusMessage ?? '';
|
||||
|
||||
@override
|
||||
String toString() => 'ErrorGlobalCommon{_originalCause: $_originalCause, '
|
||||
'errorType: $errorType, httpCode: $httpCode, response: $response}';
|
||||
}
|
||||
|
||||
/// 业务错误通用基类
|
||||
abstract class ErrorGlobalBusiness extends ErrorGlobal {
|
||||
/// 业务错误码
|
||||
String get businessCode;
|
||||
|
||||
@override
|
||||
String get errorMessage;
|
||||
|
||||
/// 错误配置
|
||||
Map<String, dynamic> get errorConfig;
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'ErrorGlobalBusiness{businessCode:$businessCode, message:$errorMessage}';
|
||||
}
|
||||
|
||||
/// 自定义错误
|
||||
class ErrorCustom extends ErrorBase {
|
||||
// 自定义错误实现
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 全局错误处理器
|
||||
```dart
|
||||
// 错误工厂方法类型定义
|
||||
typedef GlobalErrorBusinessFactory = ErrorGlobalBusiness? Function(
|
||||
BaseDtoModel model,
|
||||
);
|
||||
|
||||
typedef CustomErrorFactory = ErrorCustom? Function(BaseDtoModel model);
|
||||
|
||||
// 全局错误处理流程
|
||||
// 在网络引擎中自动调用错误处理器链
|
||||
// 支持自定义全局错误处理器扩展
|
||||
```
|
||||
|
||||
#### 错误类型枚举
|
||||
```dart
|
||||
// 全局通用错误类型
|
||||
enum GlobalCommonErrorType {
|
||||
timeout,
|
||||
noNetwork,
|
||||
serverError,
|
||||
parseError,
|
||||
other,
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 缓存机制
|
||||
|
||||
#### 请求缓存管理
|
||||
```dart
|
||||
// 网络缓存管理器
|
||||
class NetworkCacheManager {
|
||||
static final Map<String, CacheItem> _cache = {};
|
||||
static const Duration defaultCacheDuration = Duration(minutes: 5);
|
||||
|
||||
// 设置缓存
|
||||
static void setCache(
|
||||
String key,
|
||||
dynamic data, {
|
||||
Duration? duration,
|
||||
}) {
|
||||
_cache[key] = CacheItem(
|
||||
data: data,
|
||||
timestamp: DateTime.now(),
|
||||
duration: duration ?? defaultCacheDuration,
|
||||
);
|
||||
}
|
||||
|
||||
// 获取缓存
|
||||
static T? getCache<T>(String key) {
|
||||
final item = _cache[key];
|
||||
if (item == null) return null;
|
||||
|
||||
if (item.isExpired) {
|
||||
_cache.remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return item.data as T?;
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
static void clearCache([String? key]) {
|
||||
if (key != null) {
|
||||
_cache.remove(key);
|
||||
} else {
|
||||
_cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 生成缓存键
|
||||
static String generateCacheKey(
|
||||
String path,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
) {
|
||||
final uri = Uri(path: path, queryParameters: queryParameters);
|
||||
return uri.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存项
|
||||
class CacheItem {
|
||||
final dynamic data;
|
||||
final DateTime timestamp;
|
||||
final Duration duration;
|
||||
|
||||
CacheItem({
|
||||
required this.data,
|
||||
required this.timestamp,
|
||||
required this.duration,
|
||||
});
|
||||
|
||||
bool get isExpired => DateTime.now().difference(timestamp) > duration;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 请求配置管理
|
||||
|
||||
#### 环境配置
|
||||
```dart
|
||||
// 网络环境配置
|
||||
enum NetworkEnvironment {
|
||||
development,
|
||||
testing,
|
||||
staging,
|
||||
production,
|
||||
}
|
||||
|
||||
class NetworkConfig {
|
||||
final String baseUrl;
|
||||
final Duration connectTimeout;
|
||||
final Duration receiveTimeout;
|
||||
final Duration sendTimeout;
|
||||
final bool enableLogging;
|
||||
final bool enableCache;
|
||||
final int maxRetries;
|
||||
|
||||
const NetworkConfig({
|
||||
required this.baseUrl,
|
||||
this.connectTimeout = const Duration(seconds: 30),
|
||||
this.receiveTimeout = const Duration(seconds: 30),
|
||||
this.sendTimeout = const Duration(seconds: 30),
|
||||
this.enableLogging = false,
|
||||
this.enableCache = true,
|
||||
this.maxRetries = 3,
|
||||
});
|
||||
|
||||
// 开发环境配置
|
||||
static const NetworkConfig development = NetworkConfig(
|
||||
baseUrl: 'https://dev-api.oneapp.com',
|
||||
enableLogging: true,
|
||||
enableCache: false,
|
||||
);
|
||||
|
||||
// 测试环境配置
|
||||
static const NetworkConfig testing = NetworkConfig(
|
||||
baseUrl: 'https://test-api.oneapp.com',
|
||||
enableLogging: true,
|
||||
);
|
||||
|
||||
// 生产环境配置
|
||||
static const NetworkConfig production = NetworkConfig(
|
||||
baseUrl: 'https://api.oneapp.com',
|
||||
enableLogging: false,
|
||||
);
|
||||
|
||||
static NetworkConfig forEnvironment(NetworkEnvironment env) {
|
||||
switch (env) {
|
||||
case NetworkEnvironment.development:
|
||||
return development;
|
||||
case NetworkEnvironment.testing:
|
||||
return testing;
|
||||
case NetworkEnvironment.staging:
|
||||
return testing; // 使用测试环境配置
|
||||
case NetworkEnvironment.production:
|
||||
return production;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
基于实际项目的网络请求使用方法。
|
||||
|
||||
### 基本初始化和使用
|
||||
```dart
|
||||
// 初始化网络库
|
||||
final option = DefaultNetworkEngineOption()
|
||||
..baseUrl = 'https://api.oneapp.com'
|
||||
..debuggable = true
|
||||
..environment = 'production';
|
||||
|
||||
// 初始化默认网络引擎
|
||||
initNetwork(option: option);
|
||||
|
||||
// 直接使用全局方法发起请求
|
||||
try {
|
||||
final response = await request<UserModel>(
|
||||
RequestOptions(
|
||||
path: '/users/profile',
|
||||
method: 'GET',
|
||||
),
|
||||
);
|
||||
print('用户信息: ${response.name}');
|
||||
} on ErrorBase catch (e) {
|
||||
print('请求失败: ${e.errorMessage}');
|
||||
}
|
||||
```
|
||||
|
||||
### 使用RequestApi
|
||||
```dart
|
||||
// 创建自定义网络引擎
|
||||
final customOption = DefaultNetworkEngineOption()
|
||||
..baseUrl = 'https://custom-api.com'
|
||||
..retryTime = 5
|
||||
..connectTimeout = 15000;
|
||||
|
||||
final api = customNetworkEngine(customOption);
|
||||
|
||||
// 使用回调方式处理请求
|
||||
await api.requestWithCallback<List<ArticleModel>>(
|
||||
RequestOptions(
|
||||
path: '/articles',
|
||||
method: 'GET',
|
||||
queryParameters: {'page': 1, 'limit': 20},
|
||||
),
|
||||
(articles) {
|
||||
print('获取到 ${articles.length} 篇文章');
|
||||
},
|
||||
(error) {
|
||||
print('请求失败: ${error.errorMessage}');
|
||||
|
||||
// 处理特定类型的错误
|
||||
if (error is ErrorGlobalBusiness) {
|
||||
print('业务错误码: ${error.businessCode}');
|
||||
}
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### 文件上传和下载
|
||||
```dart
|
||||
// 文件上传
|
||||
final uploadResponse = await api.upload(
|
||||
options: RequestOptions(
|
||||
path: '/upload',
|
||||
method: 'POST',
|
||||
data: FormData.fromMap({
|
||||
'file': await MultipartFile.fromFile('/path/to/file.jpg'),
|
||||
'description': '头像上传',
|
||||
}),
|
||||
),
|
||||
onSendProgress: (sent, total) {
|
||||
final progress = (sent / total * 100).toStringAsFixed(1);
|
||||
print('上传进度: $progress%');
|
||||
},
|
||||
);
|
||||
|
||||
// 文件下载
|
||||
await download(
|
||||
savePath: '/path/to/save/file.pdf',
|
||||
options: RequestOptions(
|
||||
path: '/download/document.pdf',
|
||||
method: 'GET',
|
||||
),
|
||||
onReceiveProgress: (received, total) {
|
||||
final progress = (received / total * 100).toStringAsFixed(1);
|
||||
print('下载进度: $progress%');
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### 自定义配置示例
|
||||
```dart
|
||||
// 创建带有自定义拦截器的配置
|
||||
class MyNetworkOption extends DefaultNetworkEngineOption {
|
||||
@override
|
||||
String get baseUrl => 'https://my-api.com';
|
||||
|
||||
@override
|
||||
List<Interceptor> get interceptors => [
|
||||
MyAuthInterceptor(),
|
||||
MyLoggingInterceptor(),
|
||||
];
|
||||
|
||||
@override
|
||||
List<GlobalErrorHandler> get globalErrorHandlers => [
|
||||
MyCustomErrorHandler(),
|
||||
];
|
||||
|
||||
@override
|
||||
bool get debuggable => kDebugMode;
|
||||
|
||||
@override
|
||||
int get retryTime => 3;
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖管理
|
||||
|
||||
基于实际的pubspec.yaml配置,展示真实的项目依赖关系。
|
||||
|
||||
### 核心依赖
|
||||
```yaml
|
||||
# 实际项目依赖配置
|
||||
dependencies:
|
||||
http_parser: ^4.0.2 # HTTP 解析工具
|
||||
dio: ^5.7.0 # HTTP 客户端库(核心)
|
||||
pretty_dio_logger: ^1.3.1 # 美化的请求日志
|
||||
freezed_annotation: ^2.2.0 # 不可变类注解
|
||||
json_annotation: ^4.8.1 # JSON 序列化注解
|
||||
|
||||
dev_dependencies:
|
||||
json_serializable: ^6.7.0 # JSON 序列化生成器
|
||||
build_runner: ^2.4.5 # 代码生成引擎
|
||||
test: ^1.24.3 # 单元测试框架
|
||||
coverage: ^1.6.3 # 测试覆盖率工具
|
||||
freezed: ^2.3.5 # 不可变类生成器
|
||||
flutter_lints: ^5.0.0 # 代码规范检查
|
||||
```
|
||||
|
||||
### 实际的版本信息
|
||||
- **模块名称**: basic_network
|
||||
- **版本**: 0.2.3+4
|
||||
- **仓库**: https://gitlab-rd0.maezia.com/eziahz/oneapp/ezia-oneapp-basic-network
|
||||
- **Dart 版本**: >=2.17.0 <4.0.0
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 网络性能优化
|
||||
- 连接池复用
|
||||
- HTTP/2 支持
|
||||
- 请求合并和批处理
|
||||
- 智能重试机制
|
||||
|
||||
### 缓存优化
|
||||
- 多级缓存策略
|
||||
- 缓存过期管理
|
||||
- 条件请求支持
|
||||
- 离线缓存机制
|
||||
|
||||
### 内存优化
|
||||
- 流式数据处理
|
||||
- 大文件分块传输
|
||||
- 及时释放响应数据
|
||||
- 内存泄漏检测
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试
|
||||
- 网络服务接口测试
|
||||
- 拦截器功能测试
|
||||
- 错误处理测试
|
||||
- 缓存机制测试
|
||||
|
||||
### 集成测试
|
||||
- 端到端请求测试
|
||||
- 网络环境切换测试
|
||||
- 错误恢复测试
|
||||
|
||||
### Mock 测试
|
||||
- 网络请求 Mock
|
||||
- 错误场景模拟
|
||||
- 性能测试
|
||||
|
||||
## 总结
|
||||
|
||||
`basic_network` 模块为 OneApp 提供了强大而灵活的网络通信能力。通过统一的接口抽象、完善的错误处理、智能的缓存机制和丰富的拦截器功能,大大简化了网络请求的开发工作,提高了应用的稳定性和用户体验。模块的设计充分考虑了可扩展性和可维护性,为大型应用的网络通信提供了坚实的基础。
|
||||
494
basic_utils/basic_platform.md
Normal file
494
basic_utils/basic_platform.md
Normal file
@@ -0,0 +1,494 @@
|
||||
# Basic Platform 平台适配模块
|
||||
|
||||
## 模块概述
|
||||
|
||||
`basic_platform` 是 OneApp 基础工具模块群中的平台适配模块,负责统一处理 Android 和 iOS 平台的差异性,提供跨平台的统一接口。该模块封装了设备信息获取、权限管理、平台特性检测等功能。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: basic_platform
|
||||
- **版本**: 0.2.14+1
|
||||
- **描述**: 跨平台适配基础模块
|
||||
- **Flutter 版本**: >=2.10.5
|
||||
- **Dart 版本**: >=3.0.0 <4.0.0
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
1. **平台信息检测**
|
||||
- 操作系统类型和版本
|
||||
- 设备型号和硬件信息
|
||||
- 应用版本和构建信息
|
||||
- 网络环境检测
|
||||
|
||||
2. **权限管理统一**
|
||||
- 跨平台权限申请
|
||||
- 权限状态检查
|
||||
- 权限结果处理
|
||||
- 权限设置引导
|
||||
|
||||
3. **设备特性适配**
|
||||
- 屏幕方向控制
|
||||
- 设备唤醒管理
|
||||
- 平台特定功能检测
|
||||
- 硬件能力查询
|
||||
|
||||
4. **存储路径管理**
|
||||
- 应用目录获取
|
||||
- 缓存目录管理
|
||||
- 外部存储访问
|
||||
- 临时文件处理
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
lib/
|
||||
├── basic_platform.dart # 模块入口文件
|
||||
├── src/ # 源代码目录
|
||||
│ ├── platform_info/ # 平台信息
|
||||
│ ├── permissions/ # 权限管理
|
||||
│ ├── device/ # 设备功能
|
||||
│ ├── storage/ # 存储管理
|
||||
│ ├── models/ # 数据模型
|
||||
│ └── utils/ # 工具类
|
||||
├── android/ # Android特定代码
|
||||
├── ios/ # iOS特定代码
|
||||
└── test/ # 测试文件
|
||||
```
|
||||
|
||||
### 依赖关系
|
||||
|
||||
#### 核心依赖
|
||||
- `basic_storage: ^0.2.2` - 基础存储框架
|
||||
- `permission_handler: ^10.4.5` - 权限处理插件
|
||||
- `native_device_orientation: ^1.2.1` - 设备方向控制
|
||||
|
||||
## 核心模块分析
|
||||
|
||||
### 1. 模块入口 (`basic_platform.dart`)
|
||||
|
||||
**功能职责**:
|
||||
- 平台适配服务统一导出
|
||||
- 初始化配置管理
|
||||
- 单例模式实现
|
||||
|
||||
### 2. 平台信息 (`src/platform_info/`)
|
||||
|
||||
**功能职责**:
|
||||
- 操作系统信息获取
|
||||
- 设备硬件信息查询
|
||||
- 应用程序信息获取
|
||||
- 环境配置检测
|
||||
|
||||
**主要组件**:
|
||||
- `PlatformInfo` - 平台信息管理器
|
||||
- `DeviceInfo` - 设备信息获取器
|
||||
- `AppInfo` - 应用信息管理器
|
||||
- `SystemInfo` - 系统信息收集器
|
||||
|
||||
### 3. 权限管理 (`src/permissions/`)
|
||||
|
||||
**功能职责**:
|
||||
- 统一权限申请接口
|
||||
- 权限状态查询
|
||||
- 权限结果回调处理
|
||||
- 权限设置页面跳转
|
||||
|
||||
**主要组件**:
|
||||
- `PermissionManager` - 权限管理器
|
||||
- `PermissionChecker` - 权限检查器
|
||||
- `PermissionRequestor` - 权限申请器
|
||||
- `PermissionGuide` - 权限引导器
|
||||
|
||||
### 4. 设备功能 (`src/device/`)
|
||||
|
||||
**功能职责**:
|
||||
- 设备方向控制
|
||||
- 屏幕亮度管理
|
||||
- 震动反馈控制
|
||||
- 设备唤醒管理
|
||||
|
||||
**主要组件**:
|
||||
- `OrientationController` - 方向控制器
|
||||
- `BrightnessController` - 亮度控制器
|
||||
- `HapticController` - 震动控制器
|
||||
- `WakeController` - 唤醒控制器
|
||||
|
||||
### 5. 存储管理 (`src/storage/`)
|
||||
|
||||
**功能职责**:
|
||||
- 应用目录路径获取
|
||||
- 外部存储访问
|
||||
- 临时文件管理
|
||||
- 缓存目录处理
|
||||
|
||||
**主要组件**:
|
||||
- `StorageManager` - 存储管理器
|
||||
- `PathProvider` - 路径提供器
|
||||
- `DirectoryManager` - 目录管理器
|
||||
- `TempFileManager` - 临时文件管理器
|
||||
|
||||
### 6. 数据模型 (`src/models/`)
|
||||
|
||||
**功能职责**:
|
||||
- 平台信息数据模型
|
||||
- 权限状态模型
|
||||
- 设备特性模型
|
||||
- 配置参数模型
|
||||
|
||||
**主要模型**:
|
||||
- `PlatformData` - 平台数据模型
|
||||
- `PermissionStatus` - 权限状态模型
|
||||
- `DeviceCapability` - 设备能力模型
|
||||
- `PlatformConfig` - 平台配置模型
|
||||
|
||||
### 7. 工具类 (`src/utils/`)
|
||||
|
||||
**功能职责**:
|
||||
- 平台判断工具
|
||||
- 版本比较工具
|
||||
- 路径处理工具
|
||||
- 异常处理工具
|
||||
|
||||
**主要工具**:
|
||||
- `PlatformUtils` - 平台工具类
|
||||
- `VersionUtils` - 版本工具类
|
||||
- `PathUtils` - 路径工具类
|
||||
- `ExceptionUtils` - 异常工具类
|
||||
|
||||
## 平台适配实现
|
||||
|
||||
### Android 平台适配
|
||||
|
||||
#### 权限处理
|
||||
```dart
|
||||
class AndroidPermissionHandler {
|
||||
static Future<bool> requestPermission(Permission permission) async {
|
||||
final status = await permission.request();
|
||||
|
||||
switch (status) {
|
||||
case PermissionStatus.granted:
|
||||
return true;
|
||||
case PermissionStatus.denied:
|
||||
return false;
|
||||
case PermissionStatus.permanentlyDenied:
|
||||
await openAppSettings();
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<Permission, PermissionStatus>> requestMultiple(
|
||||
List<Permission> permissions,
|
||||
) async {
|
||||
return await permissions.request();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 设备信息获取
|
||||
```dart
|
||||
class AndroidDeviceInfo {
|
||||
static Future<AndroidDeviceData> getDeviceInfo() async {
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
final androidInfo = await deviceInfo.androidInfo;
|
||||
|
||||
return AndroidDeviceData(
|
||||
manufacturer: androidInfo.manufacturer,
|
||||
model: androidInfo.model,
|
||||
version: androidInfo.version.release,
|
||||
sdkInt: androidInfo.version.sdkInt,
|
||||
brand: androidInfo.brand,
|
||||
product: androidInfo.product,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### iOS 平台适配
|
||||
|
||||
#### 权限处理
|
||||
```dart
|
||||
class IOSPermissionHandler {
|
||||
static Future<bool> requestPermission(Permission permission) async {
|
||||
final status = await permission.request();
|
||||
|
||||
if (status == PermissionStatus.denied ||
|
||||
status == PermissionStatus.permanentlyDenied) {
|
||||
return await _showPermissionDialog(permission);
|
||||
}
|
||||
|
||||
return status == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
static Future<bool> _showPermissionDialog(Permission permission) async {
|
||||
// 显示权限说明对话框
|
||||
// 引导用户到设置页面
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 设备信息获取
|
||||
```dart
|
||||
class IOSDeviceInfo {
|
||||
static Future<IOSDeviceData> getDeviceInfo() async {
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
final iosInfo = await deviceInfo.iosInfo;
|
||||
|
||||
return IOSDeviceData(
|
||||
name: iosInfo.name,
|
||||
model: iosInfo.model,
|
||||
systemName: iosInfo.systemName,
|
||||
systemVersion: iosInfo.systemVersion,
|
||||
identifierForVendor: iosInfo.identifierForVendor,
|
||||
isPhysicalDevice: iosInfo.isPhysicalDevice,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 业务流程
|
||||
|
||||
### 权限申请流程
|
||||
```mermaid
|
||||
graph TD
|
||||
A[应用请求权限] --> B[检查权限状态]
|
||||
B --> C{权限是否已授予}
|
||||
C -->|是| D[直接使用功能]
|
||||
C -->|否| E[显示权限说明]
|
||||
E --> F[请求系统权限]
|
||||
F --> G{用户是否同意}
|
||||
G -->|是| H[权限授予成功]
|
||||
G -->|否| I{是否永久拒绝}
|
||||
I -->|是| J[引导用户到设置]
|
||||
I -->|否| K[记录拒绝状态]
|
||||
H --> D
|
||||
J --> L[用户手动设置]
|
||||
K --> M[功能降级处理]
|
||||
L --> N[重新检查权限]
|
||||
N --> C
|
||||
```
|
||||
|
||||
### 平台初始化流程
|
||||
```mermaid
|
||||
graph TD
|
||||
A[应用启动] --> B[初始化平台模块]
|
||||
B --> C[检测平台类型]
|
||||
C --> D[加载平台配置]
|
||||
D --> E[初始化平台服务]
|
||||
E --> F[注册权限监听器]
|
||||
F --> G[设置默认配置]
|
||||
G --> H[平台初始化完成]
|
||||
H --> I[通知上层应用]
|
||||
```
|
||||
|
||||
## 权限管理系统
|
||||
|
||||
### 支持的权限类型
|
||||
1. **基础权限**
|
||||
- 网络访问权限
|
||||
- 存储读写权限
|
||||
- 设备信息访问
|
||||
- 应用安装权限
|
||||
|
||||
2. **隐私权限**
|
||||
- 位置信息权限
|
||||
- 相机访问权限
|
||||
- 麦克风权限
|
||||
- 通讯录权限
|
||||
|
||||
3. **系统权限**
|
||||
- 电话状态权限
|
||||
- 短信发送权限
|
||||
- 系统设置修改
|
||||
- 设备管理权限
|
||||
|
||||
### 权限管理策略
|
||||
- **最小权限原则**: 仅申请必要权限
|
||||
- **延迟申请**: 在需要时才申请权限
|
||||
- **用户友好**: 提供清晰的权限说明
|
||||
- **优雅降级**: 权限拒绝时的替代方案
|
||||
|
||||
## 设备适配策略
|
||||
|
||||
### 屏幕适配
|
||||
```dart
|
||||
class ScreenAdapter {
|
||||
static double getScaleFactor() {
|
||||
final window = WidgetsBinding.instance.window;
|
||||
return window.devicePixelRatio;
|
||||
}
|
||||
|
||||
static Size getScreenSize() {
|
||||
final window = WidgetsBinding.instance.window;
|
||||
return window.physicalSize / window.devicePixelRatio;
|
||||
}
|
||||
|
||||
static bool isTablet() {
|
||||
final size = getScreenSize();
|
||||
final diagonal = sqrt(pow(size.width, 2) + pow(size.height, 2));
|
||||
return diagonal > 1100; // 7 inch threshold
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 性能优化
|
||||
```dart
|
||||
class PerformanceOptimizer {
|
||||
static bool isLowEndDevice() {
|
||||
if (Platform.isAndroid) {
|
||||
return _checkAndroidPerformance();
|
||||
} else if (Platform.isIOS) {
|
||||
return _checkIOSPerformance();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _checkAndroidPerformance() {
|
||||
// 基于 Android 设备性能指标判断
|
||||
// RAM、CPU、GPU 等参数
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _checkIOSPerformance() {
|
||||
// 基于 iOS 设备型号判断
|
||||
// 设备代数、处理器类型等
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 存储路径管理
|
||||
|
||||
### 路径类型
|
||||
1. **应用目录**
|
||||
- 应用私有目录
|
||||
- 文档目录
|
||||
- 库目录
|
||||
- 支持文件目录
|
||||
|
||||
2. **缓存目录**
|
||||
- 临时缓存
|
||||
- 图片缓存
|
||||
- 数据缓存
|
||||
- 网络缓存
|
||||
|
||||
3. **外部存储**
|
||||
- 公共文档目录
|
||||
- 下载目录
|
||||
- 图片目录
|
||||
- 媒体目录
|
||||
|
||||
### 路径获取接口
|
||||
```dart
|
||||
class PlatformPaths {
|
||||
static Future<String> getApplicationDocumentsDirectory() async {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
return directory.path;
|
||||
}
|
||||
|
||||
static Future<String> getTemporaryDirectory() async {
|
||||
final directory = await getTemporaryDirectory();
|
||||
return directory.path;
|
||||
}
|
||||
|
||||
static Future<String?> getExternalStorageDirectory() async {
|
||||
if (Platform.isAndroid) {
|
||||
final directory = await getExternalStorageDirectory();
|
||||
return directory?.path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 异常处理机制
|
||||
|
||||
### 异常类型
|
||||
1. **权限异常**
|
||||
- 权限拒绝异常
|
||||
- 权限配置错误
|
||||
- 系统权限限制
|
||||
|
||||
2. **平台异常**
|
||||
- 不支持的平台功能
|
||||
- API版本不兼容
|
||||
- 硬件功能缺失
|
||||
|
||||
3. **系统异常**
|
||||
- 文件系统错误
|
||||
- 网络连接异常
|
||||
- 内存不足异常
|
||||
|
||||
### 异常处理策略
|
||||
- **异常捕获**: 全面的异常捕获机制
|
||||
- **错误恢复**: 自动错误恢复策略
|
||||
- **用户提示**: 友好的错误提示信息
|
||||
- **日志记录**: 详细的错误日志记录
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试
|
||||
- **平台检测测试**: 平台类型识别
|
||||
- **权限管理测试**: 权限申请和状态
|
||||
- **路径获取测试**: 各种路径获取
|
||||
- **工具类测试**: 工具方法功能
|
||||
|
||||
### 集成测试
|
||||
- **跨平台兼容测试**: 不同平台功能
|
||||
- **权限流程测试**: 完整权限流程
|
||||
- **设备适配测试**: 不同设备适配
|
||||
- **性能测试**: 模块性能表现
|
||||
|
||||
### 兼容性测试
|
||||
- **系统版本兼容**: 不同系统版本
|
||||
- **设备型号兼容**: 不同设备型号
|
||||
- **权限策略兼容**: 不同权限策略
|
||||
- **API兼容性**: 不同API版本
|
||||
|
||||
## 配置管理
|
||||
|
||||
### 平台配置
|
||||
```dart
|
||||
class PlatformConfig {
|
||||
static const Map<String, dynamic> androidConfig = {
|
||||
'minSdkVersion': 21,
|
||||
'targetSdkVersion': 33,
|
||||
'requiredPermissions': [
|
||||
'android.permission.INTERNET',
|
||||
'android.permission.ACCESS_NETWORK_STATE',
|
||||
],
|
||||
};
|
||||
|
||||
static const Map<String, dynamic> iosConfig = {
|
||||
'minIOSVersion': '11.0',
|
||||
'requiredCapabilities': [
|
||||
'arm64',
|
||||
],
|
||||
'privacyDescriptions': {
|
||||
'NSLocationWhenInUseUsageDescription': 'App needs location access',
|
||||
'NSCameraUsageDescription': 'App needs camera access',
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 开发建议
|
||||
1. **统一接口**: 使用统一的平台接口
|
||||
2. **异步处理**: 耗时操作异步执行
|
||||
3. **异常处理**: 完善的异常处理机制
|
||||
4. **性能考虑**: 避免频繁的平台调用
|
||||
|
||||
### 使用指南
|
||||
1. **权限申请**: 在合适的时机申请权限
|
||||
2. **平台检测**: 根据平台提供不同功能
|
||||
3. **路径使用**: 正确使用各种存储路径
|
||||
4. **错误处理**: 妥善处理平台相关错误
|
||||
|
||||
## 总结
|
||||
|
||||
`basic_platform` 模块作为 OneApp 的平台适配基础模块,为应用提供了统一的跨平台接口和能力。通过封装平台差异性、统一权限管理和完善的异常处理,显著简化了跨平台开发的复杂性。模块具有良好的扩展性和兼容性,能够适应不断变化的平台环境和需求。
|
||||
735
basic_utils/basic_push.md
Normal file
735
basic_utils/basic_push.md
Normal file
@@ -0,0 +1,735 @@
|
||||
# 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)
|
||||
|
||||
定义推送服务初始化所需的参数:
|
||||
|
||||
```dart
|
||||
/// 初始化需要的参数
|
||||
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实现的事件发布订阅系统:
|
||||
|
||||
```dart
|
||||
/// 事件总线接口
|
||||
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定义的不可变数据类:
|
||||
|
||||
```dart
|
||||
/// 事件对象
|
||||
@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)
|
||||
|
||||
推送模块的核心接口定义:
|
||||
|
||||
```dart
|
||||
/// 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)
|
||||
|
||||
```dart
|
||||
/// 通知点击主题
|
||||
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. 推送服务初始化
|
||||
|
||||
```dart
|
||||
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. 用户绑定和解绑
|
||||
|
||||
```dart
|
||||
// 用户登录后绑定推送
|
||||
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. 权限管理
|
||||
|
||||
```dart
|
||||
// 检查通知权限
|
||||
if (pushFacade.isNotificationEnabled) {
|
||||
print('通知权限已授予');
|
||||
} else {
|
||||
print('通知权限未授予,请手动开启');
|
||||
|
||||
// 跳转到系统设置页面
|
||||
pushFacade.openSettingsForNotification();
|
||||
}
|
||||
|
||||
// 获取推送注册ID
|
||||
final regId = await pushFacade.pushRegId;
|
||||
print('推送注册ID: $regId');
|
||||
```
|
||||
|
||||
### 4. 事件发布和订阅
|
||||
|
||||
```dart
|
||||
// 发布事件
|
||||
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. 本地通知
|
||||
|
||||
```dart
|
||||
// 发送本地通知
|
||||
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. 应用生命周期管理
|
||||
|
||||
```dart
|
||||
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. 推送设置管理
|
||||
|
||||
```dart
|
||||
// 获取推送设置
|
||||
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 关键依赖
|
||||
|
||||
```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. 自定义登录依赖
|
||||
|
||||
```dart
|
||||
class MyLoginDeps with ILoginDeps {
|
||||
@override
|
||||
bool get isLogin {
|
||||
// 实现自定义的登录状态检查逻辑
|
||||
return UserManager.instance.isLoggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用自定义登录依赖
|
||||
pushFacade.initPush(
|
||||
loginDeps: MyLoginDeps(),
|
||||
pushInitParams: pushInitParams,
|
||||
);
|
||||
```
|
||||
|
||||
### 2. 复杂主题组合
|
||||
|
||||
```dart
|
||||
// 定义自定义主题
|
||||
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. 推送消息解析
|
||||
|
||||
```dart
|
||||
// 自定义推送消息解析器
|
||||
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. 推送权限处理
|
||||
```dart
|
||||
// 推荐:友好的权限请求
|
||||
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. 错误处理
|
||||
```dart
|
||||
// 推荐:完整的错误处理
|
||||
Future<void> handlePushBinding() async {
|
||||
try {
|
||||
final success = await pushFacade.bindPush();
|
||||
if (!success) {
|
||||
// 绑定失败的用户友好提示
|
||||
showSnackBar('推送服务暂时不可用,请稍后重试');
|
||||
}
|
||||
} catch (e) {
|
||||
// 记录错误日志
|
||||
Logger.e('推送绑定异常: $e', tagPush);
|
||||
// 用户友好的错误提示
|
||||
showSnackBar('网络连接异常,请检查网络后重试');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 事件处理优化
|
||||
```dart
|
||||
// 推荐:使用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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 问题排查
|
||||
|
||||
### 常见问题
|
||||
1. **推送收不到**: 检查应用权限、网络连接和推送配置
|
||||
2. **事件订阅失效**: 确认订阅没有被意外取消,检查主题匹配逻辑
|
||||
3. **心跳断开**: 检查网络稳定性和服务器配置
|
||||
|
||||
### 调试技巧
|
||||
- 启用debug模式查看详细日志
|
||||
- 使用推送测试工具验证配置
|
||||
- 监控应用生命周期事件
|
||||
|
||||
560
basic_utils/basic_utils.md
Normal file
560
basic_utils/basic_utils.md
Normal file
@@ -0,0 +1,560 @@
|
||||
# Basic Utils 基础工具模块
|
||||
|
||||
## 模块概述
|
||||
|
||||
`basic_utils` 是 OneApp 基础工具模块群中的核心工具集合,提供了应用开发中常用的基础工具类、辅助方法和通用组件。该模块包含了事件总线、图片处理、分享功能、加密解密、文件上传等丰富的工具功能。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: basic_utils
|
||||
- **版本**: 0.0.3
|
||||
- **描述**: 基础工具包
|
||||
- **Flutter 版本**: >=2.10.5
|
||||
- **Dart 版本**: >=3.0.0 <4.0.08
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
1. **事件总线系统**
|
||||
- 全局事件发布订阅
|
||||
- 类型安全的事件传递
|
||||
- 事件生命周期管理
|
||||
- 异步事件处理
|
||||
|
||||
2. **图片处理工具**
|
||||
- 图片选择和拍照
|
||||
- 图片压缩和格式转换
|
||||
- 图片裁剪和编辑
|
||||
- 图片缓存管理
|
||||
|
||||
3. **分享功能集成**
|
||||
- 社交平台分享
|
||||
- 文件分享
|
||||
- 链接分享
|
||||
- 自定义分享内容
|
||||
|
||||
4. **加密解密服务**
|
||||
- 对称加密算法
|
||||
- 非对称加密算法
|
||||
- 哈希算法
|
||||
- 数字签名
|
||||
|
||||
5. **云存储服务**
|
||||
- 腾讯云COS集成
|
||||
- 文件上传下载
|
||||
- 断点续传
|
||||
- 存储管理
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
lib/
|
||||
├── basic_utils.dart # 模块入口文件
|
||||
├── src/ # 源代码目录
|
||||
│ ├── event_bus/ # 事件总线
|
||||
│ ├── image/ # 图片处理
|
||||
│ ├── share/ # 分享功能
|
||||
│ ├── crypto/ # 加密解密
|
||||
│ ├── storage/ # 云存储
|
||||
│ ├── network/ # 网络工具
|
||||
│ ├── utils/ # 通用工具
|
||||
│ └── models/ # 数据模型
|
||||
├── widgets/ # 通用组件
|
||||
└── test/ # 测试文件
|
||||
```
|
||||
|
||||
### 依赖关系
|
||||
|
||||
#### 核心框架依赖
|
||||
- `basic_modular: ^0.2.3` - 模块化框架
|
||||
- `basic_storage: 0.2.2` - 基础存储
|
||||
- `basic_webview: ^0.2.4` - WebView组件
|
||||
|
||||
#### 工具依赖
|
||||
- `event_bus: ^2.0.0` - 事件总线
|
||||
- `shared_preferences: ^2.0.17` - 本地存储
|
||||
- `http: ^1.0.0` - HTTP客户端
|
||||
- `dartz: ^0.10.1` - 函数式编程
|
||||
|
||||
#### 功能依赖
|
||||
- `image_picker: ^1.1.2` - 图片选择
|
||||
- `flutter_image_compress: 2.1.0` - 图片压缩
|
||||
- `share_plus: 7.2.1` - 分享功能
|
||||
- `fluwx: ^4.2.5` - 微信SDK
|
||||
- `weibo_kit: ^4.0.0` - 微博SDK
|
||||
|
||||
#### 安全依赖
|
||||
- `crypto: any` - 加密算法
|
||||
- `encrypt: any` - 加密工具
|
||||
- `pointycastle: any` - 加密库
|
||||
|
||||
#### 云服务依赖
|
||||
- `tencentcloud_cos_sdk_plugin: 1.2.0` - 腾讯云COS
|
||||
|
||||
## 核心模块分析
|
||||
|
||||
### 1. 模块入口 (`basic_utils.dart`)
|
||||
|
||||
**功能职责**:
|
||||
- 工具模块统一导出
|
||||
- 全局配置初始化
|
||||
- 服务注册管理
|
||||
|
||||
### 2. 事件总线 (`src/event_bus/`)
|
||||
|
||||
**功能职责**:
|
||||
- 全局事件发布订阅机制
|
||||
- 类型安全的事件传递
|
||||
- 事件过滤和转换
|
||||
- 内存泄漏防护
|
||||
|
||||
**主要组件**:
|
||||
- `GlobalEventBus` - 全局事件总线
|
||||
- `EventSubscription` - 事件订阅管理
|
||||
- `TypedEvent` - 类型化事件
|
||||
- `EventFilter` - 事件过滤器
|
||||
|
||||
**使用示例**:
|
||||
```dart
|
||||
// 定义事件类型
|
||||
class UserLoginEvent {
|
||||
final String userId;
|
||||
final String userName;
|
||||
|
||||
UserLoginEvent(this.userId, this.userName);
|
||||
}
|
||||
|
||||
// 发布事件
|
||||
GlobalEventBus.instance.fire(UserLoginEvent('123', 'John'));
|
||||
|
||||
// 订阅事件
|
||||
final subscription = GlobalEventBus.instance.on<UserLoginEvent>().listen((event) {
|
||||
print('User ${event.userName} logged in');
|
||||
});
|
||||
|
||||
// 取消订阅
|
||||
subscription.cancel();
|
||||
```
|
||||
|
||||
### 3. 图片处理 (`src/image/`)
|
||||
|
||||
**功能职责**:
|
||||
- 图片选择和拍照功能
|
||||
- 图片压缩和格式转换
|
||||
- 图片编辑和处理
|
||||
- 图片缓存和管理
|
||||
|
||||
**主要组件**:
|
||||
- `ImagePicker` - 图片选择器
|
||||
- `ImageCompressor` - 图片压缩器
|
||||
- `ImageEditor` - 图片编辑器
|
||||
- `ImageCache` - 图片缓存管理
|
||||
|
||||
**使用示例**:
|
||||
```dart
|
||||
class ImageUtils {
|
||||
static Future<File?> pickImage({
|
||||
ImageSource source = ImageSource.gallery,
|
||||
double? maxWidth,
|
||||
double? maxHeight,
|
||||
int? imageQuality,
|
||||
}) async {
|
||||
final picker = ImagePicker();
|
||||
final XFile? image = await picker.pickImage(
|
||||
source: source,
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
imageQuality: imageQuality,
|
||||
);
|
||||
|
||||
return image != null ? File(image.path) : null;
|
||||
}
|
||||
|
||||
static Future<File> compressImage(
|
||||
File file, {
|
||||
int quality = 85,
|
||||
int? minWidth,
|
||||
int? minHeight,
|
||||
}) async {
|
||||
final result = await FlutterImageCompress.compressAndGetFile(
|
||||
file.absolute.path,
|
||||
'${file.parent.path}/compressed_${file.name}',
|
||||
quality: quality,
|
||||
minWidth: minWidth,
|
||||
minHeight: minHeight,
|
||||
);
|
||||
|
||||
return File(result!.path);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 分享功能 (`src/share/`)
|
||||
|
||||
**功能职责**:
|
||||
- 系统原生分享
|
||||
- 社交平台分享
|
||||
- 自定义分享内容
|
||||
- 分享结果回调
|
||||
|
||||
**主要组件**:
|
||||
- `ShareManager` - 分享管理器
|
||||
- `WeChatShare` - 微信分享
|
||||
- `WeiboShare` - 微博分享
|
||||
- `SystemShare` - 系统分享
|
||||
|
||||
**使用示例**:
|
||||
```dart
|
||||
class ShareUtils {
|
||||
static Future<void> shareText(String text) async {
|
||||
await Share.share(text);
|
||||
}
|
||||
|
||||
static Future<void> shareFile(String filePath) async {
|
||||
await Share.shareXFiles([XFile(filePath)]);
|
||||
}
|
||||
|
||||
static Future<void> shareToWeChat({
|
||||
required String title,
|
||||
required String description,
|
||||
String? imageUrl,
|
||||
String? webpageUrl,
|
||||
}) async {
|
||||
final model = WeChatShareWebPageModel(
|
||||
webPage: webpageUrl ?? '',
|
||||
title: title,
|
||||
description: description,
|
||||
thumbnail: WeChatImage.network(imageUrl ?? ''),
|
||||
);
|
||||
|
||||
await fluwx.shareToWeChat(model);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 加密解密 (`src/crypto/`)
|
||||
|
||||
**功能职责**:
|
||||
- 对称加密解密
|
||||
- 非对称加密解密
|
||||
- 数字签名验证
|
||||
- 哈希算法实现
|
||||
|
||||
**主要组件**:
|
||||
- `AESCrypto` - AES加密
|
||||
- `RSACrypto` - RSA加密
|
||||
- `HashUtils` - 哈希工具
|
||||
- `SignatureUtils` - 数字签名
|
||||
|
||||
**使用示例**:
|
||||
```dart
|
||||
class CryptoUtils {
|
||||
static String md5Hash(String input) {
|
||||
final bytes = utf8.encode(input);
|
||||
final digest = md5.convert(bytes);
|
||||
return digest.toString();
|
||||
}
|
||||
|
||||
static String sha256Hash(String input) {
|
||||
final bytes = utf8.encode(input);
|
||||
final digest = sha256.convert(bytes);
|
||||
return digest.toString();
|
||||
}
|
||||
|
||||
static String aesEncrypt(String plainText, String key) {
|
||||
final encrypter = Encrypter(AES(Key.fromBase64(key)));
|
||||
final encrypted = encrypter.encrypt(plainText);
|
||||
return encrypted.base64;
|
||||
}
|
||||
|
||||
static String aesDecrypt(String encryptedText, String key) {
|
||||
final encrypter = Encrypter(AES(Key.fromBase64(key)));
|
||||
final decrypted = encrypter.decrypt64(encryptedText);
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 云存储 (`src/storage/`)
|
||||
|
||||
**功能职责**:
|
||||
- 腾讯云COS集成
|
||||
- 文件上传下载
|
||||
- 断点续传
|
||||
- 存储空间管理
|
||||
|
||||
**主要组件**:
|
||||
- `COSManager` - COS管理器
|
||||
- `FileUploader` - 文件上传器
|
||||
- `FileDownloader` - 文件下载器
|
||||
- `StorageMonitor` - 存储监控
|
||||
|
||||
**使用示例**:
|
||||
```dart
|
||||
class CloudStorageUtils {
|
||||
static Future<String?> uploadFile(
|
||||
String filePath, {
|
||||
String? objectKey,
|
||||
Function(int, int)? onProgress,
|
||||
}) async {
|
||||
try {
|
||||
final result = await TencentCloudCosSdkPlugin.upload(
|
||||
filePath,
|
||||
objectKey ?? _generateObjectKey(filePath),
|
||||
onProgress: onProgress,
|
||||
);
|
||||
|
||||
return result['url'] as String?;
|
||||
} catch (e) {
|
||||
print('Upload failed: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> downloadFile(
|
||||
String url,
|
||||
String savePath, {
|
||||
Function(int, int)? onProgress,
|
||||
}) async {
|
||||
try {
|
||||
await TencentCloudCosSdkPlugin.download(
|
||||
url,
|
||||
savePath,
|
||||
onProgress: onProgress,
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
print('Download failed: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static String _generateObjectKey(String filePath) {
|
||||
final fileName = path.basename(filePath);
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
return 'uploads/$timestamp/$fileName';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 网络工具 (`src/network/`)
|
||||
|
||||
**功能职责**:
|
||||
- HTTP请求封装
|
||||
- 网络状态检测
|
||||
- 请求重试机制
|
||||
- 响应数据处理
|
||||
|
||||
**主要组件**:
|
||||
- `HttpClient` - HTTP客户端
|
||||
- `NetworkMonitor` - 网络监控
|
||||
- `RequestInterceptor` - 请求拦截器
|
||||
- `ResponseHandler` - 响应处理器
|
||||
|
||||
### 8. 通用工具 (`src/utils/`)
|
||||
|
||||
**功能职责**:
|
||||
- 字符串处理工具
|
||||
- 日期时间工具
|
||||
- 数学计算工具
|
||||
- 格式化工具
|
||||
|
||||
**主要工具**:
|
||||
- `StringUtils` - 字符串工具
|
||||
- `DateUtils` - 日期工具
|
||||
- `MathUtils` - 数学工具
|
||||
- `FormatUtils` - 格式化工具
|
||||
|
||||
**工具方法示例**:
|
||||
```dart
|
||||
class StringUtils {
|
||||
static bool isNullOrEmpty(String? str) {
|
||||
return str == null || str.isEmpty;
|
||||
}
|
||||
|
||||
static bool isValidEmail(String email) {
|
||||
return RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(email);
|
||||
}
|
||||
|
||||
static bool isValidPhone(String phone) {
|
||||
return RegExp(r'^1[3-9]\d{9}$').hasMatch(phone);
|
||||
}
|
||||
|
||||
static String maskPhone(String phone) {
|
||||
if (phone.length != 11) return phone;
|
||||
return '${phone.substring(0, 3)}****${phone.substring(7)}';
|
||||
}
|
||||
}
|
||||
|
||||
class DateUtils {
|
||||
static String formatDateTime(DateTime dateTime, [String? pattern]) {
|
||||
pattern ??= 'yyyy-MM-dd HH:mm:ss';
|
||||
return DateFormat(pattern).format(dateTime);
|
||||
}
|
||||
|
||||
static DateTime? parseDateTime(String dateStr, [String? pattern]) {
|
||||
try {
|
||||
pattern ??= 'yyyy-MM-dd HH:mm:ss';
|
||||
return DateFormat(pattern).parse(dateStr);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isToday(DateTime dateTime) {
|
||||
final now = DateTime.now();
|
||||
return dateTime.year == now.year &&
|
||||
dateTime.month == now.month &&
|
||||
dateTime.day == now.day;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 通用组件
|
||||
|
||||
### Loading组件
|
||||
```dart
|
||||
class LoadingUtils {
|
||||
static OverlayEntry? _overlayEntry;
|
||||
|
||||
static void show(BuildContext context, {String? message}) {
|
||||
hide(); // 先隐藏之前的
|
||||
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (context) => Material(
|
||||
color: Colors.black54,
|
||||
child: Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
if (message != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
Text(message),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
static void hide() {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Toast组件
|
||||
```dart
|
||||
class ToastUtils {
|
||||
static void showSuccess(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
backgroundColor: Colors.green,
|
||||
textColor: Colors.white,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
);
|
||||
}
|
||||
|
||||
static void showError(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
);
|
||||
}
|
||||
|
||||
static void showInfo(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
backgroundColor: Colors.blue,
|
||||
textColor: Colors.white,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 内存管理
|
||||
- **事件订阅管理**: 及时取消不需要的事件订阅
|
||||
- **图片内存优化**: 合理控制图片加载和缓存
|
||||
- **对象池**: 复用常用对象减少GC压力
|
||||
- **弱引用**: 使用弱引用避免内存泄漏
|
||||
|
||||
### 网络优化
|
||||
- **请求缓存**: 缓存网络请求结果
|
||||
- **并发控制**: 控制并发请求数量
|
||||
- **超时重试**: 智能超时和重试机制
|
||||
- **数据压缩**: 压缩传输数据
|
||||
|
||||
### 存储优化
|
||||
- **文件压缩**: 压缩存储文件
|
||||
- **清理策略**: 定期清理过期文件
|
||||
- **分块上传**: 大文件分块上传
|
||||
- **断点续传**: 支持上传下载断点续传
|
||||
|
||||
## 安全特性
|
||||
|
||||
### 数据安全
|
||||
- **敏感数据加密**: 敏感信息加密存储
|
||||
- **传输加密**: HTTPS传输保护
|
||||
- **数据校验**: 数据完整性校验
|
||||
- **防篡改**: 数字签名防篡改
|
||||
|
||||
### 隐私保护
|
||||
- **权限控制**: 严格的功能权限控制
|
||||
- **数据脱敏**: 敏感数据脱敏处理
|
||||
- **本地存储**: 安全的本地数据存储
|
||||
- **清理机制**: 数据清理和销毁机制
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试
|
||||
- **工具类测试**: 各种工具方法测试
|
||||
- **加密解密测试**: 加密算法正确性
|
||||
- **事件总线测试**: 事件发布订阅机制
|
||||
- **网络工具测试**: 网络请求功能
|
||||
|
||||
### 集成测试
|
||||
- **文件上传测试**: 云存储集成测试
|
||||
- **分享功能测试**: 社交分享集成测试
|
||||
- **图片处理测试**: 图片处理流程测试
|
||||
- **端到端测试**: 完整功能流程测试
|
||||
|
||||
### 性能测试
|
||||
- **内存泄漏测试**: 长时间运行检测
|
||||
- **并发性能测试**: 高并发场景测试
|
||||
- **文件处理测试**: 大文件处理性能
|
||||
- **网络性能测试**: 网络请求性能
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 开发建议
|
||||
1. **工具复用**: 优先使用现有工具方法
|
||||
2. **异常处理**: 完善的异常处理机制
|
||||
3. **资源管理**: 及时释放不需要的资源
|
||||
4. **性能考虑**: 避免不必要的操作开销
|
||||
|
||||
### 使用指南
|
||||
1. **按需导入**: 只导入需要的工具类
|
||||
2. **配置优化**: 合理配置各种参数
|
||||
3. **错误处理**: 妥善处理可能的错误
|
||||
4. **日志记录**: 记录关键操作日志
|
||||
|
||||
## 总结
|
||||
|
||||
`basic_utils` 模块作为 OneApp 的基础工具集合,提供了丰富的开发工具和辅助功能。通过事件总线、图片处理、分享功能、加密解密等核心能力,大大简化了应用开发的复杂性。模块具有良好的性能优化和安全保护机制,能够满足各种开发场景的需求。
|
||||
902
basic_utils/flutter_downloader.md
Normal file
902
basic_utils/flutter_downloader.md
Normal file
@@ -0,0 +1,902 @@
|
||||
# Flutter Downloader 文件下载器模块
|
||||
|
||||
## 模块概述
|
||||
|
||||
`flutter_downloader` 是 OneApp 基础工具模块群中的文件下载器模块,基于 Flutter Community 的开源项目进行定制化。该模块提供了强大的文件下载能力,支持多平台(Android、iOS、鸿蒙)的原生下载功能,包括后台下载、断点续传、下载管理等特性。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: flutter_downloader
|
||||
- **版本**: 1.11.8
|
||||
- **描述**: 强大的文件下载插件
|
||||
- **Flutter 版本**: >=3.19.0
|
||||
- **Dart 版本**: >=3.3.0 <4.0.0
|
||||
- **原始项目**: https://github.com/fluttercommunity/flutter_downloader
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
1. **多平台下载支持**
|
||||
- Android 原生下载管理器集成
|
||||
- iOS 后台下载任务支持
|
||||
- 鸿蒙(HarmonyOS)平台支持
|
||||
- 跨平台统一接口
|
||||
|
||||
2. **高级下载特性**
|
||||
- 后台下载支持
|
||||
- 断点续传功能
|
||||
- 下载进度监控
|
||||
- 并发下载控制
|
||||
|
||||
3. **下载管理系统**
|
||||
- 下载任务队列
|
||||
- 下载状态跟踪
|
||||
- 下载历史记录
|
||||
- 失败重试机制
|
||||
|
||||
4. **文件系统集成**
|
||||
- 灵活的保存路径配置
|
||||
- 文件完整性验证
|
||||
- 存储空间检查
|
||||
- 文件权限管理
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
flutter_downloader/
|
||||
├── lib/ # Dart代码
|
||||
│ ├── flutter_downloader.dart # 主入口文件
|
||||
│ └── src/ # 源代码
|
||||
│ ├── downloader.dart # 下载器核心
|
||||
│ ├── models.dart # 数据模型
|
||||
│ ├── callback_dispatcher.dart # 回调分发器
|
||||
│ └── utils.dart # 工具类
|
||||
├── android/ # Android原生实现
|
||||
│ └── src/main/java/
|
||||
│ └── vn/hunghd/flutterdownloader/
|
||||
│ ├── FlutterDownloaderPlugin.java
|
||||
│ ├── DownloadWorker.java
|
||||
│ ├── TaskDbHelper.java
|
||||
│ └── TaskDao.java
|
||||
├── ios/ # iOS原生实现
|
||||
│ ├── Classes/
|
||||
│ │ ├── FlutterDownloaderPlugin.h
|
||||
│ │ ├── FlutterDownloaderPlugin.m
|
||||
│ │ ├── DBManager.h
|
||||
│ │ └── DBManager.m
|
||||
│ └── flutter_downloader.podspec
|
||||
├── ohos/ # 鸿蒙原生实现
|
||||
└── example/ # 示例应用
|
||||
```
|
||||
|
||||
### 平台支持
|
||||
- **Android**: 使用 DownloadManager 和 WorkManager
|
||||
- **iOS**: 使用 URLSessionDownloadTask
|
||||
- **鸿蒙**: 使用 HarmonyOS 下载接口
|
||||
|
||||
## 核心模块分析
|
||||
|
||||
### 1. Flutter端实现
|
||||
|
||||
#### 主入口 (`lib/flutter_downloader.dart`)
|
||||
```dart
|
||||
class FlutterDownloader {
|
||||
static const MethodChannel _channel = MethodChannel('vn.hunghd/downloader');
|
||||
static const MethodChannel _backgroundChannel =
|
||||
MethodChannel('vn.hunghd/downloader_send_port');
|
||||
|
||||
/// 初始化下载器
|
||||
static Future<void> initialize({
|
||||
bool debug = false,
|
||||
bool ignoreSsl = false,
|
||||
}) async {
|
||||
await _channel.invokeMethod('initialize', {
|
||||
'debug': debug,
|
||||
'ignoreSsl': ignoreSsl,
|
||||
});
|
||||
}
|
||||
|
||||
/// 创建下载任务
|
||||
static Future<String?> enqueue({
|
||||
required String url,
|
||||
required String savedDir,
|
||||
String? fileName,
|
||||
Map<String, String>? headers,
|
||||
bool showNotification = true,
|
||||
bool openFileFromNotification = true,
|
||||
bool requiresStorageNotLow = false,
|
||||
bool saveInPublicStorage = false,
|
||||
}) async {
|
||||
try {
|
||||
final result = await _channel.invokeMethod('enqueue', {
|
||||
'url': url,
|
||||
'saved_dir': savedDir,
|
||||
'file_name': fileName,
|
||||
'headers': headers,
|
||||
'show_notification': showNotification,
|
||||
'open_file_from_notification': openFileFromNotification,
|
||||
'requires_storage_not_low': requiresStorageNotLow,
|
||||
'save_in_public_storage': saveInPublicStorage,
|
||||
});
|
||||
return result;
|
||||
} catch (e) {
|
||||
print('Error creating download task: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取所有下载任务
|
||||
static Future<List<DownloadTask>?> loadTasks() async {
|
||||
try {
|
||||
final result = await _channel.invokeMethod('loadTasks');
|
||||
return (result as List<dynamic>?)
|
||||
?.map((item) => DownloadTask.fromMap(item))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
print('Error loading tasks: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消下载任务
|
||||
static Future<void> cancel({required String taskId}) async {
|
||||
await _channel.invokeMethod('cancel', {'task_id': taskId});
|
||||
}
|
||||
|
||||
/// 暂停下载任务
|
||||
static Future<void> pause({required String taskId}) async {
|
||||
await _channel.invokeMethod('pause', {'task_id': taskId});
|
||||
}
|
||||
|
||||
/// 恢复下载任务
|
||||
static Future<void> resume({required String taskId}) async {
|
||||
await _channel.invokeMethod('resume', {'task_id': taskId});
|
||||
}
|
||||
|
||||
/// 重试下载任务
|
||||
static Future<String?> retry({required String taskId}) async {
|
||||
return await _channel.invokeMethod('retry', {'task_id': taskId});
|
||||
}
|
||||
|
||||
/// 移除下载任务
|
||||
static Future<void> remove({
|
||||
required String taskId,
|
||||
bool shouldDeleteContent = false,
|
||||
}) async {
|
||||
await _channel.invokeMethod('remove', {
|
||||
'task_id': taskId,
|
||||
'should_delete_content': shouldDeleteContent,
|
||||
});
|
||||
}
|
||||
|
||||
/// 打开下载的文件
|
||||
static Future<bool> open({required String taskId}) async {
|
||||
return await _channel.invokeMethod('open', {'task_id': taskId});
|
||||
}
|
||||
|
||||
/// 注册下载回调
|
||||
static void registerCallback(DownloadCallback callback) {
|
||||
_callback = callback;
|
||||
_channel.setMethodCallHandler(_handleMethodCall);
|
||||
}
|
||||
|
||||
static DownloadCallback? _callback;
|
||||
|
||||
static Future<dynamic> _handleMethodCall(MethodCall call) async {
|
||||
if (call.method == 'updateProgress') {
|
||||
final id = call.arguments['id'] as String;
|
||||
final status = DownloadTaskStatus.values[call.arguments['status'] as int];
|
||||
final progress = call.arguments['progress'] as int;
|
||||
_callback?.call(id, status, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef DownloadCallback = void Function(
|
||||
String id,
|
||||
DownloadTaskStatus status,
|
||||
int progress,
|
||||
);
|
||||
```
|
||||
|
||||
#### 数据模型 (`src/models.dart`)
|
||||
```dart
|
||||
enum DownloadTaskStatus {
|
||||
undefined,
|
||||
enqueued,
|
||||
running,
|
||||
complete,
|
||||
failed,
|
||||
canceled,
|
||||
paused,
|
||||
}
|
||||
|
||||
class DownloadTask {
|
||||
final String taskId;
|
||||
final String url;
|
||||
final String filename;
|
||||
final String savedDir;
|
||||
final DownloadTaskStatus status;
|
||||
final int progress;
|
||||
final int timeCreated;
|
||||
final bool allowCellular;
|
||||
|
||||
const DownloadTask({
|
||||
required this.taskId,
|
||||
required this.url,
|
||||
required this.filename,
|
||||
required this.savedDir,
|
||||
required this.status,
|
||||
required this.progress,
|
||||
required this.timeCreated,
|
||||
required this.allowCellular,
|
||||
});
|
||||
|
||||
factory DownloadTask.fromMap(Map<String, dynamic> map) {
|
||||
return DownloadTask(
|
||||
taskId: map['task_id'] as String,
|
||||
url: map['url'] as String,
|
||||
filename: map['file_name'] as String,
|
||||
savedDir: map['saved_dir'] as String,
|
||||
status: DownloadTaskStatus.values[map['status'] as int],
|
||||
progress: map['progress'] as int,
|
||||
timeCreated: map['time_created'] as int,
|
||||
allowCellular: map['allow_cellular'] as bool? ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'task_id': taskId,
|
||||
'url': url,
|
||||
'file_name': filename,
|
||||
'saved_dir': savedDir,
|
||||
'status': status.index,
|
||||
'progress': progress,
|
||||
'time_created': timeCreated,
|
||||
'allow_cellular': allowCellular,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Android端实现
|
||||
|
||||
#### 主插件类 (`FlutterDownloaderPlugin.java`)
|
||||
```java
|
||||
public class FlutterDownloaderPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
private static final String CHANNEL = "vn.hunghd/downloader";
|
||||
private static final String BACKGROUND_CHANNEL = "vn.hunghd/downloader_send_port";
|
||||
|
||||
private Context context;
|
||||
private MethodChannel channel;
|
||||
private MethodChannel backgroundChannel;
|
||||
private TaskDbHelper dbHelper;
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||
context = binding.getApplicationContext();
|
||||
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL);
|
||||
backgroundChannel = new MethodChannel(binding.getBinaryMessenger(), BACKGROUND_CHANNEL);
|
||||
channel.setMethodCallHandler(this);
|
||||
dbHelper = TaskDbHelper.getInstance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
||||
switch (call.method) {
|
||||
case "initialize":
|
||||
initialize(call, result);
|
||||
break;
|
||||
case "enqueue":
|
||||
enqueue(call, result);
|
||||
break;
|
||||
case "loadTasks":
|
||||
loadTasks(result);
|
||||
break;
|
||||
case "cancel":
|
||||
cancel(call, result);
|
||||
break;
|
||||
case "pause":
|
||||
pause(call, result);
|
||||
break;
|
||||
case "resume":
|
||||
resume(call, result);
|
||||
break;
|
||||
case "retry":
|
||||
retry(call, result);
|
||||
break;
|
||||
case "remove":
|
||||
remove(call, result);
|
||||
break;
|
||||
case "open":
|
||||
open(call, result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
private void enqueue(MethodCall call, Result result) {
|
||||
String url = call.argument("url");
|
||||
String savedDir = call.argument("saved_dir");
|
||||
String fileName = call.argument("file_name");
|
||||
Map<String, String> headers = call.argument("headers");
|
||||
boolean showNotification = call.argument("show_notification");
|
||||
|
||||
// 创建下载请求
|
||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
|
||||
|
||||
// 设置请求头
|
||||
if (headers != null) {
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
request.addRequestHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置下载路径
|
||||
String finalFileName = fileName != null ? fileName :
|
||||
URLUtil.guessFileName(url, null, null);
|
||||
request.setDestinationInExternalFilesDir(context, null,
|
||||
savedDir + "/" + finalFileName);
|
||||
|
||||
// 设置通知
|
||||
if (showNotification) {
|
||||
request.setNotificationVisibility(
|
||||
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
}
|
||||
|
||||
// 启动下载
|
||||
DownloadManager downloadManager = (DownloadManager)
|
||||
context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
long downloadId = downloadManager.enqueue(request);
|
||||
|
||||
// 保存任务信息到数据库
|
||||
String taskId = UUID.randomUUID().toString();
|
||||
TaskDao taskDao = new TaskDao(taskId, url, finalFileName, savedDir,
|
||||
DownloadTaskStatus.ENQUEUED.ordinal(), 0, System.currentTimeMillis());
|
||||
dbHelper.insertOrUpdateNewTask(taskDao);
|
||||
|
||||
result.success(taskId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 下载工作器 (`DownloadWorker.java`)
|
||||
```java
|
||||
public class DownloadWorker extends Worker {
|
||||
public DownloadWorker(@NonNull Context context, @NonNull WorkerParameters params) {
|
||||
super(context, params);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
String taskId = getInputData().getString("task_id");
|
||||
String url = getInputData().getString("url");
|
||||
|
||||
try {
|
||||
// 执行下载逻辑
|
||||
downloadFile(taskId, url);
|
||||
return Result.success();
|
||||
} catch (Exception e) {
|
||||
return Result.failure();
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadFile(String taskId, String url) {
|
||||
// 下载实现逻辑
|
||||
// 包括进度更新、错误处理等
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. iOS端实现
|
||||
|
||||
#### 主插件类 (`FlutterDownloaderPlugin.m`)
|
||||
```objc
|
||||
@implementation FlutterDownloaderPlugin
|
||||
|
||||
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
|
||||
FlutterMethodChannel* channel = [FlutterMethodChannel
|
||||
methodChannelWithName:@"vn.hunghd/downloader"
|
||||
binaryMessenger:[registrar messenger]];
|
||||
FlutterDownloaderPlugin* instance = [[FlutterDownloaderPlugin alloc] init];
|
||||
[registrar addMethodCallDelegate:instance channel:channel];
|
||||
}
|
||||
|
||||
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
if ([@"initialize" isEqualToString:call.method]) {
|
||||
[self initialize:call result:result];
|
||||
} else if ([@"enqueue" isEqualToString:call.method]) {
|
||||
[self enqueue:call result:result];
|
||||
} else if ([@"loadTasks" isEqualToString:call.method]) {
|
||||
[self loadTasks:result];
|
||||
} else if ([@"cancel" isEqualToString:call.method]) {
|
||||
[self cancel:call result:result];
|
||||
} else if ([@"pause" isEqualToString:call.method]) {
|
||||
[self pause:call result:result];
|
||||
} else if ([@"resume" isEqualToString:call.method]) {
|
||||
[self resume:call result:result];
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)enqueue:(FlutterMethodCall*)call result:(FlutterResult)result {
|
||||
NSString* url = call.arguments[@"url"];
|
||||
NSString* savedDir = call.arguments[@"saved_dir"];
|
||||
NSString* fileName = call.arguments[@"file_name"];
|
||||
NSDictionary* headers = call.arguments[@"headers"];
|
||||
|
||||
// 创建下载任务
|
||||
NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[[NSUUID UUID] UUIDString]];
|
||||
NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
|
||||
|
||||
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
|
||||
|
||||
// 设置请求头
|
||||
if (headers) {
|
||||
for (NSString* key in headers) {
|
||||
[request setValue:headers[key] forHTTPHeaderField:key];
|
||||
}
|
||||
}
|
||||
|
||||
NSURLSessionDownloadTask* downloadTask = [session downloadTaskWithRequest:request];
|
||||
[downloadTask resume];
|
||||
|
||||
// 生成任务ID并保存
|
||||
NSString* taskId = [[NSUUID UUID] UUIDString];
|
||||
[[DBManager getInstance] insertTask:taskId url:url fileName:fileName savedDir:savedDir];
|
||||
|
||||
result(taskId);
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionDownloadDelegate
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
|
||||
// 处理下载完成
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
|
||||
// 更新下载进度
|
||||
double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
|
||||
// 通知Flutter端进度更新
|
||||
}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
## 业务流程
|
||||
|
||||
### 下载任务生命周期
|
||||
```mermaid
|
||||
graph TD
|
||||
A[创建下载任务] --> B[任务入队]
|
||||
B --> C[开始下载]
|
||||
C --> D[下载进行中]
|
||||
D --> E{下载结果}
|
||||
E -->|成功| F[下载完成]
|
||||
E -->|失败| G[下载失败]
|
||||
E -->|取消| H[任务取消]
|
||||
E -->|暂停| I[任务暂停]
|
||||
|
||||
F --> J[通知用户]
|
||||
G --> K[错误处理]
|
||||
H --> L[清理资源]
|
||||
I --> M[可恢复状态]
|
||||
|
||||
K --> N{是否重试}
|
||||
N -->|是| C
|
||||
N -->|否| L
|
||||
|
||||
M --> O[恢复下载]
|
||||
O --> C
|
||||
```
|
||||
|
||||
### 进度更新流程
|
||||
```mermaid
|
||||
graph TD
|
||||
A[原生下载器] --> B[进度计算]
|
||||
B --> C[方法通道传递]
|
||||
C --> D[Flutter回调处理]
|
||||
D --> E[UI更新]
|
||||
|
||||
F[定时器] --> G[状态检查]
|
||||
G --> H{状态是否变化}
|
||||
H -->|是| I[通知状态更新]
|
||||
H -->|否| J[继续监控]
|
||||
I --> D
|
||||
J --> G
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础下载示例
|
||||
```dart
|
||||
class DownloadExample extends StatefulWidget {
|
||||
@override
|
||||
_DownloadExampleState createState() => _DownloadExampleState();
|
||||
}
|
||||
|
||||
class _DownloadExampleState extends State<DownloadExample> {
|
||||
List<DownloadTask> _tasks = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeDownloader();
|
||||
}
|
||||
|
||||
Future<void> _initializeDownloader() async {
|
||||
await FlutterDownloader.initialize(debug: true);
|
||||
|
||||
// 注册下载回调
|
||||
FlutterDownloader.registerCallback(_downloadCallback);
|
||||
|
||||
// 加载已有任务
|
||||
_loadTasks();
|
||||
}
|
||||
|
||||
void _downloadCallback(String id, DownloadTaskStatus status, int progress) {
|
||||
setState(() {
|
||||
final taskIndex = _tasks.indexWhere((task) => task.taskId == id);
|
||||
if (taskIndex != -1) {
|
||||
_tasks[taskIndex] = DownloadTask(
|
||||
taskId: id,
|
||||
url: _tasks[taskIndex].url,
|
||||
filename: _tasks[taskIndex].filename,
|
||||
savedDir: _tasks[taskIndex].savedDir,
|
||||
status: status,
|
||||
progress: progress,
|
||||
timeCreated: _tasks[taskIndex].timeCreated,
|
||||
allowCellular: _tasks[taskIndex].allowCellular,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadTasks() async {
|
||||
final tasks = await FlutterDownloader.loadTasks();
|
||||
setState(() {
|
||||
_tasks = tasks ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _startDownload(String url, String fileName) async {
|
||||
final savedDir = await _getSavedDir();
|
||||
|
||||
final taskId = await FlutterDownloader.enqueue(
|
||||
url: url,
|
||||
savedDir: savedDir,
|
||||
fileName: fileName,
|
||||
showNotification: true,
|
||||
openFileFromNotification: true,
|
||||
);
|
||||
|
||||
if (taskId != null) {
|
||||
_loadTasks();
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _getSavedDir() async {
|
||||
final directory = await getExternalStorageDirectory();
|
||||
return '${directory!.path}/downloads';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Downloads')),
|
||||
body: ListView.builder(
|
||||
itemCount: _tasks.length,
|
||||
itemBuilder: (context, index) {
|
||||
final task = _tasks[index];
|
||||
return ListTile(
|
||||
title: Text(task.filename),
|
||||
subtitle: LinearProgressIndicator(
|
||||
value: task.progress / 100.0,
|
||||
),
|
||||
trailing: _buildActionButton(task),
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _showDownloadDialog(),
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton(DownloadTask task) {
|
||||
switch (task.status) {
|
||||
case DownloadTaskStatus.running:
|
||||
return IconButton(
|
||||
icon: Icon(Icons.pause),
|
||||
onPressed: () => FlutterDownloader.pause(taskId: task.taskId),
|
||||
);
|
||||
case DownloadTaskStatus.paused:
|
||||
return IconButton(
|
||||
icon: Icon(Icons.play_arrow),
|
||||
onPressed: () => FlutterDownloader.resume(taskId: task.taskId),
|
||||
);
|
||||
case DownloadTaskStatus.failed:
|
||||
return IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
onPressed: () => FlutterDownloader.retry(taskId: task.taskId),
|
||||
);
|
||||
case DownloadTaskStatus.complete:
|
||||
return IconButton(
|
||||
icon: Icon(Icons.open_in_new),
|
||||
onPressed: () => FlutterDownloader.open(taskId: task.taskId),
|
||||
);
|
||||
default:
|
||||
return IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () => FlutterDownloader.remove(
|
||||
taskId: task.taskId,
|
||||
shouldDeleteContent: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showDownloadDialog() {
|
||||
// 显示下载URL输入对话框
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => DownloadDialog(
|
||||
onDownload: (url, fileName) => _startDownload(url, fileName),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 批量下载管理
|
||||
```dart
|
||||
class BatchDownloadManager {
|
||||
static final BatchDownloadManager _instance = BatchDownloadManager._internal();
|
||||
factory BatchDownloadManager() => _instance;
|
||||
BatchDownloadManager._internal();
|
||||
|
||||
final Map<String, DownloadTask> _activeTasks = {};
|
||||
final StreamController<List<DownloadTask>> _tasksController =
|
||||
StreamController<List<DownloadTask>>.broadcast();
|
||||
|
||||
Stream<List<DownloadTask>> get tasksStream => _tasksController.stream;
|
||||
|
||||
Future<void> initialize() async {
|
||||
await FlutterDownloader.initialize();
|
||||
FlutterDownloader.registerCallback(_onDownloadCallback);
|
||||
await _loadExistingTasks();
|
||||
}
|
||||
|
||||
Future<void> _loadExistingTasks() async {
|
||||
final tasks = await FlutterDownloader.loadTasks();
|
||||
if (tasks != null) {
|
||||
_activeTasks.clear();
|
||||
for (final task in tasks) {
|
||||
_activeTasks[task.taskId] = task;
|
||||
}
|
||||
_notifyTasksUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void _onDownloadCallback(String id, DownloadTaskStatus status, int progress) {
|
||||
final existingTask = _activeTasks[id];
|
||||
if (existingTask != null) {
|
||||
_activeTasks[id] = DownloadTask(
|
||||
taskId: id,
|
||||
url: existingTask.url,
|
||||
filename: existingTask.filename,
|
||||
savedDir: existingTask.savedDir,
|
||||
status: status,
|
||||
progress: progress,
|
||||
timeCreated: existingTask.timeCreated,
|
||||
allowCellular: existingTask.allowCellular,
|
||||
);
|
||||
_notifyTasksUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> addDownload({
|
||||
required String url,
|
||||
required String savedDir,
|
||||
String? fileName,
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
final taskId = await FlutterDownloader.enqueue(
|
||||
url: url,
|
||||
savedDir: savedDir,
|
||||
fileName: fileName,
|
||||
headers: headers,
|
||||
showNotification: true,
|
||||
);
|
||||
|
||||
if (taskId != null) {
|
||||
await _loadExistingTasks();
|
||||
}
|
||||
|
||||
return taskId;
|
||||
}
|
||||
|
||||
Future<void> pauseDownload(String taskId) async {
|
||||
await FlutterDownloader.pause(taskId: taskId);
|
||||
}
|
||||
|
||||
Future<void> resumeDownload(String taskId) async {
|
||||
await FlutterDownloader.resume(taskId: taskId);
|
||||
}
|
||||
|
||||
Future<void> cancelDownload(String taskId) async {
|
||||
await FlutterDownloader.cancel(taskId: taskId);
|
||||
_activeTasks.remove(taskId);
|
||||
_notifyTasksUpdate();
|
||||
}
|
||||
|
||||
Future<void> retryDownload(String taskId) async {
|
||||
final newTaskId = await FlutterDownloader.retry(taskId: taskId);
|
||||
if (newTaskId != null) {
|
||||
await _loadExistingTasks();
|
||||
}
|
||||
}
|
||||
|
||||
List<DownloadTask> getTasksByStatus(DownloadTaskStatus status) {
|
||||
return _activeTasks.values
|
||||
.where((task) => task.status == status)
|
||||
.toList();
|
||||
}
|
||||
|
||||
void _notifyTasksUpdate() {
|
||||
_tasksController.add(_activeTasks.values.toList());
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_tasksController.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 配置和优化
|
||||
|
||||
### Android配置
|
||||
```xml
|
||||
<!-- android/app/src/main/AndroidManifest.xml -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application>
|
||||
<provider
|
||||
android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
|
||||
android:authorities="${applicationId}.flutter_downloader.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths"/>
|
||||
</provider>
|
||||
</application>
|
||||
```
|
||||
|
||||
### iOS配置
|
||||
```xml
|
||||
<!-- ios/Runner/Info.plist -->
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>background-fetch</string>
|
||||
<string>background-processing</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
### 性能优化
|
||||
```dart
|
||||
class DownloadOptimizer {
|
||||
static const int MAX_CONCURRENT_DOWNLOADS = 3;
|
||||
static const int RETRY_ATTEMPTS = 3;
|
||||
static const Duration RETRY_DELAY = Duration(seconds: 5);
|
||||
|
||||
static Future<void> configureDownloader() async {
|
||||
await FlutterDownloader.initialize(
|
||||
debug: kDebugMode,
|
||||
ignoreSsl: false,
|
||||
);
|
||||
}
|
||||
|
||||
static Map<String, String> getOptimizedHeaders() {
|
||||
return {
|
||||
'User-Agent': 'OneApp/${getAppVersion()}',
|
||||
'Accept-Encoding': 'gzip, deflate',
|
||||
'Connection': 'keep-alive',
|
||||
};
|
||||
}
|
||||
|
||||
static Future<String?> smartDownload({
|
||||
required String url,
|
||||
required String savedDir,
|
||||
String? fileName,
|
||||
}) async {
|
||||
// 检查网络状态
|
||||
final connectivity = await Connectivity().checkConnectivity();
|
||||
if (connectivity == ConnectivityResult.none) {
|
||||
throw Exception('No network connection');
|
||||
}
|
||||
|
||||
// 检查存储空间
|
||||
final directory = Directory(savedDir);
|
||||
if (!await directory.exists()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
||||
final freeSpace = await _getAvailableSpace(savedDir);
|
||||
final fileSize = await _getFileSize(url);
|
||||
if (fileSize > freeSpace) {
|
||||
throw Exception('Insufficient storage space');
|
||||
}
|
||||
|
||||
// 开始下载
|
||||
return await FlutterDownloader.enqueue(
|
||||
url: url,
|
||||
savedDir: savedDir,
|
||||
fileName: fileName,
|
||||
headers: getOptimizedHeaders(),
|
||||
showNotification: true,
|
||||
requiresStorageNotLow: true,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<int> _getAvailableSpace(String path) async {
|
||||
// 获取可用存储空间
|
||||
return 0; // 实现细节
|
||||
}
|
||||
|
||||
static Future<int> _getFileSize(String url) async {
|
||||
// 获取文件大小
|
||||
return 0; // 实现细节
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 安全特性
|
||||
|
||||
### 文件安全
|
||||
- **路径验证**: 验证下载路径安全性
|
||||
- **文件类型检查**: 检查允许的文件类型
|
||||
- **病毒扫描**: 集成安全扫描机制
|
||||
- **权限控制**: 严格的文件访问权限
|
||||
|
||||
### 网络安全
|
||||
- **HTTPS验证**: 强制HTTPS下载
|
||||
- **证书校验**: SSL证书有效性检查
|
||||
- **请求头验证**: 防止恶意请求头
|
||||
- **下载限制**: 文件大小和类型限制
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 单元测试
|
||||
- **下载逻辑测试**: 核心下载功能
|
||||
- **状态管理测试**: 任务状态转换
|
||||
- **错误处理测试**: 异常情况处理
|
||||
- **数据模型测试**: 序列化反序列化
|
||||
|
||||
### 集成测试
|
||||
- **平台兼容测试**: 不同平台下载功能
|
||||
- **网络环境测试**: 不同网络条件
|
||||
- **存储测试**: 不同存储路径和权限
|
||||
- **并发测试**: 多任务并发下载
|
||||
|
||||
### 性能测试
|
||||
- **大文件下载**: 大文件下载稳定性
|
||||
- **长时间运行**: 长期后台下载
|
||||
- **内存使用**: 内存泄漏检测
|
||||
- **电池消耗**: 下载对电池的影响
|
||||
|
||||
## 总结
|
||||
|
||||
`flutter_downloader` 模块作为 OneApp 的文件下载器,提供了强大的跨平台下载能力。通过原生实现和Flutter接口的完美结合,实现了高效、稳定的文件下载功能。模块支持后台下载、断点续传、进度监控等高级特性,能够满足各种下载场景的需求。良好的错误处理和性能优化机制确保了下载任务的可靠性和用户体验。
|
||||
1139
basic_utils/flutter_plugin_mtpush_private.md
Normal file
1139
basic_utils/flutter_plugin_mtpush_private.md
Normal file
File diff suppressed because it is too large
Load Diff
998
basic_utils/kit_app_monitor.md
Normal file
998
basic_utils/kit_app_monitor.md
Normal file
@@ -0,0 +1,998 @@
|
||||
# Kit App Monitor 应用监控工具包
|
||||
|
||||
## 模块概述
|
||||
|
||||
`kit_app_monitor` 是 OneApp 基础工具模块群中的应用监控工具包,负责实时监控应用的性能指标、异常情况、用户行为等关键数据。该插件通过原生平台能力,为应用提供全面的监控和分析功能。
|
||||
|
||||
### 基本信息
|
||||
- **模块名称**: kit_app_monitor
|
||||
- **版本**: 0.0.2+1
|
||||
- **类型**: Flutter Plugin(原生插件)
|
||||
- **描述**: 应用监控工具包
|
||||
- **Flutter 版本**: >=3.0.0
|
||||
- **Dart 版本**: >=3.0.0<4.0.0
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
1. **性能监控**
|
||||
- CPU使用率监控
|
||||
- 内存使用监控
|
||||
- 网络请求性能
|
||||
- 帧率(FPS)监控
|
||||
|
||||
2. **异常监控**
|
||||
- Crash崩溃监控
|
||||
- ANR(应用无响应)检测
|
||||
- 网络异常监控
|
||||
- 自定义异常上报
|
||||
|
||||
3. **用户行为分析**
|
||||
- 页面访问统计
|
||||
- 用户操作轨迹
|
||||
- 功能使用频率
|
||||
- 用户留存分析
|
||||
|
||||
4. **设备信息收集**
|
||||
- 设备硬件信息
|
||||
- 系统版本信息
|
||||
- 应用版本信息
|
||||
- 网络环境信息
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
kit_app_monitor/
|
||||
├── lib/ # Dart代码
|
||||
│ ├── kit_app_monitor.dart # 插件入口
|
||||
│ ├── src/ # 源代码
|
||||
│ │ ├── platform_interface.dart # 平台接口定义
|
||||
│ │ ├── method_channel.dart # 方法通道实现
|
||||
│ │ ├── models/ # 数据模型
|
||||
│ │ │ ├── performance_data.dart
|
||||
│ │ │ ├── crash_report.dart
|
||||
│ │ │ └── user_behavior.dart
|
||||
│ │ └── utils/ # 工具类
|
||||
│ └── kit_app_monitor_platform_interface.dart
|
||||
├── android/ # Android原生代码
|
||||
│ ├── src/main/kotlin/
|
||||
│ │ └── com/cariadcn/kit_app_monitor/
|
||||
│ │ ├── KitAppMonitorPlugin.kt
|
||||
│ │ ├── PerformanceMonitor.kt
|
||||
│ │ ├── CrashHandler.kt
|
||||
│ │ └── BehaviorTracker.kt
|
||||
│ └── build.gradle
|
||||
├── ios/ # iOS原生代码
|
||||
│ ├── Classes/
|
||||
│ │ ├── KitAppMonitorPlugin.swift
|
||||
│ │ ├── PerformanceMonitor.swift
|
||||
│ │ ├── CrashHandler.swift
|
||||
│ │ └── BehaviorTracker.swift
|
||||
│ └── kit_app_monitor.podspec
|
||||
├── example/ # 示例应用
|
||||
└── test/ # 测试文件
|
||||
```
|
||||
|
||||
### 依赖关系
|
||||
|
||||
#### 核心依赖
|
||||
- `plugin_platform_interface: ^2.0.2` - 插件平台接口
|
||||
- `json_annotation: ^4.8.1` - JSON序列化
|
||||
|
||||
#### 开发依赖
|
||||
- `json_serializable: ^6.7.0` - JSON序列化代码生成
|
||||
- `build_runner: any` - 代码生成引擎
|
||||
|
||||
## 核心模块分析
|
||||
|
||||
### 1. Flutter端实现
|
||||
|
||||
#### 插件入口 (`lib/kit_app_monitor.dart`)
|
||||
```dart
|
||||
class KitAppMonitor {
|
||||
static const MethodChannel _channel = MethodChannel('kit_app_monitor');
|
||||
|
||||
static bool _isInitialized = false;
|
||||
static StreamController<PerformanceData>? _performanceController;
|
||||
static StreamController<CrashReport>? _crashController;
|
||||
static StreamController<UserBehavior>? _behaviorController;
|
||||
|
||||
/// 初始化监控
|
||||
static Future<void> initialize({
|
||||
bool enablePerformanceMonitoring = true,
|
||||
bool enableCrashReporting = true,
|
||||
bool enableBehaviorTracking = true,
|
||||
String? userId,
|
||||
Map<String, dynamic>? customProperties,
|
||||
}) async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
await _channel.invokeMethod('initialize', {
|
||||
'enablePerformanceMonitoring': enablePerformanceMonitoring,
|
||||
'enableCrashReporting': enableCrashReporting,
|
||||
'enableBehaviorTracking': enableBehaviorTracking,
|
||||
'userId': userId,
|
||||
'customProperties': customProperties,
|
||||
});
|
||||
|
||||
_setupEventChannels();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// 设置用户ID
|
||||
static Future<void> setUserId(String userId) async {
|
||||
await _channel.invokeMethod('setUserId', {'userId': userId});
|
||||
}
|
||||
|
||||
/// 设置自定义属性
|
||||
static Future<void> setCustomProperty(String key, dynamic value) async {
|
||||
await _channel.invokeMethod('setCustomProperty', {
|
||||
'key': key,
|
||||
'value': value,
|
||||
});
|
||||
}
|
||||
|
||||
/// 记录自定义事件
|
||||
static Future<void> recordEvent(String eventName, Map<String, dynamic>? parameters) async {
|
||||
await _channel.invokeMethod('recordEvent', {
|
||||
'eventName': eventName,
|
||||
'parameters': parameters,
|
||||
});
|
||||
}
|
||||
|
||||
/// 记录页面访问
|
||||
static Future<void> recordPageView(String pageName) async {
|
||||
await _channel.invokeMethod('recordPageView', {'pageName': pageName});
|
||||
}
|
||||
|
||||
/// 记录网络请求
|
||||
static Future<void> recordNetworkRequest({
|
||||
required String url,
|
||||
required String method,
|
||||
required int statusCode,
|
||||
required int responseTime,
|
||||
int? requestSize,
|
||||
int? responseSize,
|
||||
}) async {
|
||||
await _channel.invokeMethod('recordNetworkRequest', {
|
||||
'url': url,
|
||||
'method': method,
|
||||
'statusCode': statusCode,
|
||||
'responseTime': responseTime,
|
||||
'requestSize': requestSize,
|
||||
'responseSize': responseSize,
|
||||
});
|
||||
}
|
||||
|
||||
/// 手动记录异常
|
||||
static Future<void> recordException({
|
||||
required String name,
|
||||
required String reason,
|
||||
String? stackTrace,
|
||||
Map<String, dynamic>? customData,
|
||||
}) async {
|
||||
await _channel.invokeMethod('recordException', {
|
||||
'name': name,
|
||||
'reason': reason,
|
||||
'stackTrace': stackTrace,
|
||||
'customData': customData,
|
||||
});
|
||||
}
|
||||
|
||||
/// 获取性能数据流
|
||||
static Stream<PerformanceData> get performanceStream {
|
||||
_performanceController ??= StreamController<PerformanceData>.broadcast();
|
||||
return _performanceController!.stream;
|
||||
}
|
||||
|
||||
/// 获取崩溃报告流
|
||||
static Stream<CrashReport> get crashStream {
|
||||
_crashController ??= StreamController<CrashReport>.broadcast();
|
||||
return _crashController!.stream;
|
||||
}
|
||||
|
||||
/// 获取用户行为流
|
||||
static Stream<UserBehavior> get behaviorStream {
|
||||
_behaviorController ??= StreamController<UserBehavior>.broadcast();
|
||||
return _behaviorController!.stream;
|
||||
}
|
||||
|
||||
/// 强制上报数据
|
||||
static Future<void> flush() async {
|
||||
await _channel.invokeMethod('flush');
|
||||
}
|
||||
|
||||
/// 设置事件通道
|
||||
static void _setupEventChannels() {
|
||||
const performanceChannel = EventChannel('kit_app_monitor/performance');
|
||||
const crashChannel = EventChannel('kit_app_monitor/crash');
|
||||
const behaviorChannel = EventChannel('kit_app_monitor/behavior');
|
||||
|
||||
performanceChannel.receiveBroadcastStream().listen((data) {
|
||||
final performanceData = PerformanceData.fromJson(data);
|
||||
_performanceController?.add(performanceData);
|
||||
});
|
||||
|
||||
crashChannel.receiveBroadcastStream().listen((data) {
|
||||
final crashReport = CrashReport.fromJson(data);
|
||||
_crashController?.add(crashReport);
|
||||
});
|
||||
|
||||
behaviorChannel.receiveBroadcastStream().listen((data) {
|
||||
final userBehavior = UserBehavior.fromJson(data);
|
||||
_behaviorController?.add(userBehavior);
|
||||
});
|
||||
}
|
||||
|
||||
/// 清理资源
|
||||
static void dispose() {
|
||||
_performanceController?.close();
|
||||
_crashController?.close();
|
||||
_behaviorController?.close();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 数据模型 (`src/models/`)
|
||||
|
||||
##### 性能数据模型
|
||||
```dart
|
||||
@JsonSerializable()
|
||||
class PerformanceData {
|
||||
final double cpuUsage;
|
||||
final int memoryUsage;
|
||||
final int totalMemory;
|
||||
final double fps;
|
||||
final int networkLatency;
|
||||
final DateTime timestamp;
|
||||
final String? pageName;
|
||||
|
||||
const PerformanceData({
|
||||
required this.cpuUsage,
|
||||
required this.memoryUsage,
|
||||
required this.totalMemory,
|
||||
required this.fps,
|
||||
required this.networkLatency,
|
||||
required this.timestamp,
|
||||
this.pageName,
|
||||
});
|
||||
|
||||
factory PerformanceData.fromJson(Map<String, dynamic> json) =>
|
||||
_$PerformanceDataFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$PerformanceDataToJson(this);
|
||||
|
||||
double get memoryUsagePercentage => (memoryUsage / totalMemory) * 100;
|
||||
|
||||
bool get isHighCpuUsage => cpuUsage > 80.0;
|
||||
|
||||
bool get isHighMemoryUsage => memoryUsagePercentage > 90.0;
|
||||
|
||||
bool get isLowFps => fps < 30.0;
|
||||
}
|
||||
```
|
||||
|
||||
##### 崩溃报告模型
|
||||
```dart
|
||||
@JsonSerializable()
|
||||
class CrashReport {
|
||||
final String crashId;
|
||||
final String type;
|
||||
final String message;
|
||||
final String stackTrace;
|
||||
final DateTime timestamp;
|
||||
final String appVersion;
|
||||
final String deviceModel;
|
||||
final String osVersion;
|
||||
final Map<String, dynamic> customData;
|
||||
final String? userId;
|
||||
|
||||
const CrashReport({
|
||||
required this.crashId,
|
||||
required this.type,
|
||||
required this.message,
|
||||
required this.stackTrace,
|
||||
required this.timestamp,
|
||||
required this.appVersion,
|
||||
required this.deviceModel,
|
||||
required this.osVersion,
|
||||
required this.customData,
|
||||
this.userId,
|
||||
});
|
||||
|
||||
factory CrashReport.fromJson(Map<String, dynamic> json) =>
|
||||
_$CrashReportFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CrashReportToJson(this);
|
||||
|
||||
bool get isFatal => type.toLowerCase().contains('fatal');
|
||||
|
||||
bool get isNativeCrash => type.toLowerCase().contains('native');
|
||||
}
|
||||
```
|
||||
|
||||
##### 用户行为模型
|
||||
```dart
|
||||
@JsonSerializable()
|
||||
class UserBehavior {
|
||||
final String eventType;
|
||||
final String? pageName;
|
||||
final String? elementName;
|
||||
final Map<String, dynamic> parameters;
|
||||
final DateTime timestamp;
|
||||
final String? userId;
|
||||
final String sessionId;
|
||||
|
||||
const UserBehavior({
|
||||
required this.eventType,
|
||||
this.pageName,
|
||||
this.elementName,
|
||||
required this.parameters,
|
||||
required this.timestamp,
|
||||
this.userId,
|
||||
required this.sessionId,
|
||||
});
|
||||
|
||||
factory UserBehavior.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserBehaviorFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserBehaviorToJson(this);
|
||||
|
||||
bool get isPageView => eventType == 'page_view';
|
||||
|
||||
bool get isButtonClick => eventType == 'button_click';
|
||||
|
||||
bool get isUserAction => ['click', 'tap', 'swipe', 'scroll'].contains(eventType);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Android端实现
|
||||
|
||||
#### 主插件类 (`KitAppMonitorPlugin.kt`)
|
||||
```kotlin
|
||||
class KitAppMonitorPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
||||
private lateinit var channel: MethodChannel
|
||||
private lateinit var context: Context
|
||||
private var activity: Activity? = null
|
||||
|
||||
private var performanceMonitor: PerformanceMonitor? = null
|
||||
private var crashHandler: CrashHandler? = null
|
||||
private var behaviorTracker: BehaviorTracker? = null
|
||||
|
||||
private var performanceEventChannel: EventChannel? = null
|
||||
private var crashEventChannel: EventChannel? = null
|
||||
private var behaviorEventChannel: EventChannel? = null
|
||||
|
||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(binding.binaryMessenger, "kit_app_monitor")
|
||||
context = binding.applicationContext
|
||||
channel.setMethodCallHandler(this)
|
||||
|
||||
// 设置事件通道
|
||||
setupEventChannels(binding.binaryMessenger)
|
||||
}
|
||||
|
||||
private fun setupEventChannels(messenger: BinaryMessenger) {
|
||||
performanceEventChannel = EventChannel(messenger, "kit_app_monitor/performance")
|
||||
crashEventChannel = EventChannel(messenger, "kit_app_monitor/crash")
|
||||
behaviorEventChannel = EventChannel(messenger, "kit_app_monitor/behavior")
|
||||
}
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
||||
when (call.method) {
|
||||
"initialize" -> initialize(call, result)
|
||||
"setUserId" -> setUserId(call, result)
|
||||
"setCustomProperty" -> setCustomProperty(call, result)
|
||||
"recordEvent" -> recordEvent(call, result)
|
||||
"recordPageView" -> recordPageView(call, result)
|
||||
"recordNetworkRequest" -> recordNetworkRequest(call, result)
|
||||
"recordException" -> recordException(call, result)
|
||||
"flush" -> flush(result)
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialize(call: MethodCall, result: Result) {
|
||||
val enablePerformanceMonitoring = call.argument<Boolean>("enablePerformanceMonitoring") ?: true
|
||||
val enableCrashReporting = call.argument<Boolean>("enableCrashReporting") ?: true
|
||||
val enableBehaviorTracking = call.argument<Boolean>("enableBehaviorTracking") ?: true
|
||||
val userId = call.argument<String>("userId")
|
||||
val customProperties = call.argument<Map<String, Any>>("customProperties")
|
||||
|
||||
try {
|
||||
if (enablePerformanceMonitoring) {
|
||||
performanceMonitor = PerformanceMonitor(context)
|
||||
performanceMonitor?.initialize(performanceEventChannel)
|
||||
}
|
||||
|
||||
if (enableCrashReporting) {
|
||||
crashHandler = CrashHandler(context)
|
||||
crashHandler?.initialize(crashEventChannel)
|
||||
}
|
||||
|
||||
if (enableBehaviorTracking) {
|
||||
behaviorTracker = BehaviorTracker(context)
|
||||
behaviorTracker?.initialize(behaviorEventChannel)
|
||||
}
|
||||
|
||||
userId?.let { setUserId(it) }
|
||||
customProperties?.forEach { (key, value) ->
|
||||
setCustomProperty(key, value)
|
||||
}
|
||||
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
result.error("INITIALIZATION_FAILED", e.message, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordEvent(call: MethodCall, result: Result) {
|
||||
val eventName = call.argument<String>("eventName")
|
||||
val parameters = call.argument<Map<String, Any>>("parameters")
|
||||
|
||||
behaviorTracker?.recordEvent(eventName ?: "", parameters ?: emptyMap())
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
private fun recordPageView(call: MethodCall, result: Result) {
|
||||
val pageName = call.argument<String>("pageName")
|
||||
behaviorTracker?.recordPageView(pageName ?: "")
|
||||
result.success(true)
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
performanceMonitor?.setActivity(activity)
|
||||
behaviorTracker?.setActivity(activity)
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
activity = null
|
||||
performanceMonitor?.setActivity(null)
|
||||
behaviorTracker?.setActivity(null)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 性能监控器 (`PerformanceMonitor.kt`)
|
||||
```kotlin
|
||||
class PerformanceMonitor(private val context: Context) {
|
||||
private var eventSink: EventChannel.EventSink? = null
|
||||
private var monitoringHandler: Handler? = null
|
||||
private var activity: Activity? = null
|
||||
private val updateInterval = 5000L // 5秒更新一次
|
||||
|
||||
fun initialize(eventChannel: EventChannel?) {
|
||||
eventChannel?.setStreamHandler(object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||
eventSink = events
|
||||
startMonitoring()
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
eventSink = null
|
||||
stopMonitoring()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun setActivity(activity: Activity?) {
|
||||
this.activity = activity
|
||||
}
|
||||
|
||||
private fun startMonitoring() {
|
||||
monitoringHandler = Handler(Looper.getMainLooper())
|
||||
monitoringHandler?.post(monitoringRunnable)
|
||||
}
|
||||
|
||||
private fun stopMonitoring() {
|
||||
monitoringHandler?.removeCallbacks(monitoringRunnable)
|
||||
monitoringHandler = null
|
||||
}
|
||||
|
||||
private val monitoringRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
collectPerformanceData()
|
||||
monitoringHandler?.postDelayed(this, updateInterval)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectPerformanceData() {
|
||||
try {
|
||||
val cpuUsage = getCpuUsage()
|
||||
val memoryInfo = getMemoryInfo()
|
||||
val fps = getFps()
|
||||
val networkLatency = getNetworkLatency()
|
||||
|
||||
val performanceData = mapOf(
|
||||
"cpuUsage" to cpuUsage,
|
||||
"memoryUsage" to memoryInfo.first,
|
||||
"totalMemory" to memoryInfo.second,
|
||||
"fps" to fps,
|
||||
"networkLatency" to networkLatency,
|
||||
"timestamp" to System.currentTimeMillis(),
|
||||
"pageName" to getCurrentPageName()
|
||||
)
|
||||
|
||||
eventSink?.success(performanceData)
|
||||
} catch (e: Exception) {
|
||||
eventSink?.error("PERFORMANCE_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCpuUsage(): Double {
|
||||
// 获取CPU使用率的实现
|
||||
return try {
|
||||
val runtime = Runtime.getRuntime()
|
||||
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
|
||||
val maxMemory = runtime.maxMemory()
|
||||
(usedMemory.toDouble() / maxMemory.toDouble()) * 100
|
||||
} catch (e: Exception) {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMemoryInfo(): Pair<Long, Long> {
|
||||
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
val memoryInfo = ActivityManager.MemoryInfo()
|
||||
activityManager.getMemoryInfo(memoryInfo)
|
||||
|
||||
val usedMemory = memoryInfo.totalMem - memoryInfo.availMem
|
||||
return Pair(usedMemory, memoryInfo.totalMem)
|
||||
}
|
||||
|
||||
private fun getFps(): Double {
|
||||
// 获取FPS的实现
|
||||
return activity?.let {
|
||||
// 使用Choreographer或其他方式计算FPS
|
||||
60.0 // 默认返回60FPS
|
||||
} ?: 0.0
|
||||
}
|
||||
|
||||
private fun getNetworkLatency(): Int {
|
||||
// 获取网络延迟的实现
|
||||
return 0 // 默认返回0
|
||||
}
|
||||
|
||||
private fun getCurrentPageName(): String? {
|
||||
return activity?.let {
|
||||
it.javaClass.simpleName
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 崩溃处理器 (`CrashHandler.kt`)
|
||||
```kotlin
|
||||
class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandler {
|
||||
private var eventSink: EventChannel.EventSink? = null
|
||||
private var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null
|
||||
|
||||
fun initialize(eventChannel: EventChannel?) {
|
||||
eventChannel?.setStreamHandler(object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||
eventSink = events
|
||||
setupCrashHandler()
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
eventSink = null
|
||||
restoreDefaultHandler()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupCrashHandler() {
|
||||
defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler(this)
|
||||
}
|
||||
|
||||
private fun restoreDefaultHandler() {
|
||||
defaultExceptionHandler?.let {
|
||||
Thread.setDefaultUncaughtExceptionHandler(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun uncaughtException(thread: Thread, exception: Throwable) {
|
||||
try {
|
||||
val crashReport = createCrashReport(exception)
|
||||
eventSink?.success(crashReport)
|
||||
|
||||
// 保存崩溃信息到本地
|
||||
saveCrashToLocal(crashReport)
|
||||
} catch (e: Exception) {
|
||||
// 处理崩溃报告生成失败的情况
|
||||
} finally {
|
||||
// 调用默认的异常处理器
|
||||
defaultExceptionHandler?.uncaughtException(thread, exception)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCrashReport(exception: Throwable): Map<String, Any> {
|
||||
return mapOf(
|
||||
"crashId" to UUID.randomUUID().toString(),
|
||||
"type" to exception.javaClass.simpleName,
|
||||
"message" to (exception.message ?: ""),
|
||||
"stackTrace" to getStackTraceString(exception),
|
||||
"timestamp" to System.currentTimeMillis(),
|
||||
"appVersion" to getAppVersion(),
|
||||
"deviceModel" to Build.MODEL,
|
||||
"osVersion" to Build.VERSION.RELEASE,
|
||||
"customData" to getCustomData()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStackTraceString(exception: Throwable): String {
|
||||
val stringWriter = StringWriter()
|
||||
val printWriter = PrintWriter(stringWriter)
|
||||
exception.printStackTrace(printWriter)
|
||||
return stringWriter.toString()
|
||||
}
|
||||
|
||||
private fun getAppVersion(): String {
|
||||
return try {
|
||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
packageInfo.versionName ?: "unknown"
|
||||
} catch (e: Exception) {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCustomData(): Map<String, Any> {
|
||||
return mapOf(
|
||||
"freeMemory" to Runtime.getRuntime().freeMemory(),
|
||||
"totalMemory" to Runtime.getRuntime().totalMemory(),
|
||||
"maxMemory" to Runtime.getRuntime().maxMemory()
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveCrashToLocal(crashReport: Map<String, Any>) {
|
||||
// 将崩溃信息保存到本地文件
|
||||
// 用于离线上报
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. iOS端实现
|
||||
|
||||
#### 主插件类 (`KitAppMonitorPlugin.swift`)
|
||||
```swift
|
||||
public class KitAppMonitorPlugin: NSObject, FlutterPlugin {
|
||||
private var performanceMonitor: PerformanceMonitor?
|
||||
private var crashHandler: CrashHandler?
|
||||
private var behaviorTracker: BehaviorTracker?
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "kit_app_monitor",
|
||||
binaryMessenger: registrar.messenger())
|
||||
let instance = KitAppMonitorPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
|
||||
instance.setupEventChannels(with: registrar.messenger())
|
||||
}
|
||||
|
||||
private func setupEventChannels(with messenger: FlutterBinaryMessenger) {
|
||||
let performanceChannel = FlutterEventChannel(name: "kit_app_monitor/performance",
|
||||
binaryMessenger: messenger)
|
||||
let crashChannel = FlutterEventChannel(name: "kit_app_monitor/crash",
|
||||
binaryMessenger: messenger)
|
||||
let behaviorChannel = FlutterEventChannel(name: "kit_app_monitor/behavior",
|
||||
binaryMessenger: messenger)
|
||||
|
||||
performanceMonitor = PerformanceMonitor()
|
||||
crashHandler = CrashHandler()
|
||||
behaviorTracker = BehaviorTracker()
|
||||
|
||||
performanceChannel.setStreamHandler(performanceMonitor)
|
||||
crashChannel.setStreamHandler(crashHandler)
|
||||
behaviorChannel.setStreamHandler(behaviorTracker)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "initialize":
|
||||
initialize(call: call, result: result)
|
||||
case "setUserId":
|
||||
setUserId(call: call, result: result)
|
||||
case "recordEvent":
|
||||
recordEvent(call: call, result: result)
|
||||
case "recordPageView":
|
||||
recordPageView(call: call, result: result)
|
||||
case "flush":
|
||||
flush(result: result)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
private func initialize(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
guard let args = call.arguments as? [String: Any] else {
|
||||
result(FlutterError(code: "INVALID_ARGUMENTS",
|
||||
message: "Invalid arguments",
|
||||
details: nil))
|
||||
return
|
||||
}
|
||||
|
||||
let enablePerformanceMonitoring = args["enablePerformanceMonitoring"] as? Bool ?? true
|
||||
let enableCrashReporting = args["enableCrashReporting"] as? Bool ?? true
|
||||
let enableBehaviorTracking = args["enableBehaviorTracking"] as? Bool ?? true
|
||||
|
||||
if enablePerformanceMonitoring {
|
||||
performanceMonitor?.startMonitoring()
|
||||
}
|
||||
|
||||
if enableCrashReporting {
|
||||
crashHandler?.setupCrashHandler()
|
||||
}
|
||||
|
||||
if enableBehaviorTracking {
|
||||
behaviorTracker?.startTracking()
|
||||
}
|
||||
|
||||
result(true)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础使用
|
||||
```dart
|
||||
class MonitorExample extends StatefulWidget {
|
||||
@override
|
||||
_MonitorExampleState createState() => _MonitorExampleState();
|
||||
}
|
||||
|
||||
class _MonitorExampleState extends State<MonitorExample> {
|
||||
StreamSubscription<PerformanceData>? _performanceSubscription;
|
||||
StreamSubscription<CrashReport>? _crashSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeMonitoring();
|
||||
}
|
||||
|
||||
Future<void> _initializeMonitoring() async {
|
||||
// 初始化监控
|
||||
await KitAppMonitor.initialize(
|
||||
enablePerformanceMonitoring: true,
|
||||
enableCrashReporting: true,
|
||||
enableBehaviorTracking: true,
|
||||
userId: 'user_123',
|
||||
customProperties: {
|
||||
'app_version': '1.0.0',
|
||||
'build_number': '100',
|
||||
},
|
||||
);
|
||||
|
||||
// 监听性能数据
|
||||
_performanceSubscription = KitAppMonitor.performanceStream.listen((data) {
|
||||
if (data.isHighCpuUsage || data.isHighMemoryUsage || data.isLowFps) {
|
||||
_handlePerformanceIssue(data);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听崩溃报告
|
||||
_crashSubscription = KitAppMonitor.crashStream.listen((crash) {
|
||||
_handleCrashReport(crash);
|
||||
});
|
||||
|
||||
// 记录应用启动事件
|
||||
await KitAppMonitor.recordEvent('app_start', {
|
||||
'launch_time': DateTime.now().millisecondsSinceEpoch,
|
||||
'is_cold_start': true,
|
||||
});
|
||||
}
|
||||
|
||||
void _handlePerformanceIssue(PerformanceData data) {
|
||||
// 处理性能问题
|
||||
print('Performance issue detected: '
|
||||
'CPU: ${data.cpuUsage}%, '
|
||||
'Memory: ${data.memoryUsagePercentage}%, '
|
||||
'FPS: ${data.fps}');
|
||||
|
||||
// 记录性能问题事件
|
||||
KitAppMonitor.recordEvent('performance_issue', {
|
||||
'cpu_usage': data.cpuUsage,
|
||||
'memory_usage': data.memoryUsagePercentage,
|
||||
'fps': data.fps,
|
||||
'page_name': data.pageName,
|
||||
});
|
||||
}
|
||||
|
||||
void _handleCrashReport(CrashReport crash) {
|
||||
// 处理崩溃报告
|
||||
print('Crash detected: ${crash.type} - ${crash.message}');
|
||||
|
||||
// 可以在这里实现自定义的崩溃处理逻辑
|
||||
// 例如发送到自定义的崩溃收集服务
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Monitor Example')),
|
||||
body: Column(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () => _simulateHighCpuUsage(),
|
||||
child: Text('Simulate High CPU'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => _simulateMemoryLeak(),
|
||||
child: Text('Simulate Memory Issue'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => _simulateCrash(),
|
||||
child: Text('Simulate Crash'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => _recordCustomEvent(),
|
||||
child: Text('Record Custom Event'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _simulateHighCpuUsage() {
|
||||
// 模拟高CPU使用
|
||||
Timer.periodic(Duration(milliseconds: 1), (timer) {
|
||||
// 执行密集计算
|
||||
for (int i = 0; i < 1000000; i++) {
|
||||
math.sqrt(i);
|
||||
}
|
||||
|
||||
if (timer.tick > 1000) {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _simulateMemoryLeak() {
|
||||
// 模拟内存泄漏
|
||||
List<List<int>> memoryHog = [];
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
memoryHog.add(List.filled(1000000, i));
|
||||
}
|
||||
}
|
||||
|
||||
void _simulateCrash() {
|
||||
// 模拟崩溃
|
||||
throw Exception('Simulated crash for testing');
|
||||
}
|
||||
|
||||
void _recordCustomEvent() {
|
||||
KitAppMonitor.recordEvent('button_clicked', {
|
||||
'button_name': 'custom_event_button',
|
||||
'timestamp': DateTime.now().millisecondsSinceEpoch,
|
||||
'user_action': 'tap',
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_performanceSubscription?.cancel();
|
||||
_crashSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 高级监控配置
|
||||
```dart
|
||||
class AdvancedMonitorConfig {
|
||||
static Future<void> setupAdvancedMonitoring() async {
|
||||
// 初始化高级监控配置
|
||||
await KitAppMonitor.initialize(
|
||||
enablePerformanceMonitoring: true,
|
||||
enableCrashReporting: true,
|
||||
enableBehaviorTracking: true,
|
||||
userId: await _getUserId(),
|
||||
customProperties: await _getCustomProperties(),
|
||||
);
|
||||
|
||||
// 设置性能监控阈值
|
||||
_setupPerformanceThresholds();
|
||||
|
||||
// 设置自动页面跟踪
|
||||
_setupAutomaticPageTracking();
|
||||
|
||||
// 设置网络监控
|
||||
_setupNetworkMonitoring();
|
||||
}
|
||||
|
||||
static void _setupPerformanceThresholds() {
|
||||
KitAppMonitor.performanceStream.listen((data) {
|
||||
// CPU使用率过高
|
||||
if (data.cpuUsage > 80) {
|
||||
KitAppMonitor.recordEvent('high_cpu_usage', {
|
||||
'cpu_usage': data.cpuUsage,
|
||||
'page_name': data.pageName,
|
||||
'severity': 'warning',
|
||||
});
|
||||
}
|
||||
|
||||
// 内存使用率过高
|
||||
if (data.memoryUsagePercentage > 90) {
|
||||
KitAppMonitor.recordEvent('high_memory_usage', {
|
||||
'memory_usage': data.memoryUsagePercentage,
|
||||
'total_memory': data.totalMemory,
|
||||
'severity': 'critical',
|
||||
});
|
||||
}
|
||||
|
||||
// FPS过低
|
||||
if (data.fps < 30) {
|
||||
KitAppMonitor.recordEvent('low_fps', {
|
||||
'fps': data.fps,
|
||||
'page_name': data.pageName,
|
||||
'severity': 'warning',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void _setupAutomaticPageTracking() {
|
||||
// 可以与路由系统集成,自动记录页面访问
|
||||
// 这里是一个简化的示例
|
||||
NavigatorObserver observer = RouteObserver<PageRoute<dynamic>>();
|
||||
// 实现路由监听,自动调用 KitAppMonitor.recordPageView
|
||||
}
|
||||
|
||||
static void _setupNetworkMonitoring() {
|
||||
// 可以与网络请求库集成,自动记录网络请求
|
||||
// 例如与 Dio 集成
|
||||
// dio.interceptors.add(MonitoringInterceptor());
|
||||
}
|
||||
|
||||
static Future<String> _getUserId() async {
|
||||
// 获取用户ID的逻辑
|
||||
return 'user_123';
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> _getCustomProperties() async {
|
||||
return {
|
||||
'app_version': await _getAppVersion(),
|
||||
'device_model': await _getDeviceModel(),
|
||||
'os_version': await _getOSVersion(),
|
||||
'network_type': await _getNetworkType(),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 数据采集优化
|
||||
- **采样策略**: 根据性能影响调整数据采集频率
|
||||
- **批量上报**: 批量发送监控数据减少网络请求
|
||||
- **本地缓存**: 离线时缓存数据,联网后上报
|
||||
- **数据压缩**: 压缩监控数据减少传输量
|
||||
|
||||
### 内存管理
|
||||
- **及时清理**: 及时清理监控数据和临时对象
|
||||
- **内存监控**: 监控自身内存使用避免影响应用
|
||||
- **弱引用**: 使用弱引用避免内存泄漏
|
||||
- **资源复用**: 复用监控相关对象
|
||||
|
||||
## 安全和隐私
|
||||
|
||||
### 数据安全
|
||||
- **敏感信息过滤**: 过滤用户敏感信息
|
||||
- **数据加密**: 加密存储和传输监控数据
|
||||
- **访问控制**: 严格的数据访问权限控制
|
||||
- **数据脱敏**: 对敏感数据进行脱敏处理
|
||||
|
||||
### 隐私合规
|
||||
- **用户同意**: 明确的数据收集用户同意
|
||||
- **数据最小化**: 仅收集必要的监控数据
|
||||
- **透明度**: 清晰的隐私政策说明
|
||||
- **用户控制**: 用户可控制监控功能开关
|
||||
|
||||
## 总结
|
||||
|
||||
`kit_app_monitor` 模块作为 OneApp 的应用监控工具包,提供了全面的应用性能监控、异常检测和用户行为分析能力。通过原生平台的深度集成,实现了高效、准确的应用监控功能。模块具有良好的性能优化和隐私保护机制,能够帮助开发团队及时发现和解决应用问题,提升用户体验。
|
||||
Reference in New Issue
Block a user