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 协同:多设备共享空间定位信息。

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

相关推荐
bugcome_com5 分钟前
C# 字符串拼接全面指南
c#·.net·wpf
程序员老刘7 分钟前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
奋斗的小青年!!3 小时前
OpenHarmony Flutter 拖拽排序组件性能优化与跨平台适配指南
flutter·harmonyos·鸿蒙
小雨下雨的雨5 小时前
Flutter 框架跨平台鸿蒙开发 —— Stack 控件之三维层叠艺术
flutter·华为·harmonyos
行者966 小时前
OpenHarmony平台Flutter手风琴菜单组件的跨平台适配实践
flutter·harmonyos·鸿蒙
小雨下雨的雨7 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
cn_mengbei7 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
cn_mengbei7 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!8 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙
Coder_Boy_8 小时前
Flutter基础介绍-跨平台移动应用开发框架
spring boot·flutter