状态共享新范式:在 Flutter + OpenHarmony 应用中实现跨框架状态同步(Riverpod + ArkState)
作者 :L、218
发布平台 :CSDN
发布时间 :2025年12月8日
关键词:Flutter、OpenHarmony、状态管理、Riverpod、ArkState、跨框架通信、数据同步、Reactive Programming、统一状态层
引言
随着 OpenHarmony 生态的快速发展,越来越多企业开始采用"混合架构"开发策略:
- 核心页面使用 Flutter 实现高性能 UI 与跨端复用
- 容器层基于 OpenHarmony 原生能力构建服务卡片、分布式任务等
但在实际开发中,我们面临一个棘手问题:
❓ 当用户在 Flutter 商品页点击"加入购物车",OpenHarmony 的底部 Tab 如何实时更新 Badge 数量?
换句话说:
🔥 Flutter 与 ArkTS 如何共享同一份应用状态?
本文将由 L、218 带你从零构建一个 跨框架统一状态管理层 ,通过 FFI + 共享内存池 + 发布订阅模型,实现 Flutter(Riverpod)与 OpenHarmony(ArkTS)之间的双向响应式数据同步。
这不是简单的消息传递,而是真正意义上的"一处修改,处处响应"。
一、为什么需要跨框架状态同步?
| 场景 | 当前痛点 |
|---|---|
| 混合架构 App | Flutter 负责商品页,ArkTS 负责导航栏 |
| 分布式任务 | 手机上启动下载,手表上显示进度条 |
| 主题切换 | 用户设置深色模式,Flutter 页面未刷新 |
如果每个框架维护自己的状态副本,就会导致:
🔄 数据不一致
💣 内存冗余
🐞 逻辑冲突
我们的目标是:
✅ 构建一个独立于 UI 框架的统一状态中心
✅ 支持 Flutter 与 ArkTS 双向监听与更新
二、技术选型对比
| 方案 | 是否推荐 | 说明 |
|---|---|---|
| WebView + localStorage | ⚠️ 仅限 Web 方案 | 性能差,无法用于原生嵌入 |
| FFI 直接读写内存 | ✅ 核心基础 | 高性能,低延迟 |
| 文件轮询机制 | ❌ 不推荐 | 延迟高,耗电严重 |
| 自定义事件通道 | ✅ 推荐 | 结合 FFI 实现高效通信 |
最终方案:
FFI + 共享内存池 + 发布订阅模型
三、整体架构设计
+----------------------------+
| Flutter (Dart) |
| ┌──────────────────┐ |
| │ Riverpod │<----+--- 监听 state/cart.count
| └──────────────────┘ |
| ↑ |
| ┌──────────────────┐ |
| │ FFI Bridge │<----+--- 读写 shared_memory
| └──────────────────┘ |
+------------↑---------------+
| dlopen / memfd
+------------↓-------------------------+
| Shared Memory Pool (C++) |
| - user.name |
| - cart.count |
| - theme.isDark |
| - download.progress |
+------------↑-------------------------+
| JNI / Native API
+------------↓-------------------------+
| OpenHarmony (ArkTS) |
| ┌──────────────────┐ |
| │ ArkState │<--------------+--- @Watch('cart.count')
| └──────────────────┘ |
| ┌──────────────────┐ |
| │ NDK Bridge │<--------------+--- 调用 nativeModule.getState()
| └──────────────────┘ |
+--------------------------------------+
所有状态变更都通过 C++ 层广播,两端各自触发 UI 刷新。
四、实战步骤:实现"购物车数量"跨框架同步
🧩 功能目标
- 在 Flutter 商品页点击"加入购物车"
- OpenHarmony 底部 Tab 上的 Badge 实时 +1
- 支持反向更新(模拟其他设备同步)
步骤 1:创建共享内存层(C++)
state_pool.h
cpp
#ifndef STATE_POOL_H
#define STATE_POOL_H
#include <map>
#include <string>
#include <functional>
#include <mutex>
typedef std::function<void(const std::string& key, const std::string& value)> StateChangeListener;
class StatePool {
public:
static StatePool& GetInstance() {
static StatePool instance;
return instance;
}
void SetString(const std::string& key, const std::string& value);
std::string GetString(const std::string& key);
int32_t GetInt(const std::string& key);
void SetInt(const std::string& key, int32_t value);
void AddListener(const std::string& key, StateChangeListener listener);
void RemoveListener(const std::string& key);
private:
std::map<std::string, std::string> data_;
std::map<std::string, std::vector<StateChangeListener>> listeners_;
std::mutex mutex_;
};
// C 导出函数(供 FFI 调用)
extern "C" {
void state_set_int(const char* key, int32_t value);
int32_t state_get_int(const char* key);
}
#endif
state_pool.cpp
cpp
#include "state_pool.h"
#include <iostream>
using namespace std;
void StatePool::SetString(const string& key, const string& value) {
lock_guard<mutex> lock(mutex_);
auto old = data_[key];
data_[key] = value;
// 广播变更
if (listeners_.find(key) != listeners_.end()) {
for (auto& cb : listeners_[key]) {
cb(key, value);
}
}
}
int32_t StatePool::GetInt(const string& key) {
lock_guard<mutex> lock(mutex_);
auto it = data_.find(key);
if (it == data_.end()) return 0;
try {
return stoi(it->second);
} catch (...) {
return 0;
}
}
void StatePool::SetInt(const string& key, int32_t value) {
SetString(key, to_string(value));
}
void StatePool::AddListener(const string& key, StateChangeListener listener) {
lock_guard<mutex> lock(mutex_);
listeners_[key].push_back(listener);
}
C 导出函数实现
cpp
void state_set_int(const char* key, int32_t value) {
StatePool::GetInstance().SetInt(string(key), value);
}
int32_t state_get_int(const char* key) {
return StatePool::GetInstance().GetInt(string(key));
}
步骤 2:Flutter 端接入(Riverpod)
添加依赖 pubspec.yaml
yaml
dependencies:
flutter:
sdk: flutter
riverpod: ^2.4.0
ffi: ^2.1.1
创建 FFI 绑定 lib/state_ffi.dart
dart
import 'dart:ffi' as ffi;
typedef SetIntNative = ffi.Void Function(ffi.Pointer<Utf8>, ffi.Int32);
typedef GetIntNative = ffi.Int32 Function(ffi.Pointer<Utf8>);
class NativeStateBridge {
final ffi.Pointer<ffi.DynamicLibrary> _dylib;
final SetIntNative _setState;
final GetIntNative _getState;
NativeStateBridge(String soPath)
: _dylib = ffi.DynamicLibrary.open(soPath),
_setState = _dylib
.lookup<ffi.NativeFunction<SetIntNative>>('state_set_int')
.asFunction(),
_getState = _dylib
.lookup<ffi.NativeFunction<GetIntNative>>('state_get_int')
.asFunction();
void setInt(String key, int value) {
final cKey = ffi.Utf8.toUtf8(key);
_setState(cKey, value);
ffi.free(cKey);
}
int getInt(String key) {
final cKey = ffi.Utf8.toUtf8(key);
final result = _getState(cKey);
ffi.free(cKey);
return result;
}
}
封装为 Riverpod Provider
dart
final stateBridgeProvider = Provider<NativeStateBridge>((ref) {
return NativeStateBridge("libs/libstate_pool.so");
});
final cartCountProvider = StateNotifierProvider<CartNotifier, int>((ref) {
final bridge = ref.watch(stateBridgeProvider);
final initial = bridge.getInt('cart.count');
return CartNotifier(initial, bridge);
});
class CartNotifier extends StateNotifier<int> {
final NativeStateBridge bridge;
CartNotifier(int state, this.bridge) : super(state);
void increment() {
final next = state + 1;
state = next;
bridge.setInt('cart.count', next); // 同步到底层
}
void decrement() {
final next = state - 1;
if (next >= 0) {
state = next;
bridge.setInt('cart.count', next);
}
}
}
步骤 3:OpenHarmony 端接入(ArkTS)
创建 NDK 接口 native_state.cpp
cpp
#include <cstdint>
#include "bridge_util.h"
extern "C" {
int32_t state_get_int(const char* key);
void state_set_int(const char* key, int32_t value);
}
static napi_value GetCartCount(napi_env env, napi_callback_info info) {
int32_t count = state_get_int("cart.count");
napi_value result;
napi_create_int32(env, count, &result);
return result;
}
static napi_value SetCartCount(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int32_t value;
napi_get_value_int32(env, args[0], &value);
state_set_int("cart.count", value);
return nullptr;
}
EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{ "getCartCount", nullptr, GetCartCount, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "setCartCount", nullptr, SetCartCount, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, 2, desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "state",
.nm_priv = nullptr,
.reserved = { 0 }
};
extern "C" __attribute__((constructor)) void RegisterModule() {
napi_register_module(&demoModule);
}
在 ArkTS 中使用
ets
import state from 'libstate.so'; // 加载 native 模块
@Entry
@Component
struct BottomTabs {
@State cartCount: number = 0
private timer: number | undefined = undefined
aboutToAppear(): void {
this.loadInitialState();
this.startPolling(); // 简单轮询(进阶可用 callback)
}
loadInitialState(): void {
this.cartCount = state.getCartCount() ?? 0;
}
startPolling(): void {
this.timer = setInterval(() => {
const remote = state.getCartCount();
if (remote !== this.cartCount) {
this.cartCount = remote;
console.info(`[Sync] Cart count updated: ${remote}`);
}
}, 300);
}
build() {
Row({ space: 50 }) {
TabItem(icon: $r('app.media.home'), text: '首页')
TabItem(icon: $r('app.media.discover'), text: '发现')
// 购物车 Tab(带 Badge)
Column() {
Image($r('app.media.cart'))
.width(24).height(24)
if (this.cartCount > 0) {
Text(this.cartCount.toString())
.fontSize(10).fontColor(Color.White)
.backgroundColor(Color.Red)
.size({ width: 16, height: 16 })
.borderRadius(8)
.position({ x: 18, y: -8 })
}
}.onClick(() => router.pushUrl('/cart'))
TabItem(icon: $r('app.media.profile'), text: '我的')
}
.justifyContent(FlexAlign.SpaceAround)
.height(60)
.padding(5)
.background(Color.White)
}
}
日志验证
INFO: [NDK] state_get_int(cart.count) = 1
INFO: [Sync] Cart count updated: 1
五、进阶方向
| 功能 | 实现方式 |
|---|---|
| 真·响应式监听 | 在 C++ 层暴露 onStateChange(void(*cb)(const char*, const char*)) |
| JSON 对象支持 | 使用 nlohmann/json 存储复杂结构 |
| 持久化存储 | 联动 SQLite 自动落盘 |
| 分布式同步 | 结合 OpenHarmony 分布式数据服务(DistributedDataManager) |
七、结语:状态即真理
在现代前端开发中,UI 是状态的函数。
当我们把"状态"从 UI 框架中剥离出来,变成一个独立的服务层时,我们就获得了前所未有的灵活性:
🔄 Flutter 可以是视图层
🖼️ ArkTS 也可以是视图层
💾 而真正的业务逻辑和数据,始终如一
这不仅是技术方案,更是一种架构哲学。
💬 如果你也正在构建混合架构应用,欢迎关注我,后续将推出《跨端路由系统设计》《统一埋点 SDK 实践》等系列文章。
参考资料
- Riverpod 官网:https://riverpod.dev
- OpenHarmony NDK 开发:https://gitee.com/openharmony/ndk
- ArkTS 状态管理:https://developer.harmonyos.com/cn/docs/documentation/doc-guides-v5/state-management-0000001524315333
- 示例代码仓库:https://github.com/l218/flutter-oh-shared-state
❤️ 欢迎交流
你在项目中是如何处理多框架状态一致性的?有没有更好的解法?
欢迎在评论区分享你的经验!
📌 关注我 @L、218,获取更多 Flutter × OpenHarmony 工程化深度内容,包括:
- 跨端路由系统
- 统一日志埋点
- 分布式任务调度
让我们共同推动中国基础软件生态走向成熟!
版权声明 :本文原创,转载请注明出处及作者。商业转载请联系授权。
作者主页 :https://blog.csdn.net/L218
✅ 点赞 + 收藏 + 转发,让更多人告别"状态不同步"的噩梦!
📌 标签:#Flutter #OpenHarmony #状态管理 #Riverpod #ArkState #跨框架通信 #共享内存 #响应式编程 #L218 #CSDN #2025架构实践