Flutter 与 OpenHarmony 深度集成:实现跨设备传感器数据协同监测系统

引言

在万物互联时代,单一设备的感知能力已无法满足复杂场景需求。例如:

  • 健康监护:手表采集心率,手机分析趋势,平板展示报告;
  • 智能家居:温湿度传感器部署在客厅、卧室、阳台,由中央设备统一调控空调与加湿器;
  • 工业巡检:多个手持终端同步采集振动、光照、噪声数据,实时上传至边缘服务器。

OpenHarmony 的 分布式硬件虚拟化(Distributed Hardware Virtualization) 能力,允许应用调用远程设备的传感器,如同本地硬件一般。而 Flutter 凭借其跨平台 UI 优势,可构建统一的交互界面。

本文将带你从零搭建一个 "分布式环境监测系统",实现:

  • 手机作为主控端;
  • 平板/智能手表作为传感节点;
  • 实时采集 温度、光照、加速度 数据;
  • 通过软总线聚合、可视化,并触发告警。

一、技术原理:分布式硬件如何工作?

OpenHarmony 通过 DeviceManager + HardwareResourceManager 提供分布式硬件访问能力:

复制代码
+------------------+       +------------------+
|   主控设备       |       |   传感节点       |
| (Phone, Flutter) |<----->| (Tablet, Watch)  |
+--------+---------+ DSoftBus +--------+---------+
         |                          |
   [Hardware ResourceManager]  [Local Sensor]
         |                          |
         +--------> 虚拟化 <--------+
                  远程传感器

关键流程:

  1. 主控设备发现可信传感节点;
  2. 申请使用其传感器权限;
  3. 系统在主控端创建 虚拟传感器实例
  4. 应用通过标准 API 读取数据,底层自动通过软总线透传。

✅ 优势:无需关心数据来源,代码逻辑与本地传感器一致。


二、整体架构设计

MethodChannel DSoftBus Flutter UI SensorManagerPlugin HardwareResourceManager Remote Device: Sensor Local Sensor Fallback Chart / Alert UI

模块说明:

  • SensorManagerPlugin:封装原生传感器调用;
  • HardwareResourceManager:OpenHarmony 提供的分布式硬件管理器;
  • 远程/本地双模式:若节点离线,自动降级为本地采集;
  • 实时图表 :使用 fl_chart 展示多设备数据流。

三、原生侧:分布式传感器封装(ArkTS)

1. 权限与配置

json 复制代码
// module.json5
{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.DISTRIBUTED_DATASYNC" },
      { "name": "ohos.permission.GET_SENSORS_INFO" },
      { "name": "ohos.permission.ACCELEROMETER" },
      { "name": "ohos.permission.LIGHT" },
      { "name": "ohos.permission.TEMPERATURE" }
    ]
  }
}

2. 创建 DistributedSensorManager.ets

ts 复制代码
// services/DistributedSensorManager.ets
import sensor from '@ohos.sensor';
import deviceManager from '@ohos.distributedHardware.deviceManager';
import { Callback } from '@ohos.base';

type SensorData = {
  deviceId: string;
  type: 'temperature' | 'light' | 'accelerometer';
  value: any;
  timestamp: number;
};

class DistributedSensorManager {
  private localSensors: Record<string, any> = {};
  private remoteSubscribers: Record<string, Callback<void>> = {};
  private onDataCallback: ((data: SensorData) => void) | null = null;

  // 初始化本地传感器
  initLocalSensors(): void {
    try {
      this.localSensors['temperature'] = sensor.createTemperatureSensor();
      this.localSensors['light'] = sensor.createLightSensor();
      this.localSensors['accelerometer'] = sensor.createAccelerometer();
    } catch (err) {
      console.error('[Sensor] Failed to create local sensors:', err);
    }
  }

  // 订阅本地传感器
  subscribeLocal(type: string, interval: number = 2000): boolean {
    const sensorObj = this.localSensors[type];
    if (!sensorObj) return false;

    sensorObj.on(interval, (data) => {
      if (this.onDataCallback) {
        this.onDataCallback({
          deviceId: 'local',
          type: type as any,
          value: data,
          timestamp: Date.now()
        });
      }
    });
    return true;
  }

  // 发现并订阅远程设备传感器
  async subscribeRemote(deviceId: string, sensorType: string): Promise<boolean> {
    try {
      // 获取远程硬件资源管理器
      const hardwareManager = await deviceManager.createHardwareResourceManager(deviceId);
      
      // 创建虚拟传感器(关键!)
      const remoteSensor = await hardwareManager.createSensor(sensorType);
      
      // 订阅数据
      const callback = (data: any) => {
        if (this.onDataCallback) {
          this.onDataCallback({
            deviceId: deviceId,
            type: sensorType as any,
            value: data,
            timestamp: Date.now()
          });
        }
      };

      remoteSensor.on(2000, callback);
      this.remoteSubscribers[`${deviceId}_${sensorType}`] = callback;

      console.info(`[Sensor] Subscribed to ${sensorType} on ${deviceId}`);
      return true;
    } catch (err) {
      console.error(`[Sensor] Failed to subscribe remote ${sensorType}:`, err);
      return false;
    }
  }

