1. 项目背景与需求分析
在开发"幸运大转盘"这类互动营销应用时,抽奖次数管理是最核心的业务逻辑之一。不同于简单的计数器实现,它需要处理以下几个关键问题:
- 数据持久化:用户关闭应用后再次打开时,需要保留剩余的抽奖次数
- 并发控制:防止用户快速连续点击导致的多次抽奖
- 业务规则:如每日重置次数、分享增加次数等运营需求
- 状态同步:在多个页面间共享当前的剩余次数
在Flutter for OpenHarmony的混合开发环境下,这个问题变得更加有趣。OpenHarmony的分布式能力理论上允许我们在多设备间同步抽奖状态,而Flutter的跨平台特性又要求我们的代码能在不同平台上保持一致性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 状态管理方案对比
对于抽奖次数这种全局状态,Flutter社区有多种解决方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| InheritedWidget | 官方原生支持 | 更新效率低 | 简单应用 |
| Provider | 轻量易用 | 功能较基础 | 中小型应用 |
| Riverpod | 类型安全 | 学习曲线陡 | 大型应用 |
| Bloc | 分离业务逻辑 | 模板代码多 | 复杂状态 |
| GetX | 功能全面 | 耦合度高 | 快速开发 |
考虑到我们的应用规模不大但需要良好的可维护性,我选择了Riverpod作为状态管理方案。它不仅能完美解决我们的需求,还能为后续功能扩展预留空间。
2.2 数据持久化方案
在OpenHarmony环境下,我们有几种存储选择:
- Preferences:适合简单键值对
- Hive:轻量级NoSQL数据库
- SQLite:关系型数据库
- 分布式数据对象:OpenHarmony特有功能
对于抽奖次数这种简单数据,Preferences完全够用。但为了展示OpenHarmony的特性,我决定同时实现本地Preferences存储和分布式同步两种方案。
3. 核心实现步骤
3.1 创建抽奖服务单例
dart复制class LotteryService {
static final LotteryService _instance = LotteryService._internal();
factory LotteryService() => _instance;
LotteryService._internal();
final _prefs = SharedPreferences.getInstance();
int _remainingChances = 0;
Future<void> init() async {
final prefs = await _prefs;
_remainingChances = prefs.getInt('remaining_chances') ?? 5;
}
int get remainingChances => _remainingChances;
Future<bool> consumeChance() async {
if (_remainingChances <= 0) return false;
_remainingChances--;
final prefs = await _prefs;
return prefs.setInt('remaining_chances', _remainingChances);
}
Future<bool> addChances(int count) async {
_remainingChances += count;
final prefs = await _prefs;
return prefs.setInt('remaining_chances', _remainingChances);
}
}
这个单例类封装了所有抽奖次数相关的业务逻辑,注意几个关键点:
- 使用工厂构造函数确保全局唯一实例
- 初始化时从持久化存储加载数据
- 每次修改都立即持久化
- 提供了原子性的增减操作
3.2 集成Riverpod状态管理
dart复制final lotteryServiceProvider = FutureProvider<LotteryService>((ref) async {
final service = LotteryService();
await service.init();
return service;
});
final remainingChancesProvider = Provider<int>((ref) {
final service = ref.watch(lotteryServiceProvider).value;
return service?.remainingChances ?? 0;
});
这里创建了两个Provider:
- 异步初始化LotteryService
- 派生出一个只读的剩余次数状态
3.3 实现OpenHarmony分布式能力
dart复制import 'package:ohos_distributed_module/ohos_distributed_module.dart';
class DistributedLotteryService {
final DistributedDataManager _manager;
final LotteryService _localService;
DistributedLotteryService(this._localService)
: _manager = DistributedDataManager();
Future<void> syncChances() async {
final deviceList = await _manager.getTrustedDeviceList();
if (deviceList.isEmpty) return;
final distributedObject = await _manager.createDistributedObject(
'lottery_chances',
{'chances': _localService.remainingChances}
);
distributedObject.onDataChange((data) {
_localService.addChances(data['chances'] - _localService.remainingChances);
});
}
}
这个扩展类实现了:
- 获取可信设备列表
- 创建分布式数据对象
- 监听数据变化并同步到本地
4. 业务逻辑与UI集成
4.1 抽奖按钮状态管理
dart复制class LotteryButton extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final chances = ref.watch(remainingChancesProvider);
final service = ref.watch(lotteryServiceProvider);
return ElevatedButton(
onPressed: chances > 0 && service.hasValue
? () async {
final success = await service.value!.consumeChance();
if (success) {
// 执行抽奖逻辑
}
}
: null,
child: Text(chances > 0 ? '立即抽奖' : '次数已用完'),
);
}
}
这个按钮组件会:
- 自动响应剩余次数变化
- 在次数为0时禁用
- 点击时原子性减少次数
4.2 每日重置逻辑
dart复制class DailyResetHandler {
static Future<void> checkReset() async {
final prefs = await SharedPreferences.getInstance();
final lastReset = prefs.getString('last_reset_date');
final today = DateFormat('yyyy-MM-dd').format(DateTime.now());
if (lastReset != today) {
final service = LotteryService();
await service.addChances(5); // 每日赠送5次
await prefs.setString('last_reset_date', today);
}
}
}
在应用启动时调用这个方法,可以实现每日重置抽奖次数。
5. 性能优化与调试技巧
5.1 减少持久化操作频率
频繁写入持久化存储会影响性能,我们可以实现一个缓冲机制:
dart复制class BufferedLotteryService extends LotteryService {
Timer? _saveTimer;
@override
Future<bool> consumeChance() async {
_remainingChances--;
_scheduleSave();
return true;
}
void _scheduleSave() {
_saveTimer?.cancel();
_saveTimer = Timer(const Duration(seconds: 1), () async {
final prefs = await _prefs;
await prefs.setInt('remaining_chances', _remainingChances);
});
}
}
这样可以将多次连续操作合并为一次保存。
5.2 调试分布式同步
在开发OpenHarmony分布式功能时,可以使用以下调试技巧:
- 使用
hdc工具查看分布式数据对象状态 - 在设备间ping测试网络连通性
- 使用
hilog命令查看系统日志
bash复制hilog | grep DistributedData
5.3 内存泄漏预防
在使用单例模式时,要注意避免内存泄漏:
- 不要在单例中持有Context/Widget引用
- 及时取消分布式数据监听
- 在应用退出时清理资源
dart复制class AppLifecycleObserver with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.detached) {
DistributedLotteryService._instance?.dispose();
}
}
}
6. 扩展功能实现
6.1 分享增加抽奖次数
dart复制class ShareHandler {
static Future<void> handleShare() async {
final service = LotteryService();
final success = await service.addChances(1);
if (success) {
// 调用分享API
await Share.share('快来参与抽奖活动!');
}
}
}
6.2 签到连续奖励
dart复制class CheckInSystem {
static Future<void> checkIn() async {
final prefs = await SharedPreferences.getInstance();
final streak = prefs.getInt('check_in_streak') ?? 0;
final reward = 1 + (streak ~/ 7); // 每周额外奖励
final service = LotteryService();
await service.addChances(reward);
await prefs.setInt('check_in_streak', streak + 1);
}
}
6.3 付费购买次数
dart复制class PurchaseHandler {
static Future<bool> purchaseChances(int count) async {
// 调用支付SDK
final success = await PaymentService.purchase('chances_$count');
if (success) {
final service = LotteryService();
return service.addChances(count);
}
return false;
}
}
7. 测试策略与质量保障
7.1 单元测试要点
dart复制void main() {
test('抽奖次数消费测试', () async {
final service = LotteryService();
await service.init();
expect(service.remainingChances, 5);
await service.consumeChance();
expect(service.remainingChances, 4);
});
test('次数不足时消费测试', () async {
final service = LotteryService();
await service.init();
for (var i = 0; i < 5; i++) {
await service.consumeChance();
}
expect(await service.consumeChance(), false);
});
}
7.2 集成测试场景
- 测试应用重启后次数保持
- 测试跨设备同步延迟
- 测试并发操作时的数据一致性
- 测试网络中断时的降级处理
7.3 性能测试指标
- 持久化操作延迟
- 分布式同步耗时
- 内存占用情况
- 冷启动初始化时间
8. 实际开发中的经验教训
在实现这个功能的过程中,我总结了几点值得分享的经验:
-
分布式数据的一致性:OpenHarmony的分布式能力虽然强大,但在网络不稳定时会出现同步延迟。我们最终添加了本地缓存+远程同步的双层架构,并提供了手动刷新按钮。
-
单例的生命周期管理:最初没有正确处理单例的销毁,导致在热重载时状态异常。后来通过结合WidgetsBindingObserver来管理生命周期解决了这个问题。
-
Riverpod的异步初始化:刚开始直接在使用时await初始化,导致UI闪烁。改用FutureProvider后,UI可以优雅地处理加载状态。
-
测试覆盖率的重要性:抽奖次数作为核心业务逻辑,我们为其编写了详细的单元测试和集成测试,这在后续添加每日重置功能时节省了大量调试时间。
-
性能与用户体验的平衡:最初每次修改都立即持久化的设计在低端设备上会出现卡顿。引入缓冲机制后,既保证了数据安全又提升了操作流畅度。