  // 停止所有订阅
  unsubscribeAll(): void {
    Object.values(this.localSensors).forEach(s => s?.off());
    Object.values(this.remoteSubscribers).forEach(cb => cb?.());
    this.remoteSubscribers = {};
  }

  setDataCallback(callback: (data: SensorData) => void): void {
    this.onDataCallback = callback;
  }
}

const sensorManager = new DistributedSensorManager();
export default sensorManager;

3. 暴露给 Flutter(插件层)

ts 复制代码
// plugins/SensorPlugin.ets
import sensorManager from '../services/DistributedSensorManager';
import { MethodChannel, EventChannel } from '@flutter/engine';

const METHOD_CHANNEL = 'com.example.flutter/sensor/method';
const EVENT_CHANNEL = 'com.example.flutter/sensor/event';

export class SensorPlugin {
  private eventSink: any = null;

  init() {
    sensorManager.initLocalSensors();
    sensorManager.setDataCallback((data) => {
      if (this.eventSink) {
        this.eventSink.success(data);
      }
    });

    const methodChannel = new MethodChannel(METHOD_CHANNEL);
    methodChannel.setMethodCallHandler(this.handleMethod.bind(this));

    const eventChannel = new EventChannel(EVENT_CHANNEL);
    eventChannel.setStreamHandler({
      onListen: (_, sink) => this.eventSink = sink,
      onCancel: () => this.eventSink = null
    });
  }

  private async handleMethod(call: any): Promise<any> {
    switch (call.method) {
      case 'subscribeLocal':
        const success = sensorManager.subscribeLocal(call.arguments['type']);
        return { success };

      case 'subscribeRemote':
        const { deviceId, sensorType } = call.arguments;
        const remoteSuccess = await sensorManager.subscribeRemote(deviceId, sensorType);
        return { success: remoteSuccess };

      case 'unsubscribeAll':
        sensorManager.unsubscribeAll();
        return { success: true };
    }
    throw new Error('Unknown method');
  }
}

EntryAbility.ets 中初始化:

ts 复制代码
new SensorPlugin().init();

四、Flutter 侧:数据接收与可视化

1. 定义数据模型

dart 复制代码
// lib/models/sensor_data.dart
class SensorData {
  final String deviceId;
  final String type; // 'temperature', 'light', 'accelerometer'
  final dynamic value;
  final int timestamp;

  SensorData({
    required this.deviceId,
    required this.type,
    required this.value,
    required this.timestamp,
  });

  factory SensorData.fromJson(Map<dynamic, dynamic> json) {
    return SensorData(
      deviceId: json['deviceId'] as String,
      type: json['type'] as String,
      value: json['value'],
      timestamp: json['timestamp'] as int,
    );
  }
}

2. 封装服务

dart 复制代码
// lib/services/sensor_service.dart
import 'package:flutter/services.dart';
import '../models/sensor_data.dart';

class SensorService {
  static const _method = MethodChannel('com.example.flutter/sensor/method');
  static const _event = EventChannel('com.example.flutter/sensor/event');

  static Future<void> subscribeLocal(String type) async {
    await _method.invokeMethod('subscribeLocal', {'type': type});
  }

  static Future<void> subscribeRemote(String deviceId, String sensorType) async {
    await _method.invokeMethod('subscribeRemote', {
      'deviceId': deviceId,
      'sensorType': sensorType,
    });
  }

  static Future<void> unsubscribeAll() async {
    await _method.invokeMethod('unsubscribeAll');
  }

  static Stream<SensorData> onData() async* {
    await for (final event in _event.receiveBroadcastStream()) {
      yield SensorData.fromJson(event as Map);
    }
  }
}

3. 使用 Riverpod 管理多设备数据流

dart 复制代码
// lib/providers/sensor_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/sensor_service.dart';
import '../models/sensor_data.dart';

final sensorDataProvider = StateNotifierProvider<SensorDataManager, Map<String, List<SensorData>>>((ref) {
  return SensorDataManager();
});

class SensorDataManager extends StateNotifier<Map<String, List<SensorData>>> {
  late StreamSubscription _subscription;

  SensorDataManager() : super({}) {
    _subscription = SensorService.onData().listen((data) {
      final deviceId = data.deviceId;
      final list = state.putIfAbsent(deviceId, () => []);
      // 保留最近 100 条
      if (list.length >= 100) list.removeAt(0);
      list.add(data);
      state = {...state};
    });
  }

  Future<void> startMonitoring(List<String> remoteDevices) async {
    // 订阅本地
    await SensorService.subscribeLocal('temperature');
    await SensorService.subscribeLocal('light');
    await SensorService.subscribeLocal('accelerometer');

    // 订阅远程
    for (final deviceId in remoteDevices) {
      await SensorService.subscribeRemote(deviceId, 'temperature');
      await SensorService.subscribeRemote(deviceId, 'light');
    }
  }

  @override
  void dispose() {
    _subscription.cancel();
    SensorService.unsubscribeAll();
    super.dispose();
  }
}

4. 构建实时图表界面

dart 复制代码
// lib/screens/monitor_screen.dart
import 'package:fl_chart/fl_chart.dart';

class MonitorScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final allData = ref.watch(sensorDataProvider);

    return Scaffold(
      appBar: AppBar(title: Text('分布式环境监测')),
      body: ListView(
        children: [
          _buildChart(allData, 'temperature', '温度 (°C)'),
          _buildChart(allData, 'light', '光照 (lux)'),
        ],
      ),
    );
  }

  Widget _buildChart(Map<String, List<SensorData>> dataMap, String type, String title) {
    final lines = <LineChartBarData>[];
    int colorIndex = 0;
    final colors = [Colors.blue, Colors.green, Colors.red, Colors.purple];

    dataMap.forEach((deviceId, dataList) {
      final filtered = dataList.where((d) => d.type == type).toList();
      if (filtered.isEmpty) return;

      final points = filtered.asMap().entries.map((entry) {
        final index = entry.key.toDouble();
        final value = _extractValue(entry.value.value, type);
        return FlSpot(index, value);
      }).toList();

      lines.add(LineChartBarData(
        spots: points,
        color: colors[colorIndex % colors.length],
        barWidth: 2,
        isCurved: true,
        dotData: FlDotData(show: false),
      ));

      colorIndex++;
    });

    return Container(
      height: 200,
      margin: EdgeInsets.all(16),
      child: LineChart(
        LineChartData(
          lineBarsData: lines,
          titlesData: FlTitlesData(
            bottomTitles: AxisTitles(side: left, showTitles: false),
            leftTitles: AxisTitles(side: left, title: TextTitle(text: title)),
          ),
          gridData: FlGridData(show: true),
        ),
      ),
    );
  }

  double _extractValue(dynamic value, String type) {
    if (type == 'temperature') {
      return (value as Map?)?['temperature']?.toDouble() ?? 0.0;
    } else if (type == 'light') {
      return (value as Map?)?['lightIntensity']?.toDouble() ?? 0.0;
    }
    return 0.0;
  }
}

五、关键问题与解决方案

问题 解决方案
远程设备无对应传感器 调用前先查询设备能力(deviceManager.getCapability
软总线断开导致数据丢失 原生侧监听连接状态,自动重连或降级本地
高频数据导致 UI 卡顿 Dart 侧使用 debounce 或采样(如每秒最多1条)
多设备数据时间不同步 使用 timestamp 字段对齐,而非本地时间

六、测试场景

  1. 手机 + 平板均安装应用;
  2. 手机启动监测,自动发现平板;
  3. 平板提供温度/光照数据,手机本地提供加速度;
  4. 遮挡平板光线,观察图表实时下降;
  5. 断开平板 Wi-Fi,系统自动切换为仅显示手机数据。

七、总结

本文实现了 Flutter 应用调用 OpenHarmony 分布式传感器的完整链路,展示了:

  • 如何通过 HardwareResourceManager 虚拟化远程硬件;
  • 如何设计 本地/远程双模兼容 的健壮架构;
  • 如何在 Flutter 中高效渲染 多源实时数据流

这不仅适用于环境监测,还可扩展至:

  • 远程医疗:调用家属手表的心率数据;
  • 智慧农业:聚合田间多个土壤湿度传感器;
  • AR 协同:多设备共享空间定位信息。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

相关推荐
棉晗榜5 小时前
WPF输入框里面加文本提示
wpf
嗝o゚5 小时前
鸿蒙跨端协同与Flutter结合的远程办公轻应用开发
flutter·华为·wpf
豫狮恒5 小时前
OpenHarmony Flutter 分布式权限管理:跨设备可信访问与权限协同方案
分布式·flutter·wpf·openharmony
小白|5 小时前
Flutter 与 OpenHarmony 深度整合:构建跨设备统一剪贴板同步系统
flutter·wpf
帅气马战的账号15 小时前
OpenHarmony 分布式数据同步:基于 ArkTS 与轻量级协议的全场景实践
flutter
He BianGu5 小时前
【笔记】在WPF中如何使用ContentPresenter 与 Generic.xaml 设置数据默认 DataTemplate
windows·笔记·wpf
ujainu6 小时前
Flutter+DevEco Studio实战:简易天气查询工具开发指南
flutter·deveco studio
小白|6 小时前
Flutter 与 OpenHarmony 深度融合:实现分布式文件共享与跨设备协同编辑系统
分布式·flutter·wpf
帅气马战的账号16 小时前
OpenHarmony与Flutter深度融合:分布式跨端开发全栈实践指南
flutter