Flutter移动感知插件carp_mobile_sensing的使用

发布于 1周前 作者 zlyuanteng 来自 Flutter

Flutter移动感知插件carp_mobile_sensing的使用

简介

carp_mobile_sensing 是一个用于跨平台(iOS和Android)移动感知的核心Flutter包。它支持通过定义研究协议来收集各种传感器数据。

使用步骤

  1. 添加依赖: 在你的 pubspec.yaml 文件中添加以下依赖:

    dependencies:
      carp_core: ^latest
      carp_mobile_sensing: ^latest
    
  2. 配置应用

    • CAMS依赖于 flutter_local_notifications 插件,因此需要根据其文档进行配置。
    • 对于Android和iOS平台,需要在相应的配置文件中添加权限和接收器。

Android集成

  • 设置最低SDK版本为26,并在 build.gradle 文件中设置 minSdkVersion, compileSdkVersiontargetSdkVersion

  • manifest.xml 中添加必要的权限:

    <!-- 用于活动识别(步数统计) -->
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
    
    <!-- 用于发送和调度通知 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
            android:maxSdkVersion="32" />
    
  • <application> 标签之间添加以下内容以显示计划的通知:

    <!-- 用于调度通知 -->
    <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
    <receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
            <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
            <action android:name="android.intent.action.QUICKBOOT_POWERON" />
            <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
        </intent-filter>
    </receiver>
    

iOS集成

  • Info.plist 文件中添加以下键值对:

    <key>NSMotionUsageDescription</key>
    <string>此应用程序跟踪您的步数</string>
    

示例代码

以下是一个简单的示例,展示了如何使用 carp_mobile_sensing 进行步数、环境光、屏幕事件和电池事件的采样:

import 'package:carp_serializable/carp_serializable.dart';
import 'package:carp_core/carp_core.dart';
import 'package:carp_mobile_sensing/carp_mobile_sensing.dart';
import 'package:flutter/material.dart' hide TimeOfDay;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  CarpMobileSensing.ensureInitialized();
  runApp(const CARPMobileSensingApp());
}

class CARPMobileSensingApp extends StatelessWidget {
  const CARPMobileSensingApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => MaterialApp(
        title: 'CARP Mobile Sensing Demo',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: const ConsolePage(title: 'CARP Mobile Sensing Demo'),
      );
}

class ConsolePage extends StatefulWidget {
  final String title;
  const ConsolePage({super.key, required this.title});
  [@override](/user/override)
  Console createState() => Console();
}

class Console extends State<ConsolePage> {
  String _log = '';

  [@override](/user/override)
  void initState() {
    super.initState();
    Settings().debugLevel = DebugLevel.debug;
    Settings().init().then((_) {
      Sensing().init().then((_) {
        log('Client state : ${SmartPhoneClientManager().state}');
      });
    });
  }

  [@override](/user/override)
  void dispose() {
    Sensing().dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: SingleChildScrollView(
          child: StreamBuilder(
            stream: SmartPhoneClientManager().measurements,
            builder: (context, AsyncSnapshot<Measurement> snapshot) => Text(
                (snapshot.hasData)
                    ? _log += toJsonString(snapshot.data)
                    : _log),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: restart,
          tooltip: '开始/停止研究',
          child: Sensing().isRunning
              ? const Icon(Icons.stop)
              : const Icon(Icons.play_arrow),
        ),
      );

  void log(String msg) => setState(() => _log += '$msg\n');

  void clearLog() => setState(() => _log = '');

  void restart() {
    debug('>> status: ${Sensing().isRunning}');
    Sensing().isRunning ? Sensing().stop() : Sensing().start();
    setState(() {}); // 更新播放/停止图标
  }
}

class Sensing {
  static final Sensing _instance = Sensing._();
  Sensing._() {
    ExecutorFactory().registerTriggerFactory(RemoteTriggerFactory());
  }

  factory Sensing() => _instance;

  Study? study;

  Future<void> init() async {
    final protocol = await LocalStudyProtocolManager().getStudyProtocol('');

    SmartPhoneClientManager().configure().then((_) => SmartPhoneClientManager()
        .addStudyFromProtocol(protocol)
        .then((value) => study = value));

    SmartPhoneClientManager()
        .measurements
        .listen((data) => print(toJsonString(data)));
  }

  bool get isRunning =>
      SmartPhoneClientManager().state == ClientManagerState.started;

  void start() => SmartPhoneClientManager().start();

  void stop() => SmartPhoneClientManager().stop();

  void dispose() => SmartPhoneClientManager().dispose();
}

class LocalStudyProtocolManager implements StudyProtocolManager {
  [@override](/user/override)
  Future<void> initialize() async {}

  [@override](/user/override)
  Future<SmartphoneStudyProtocol> getStudyProtocol(String id) async {
    SmartphoneStudyProtocol protocol = SmartphoneStudyProtocol(
        ownerId: 'AB',
        name: 'Protocol - id: $id',
        dataEndPoint: SQLiteDataEndPoint());

    var phone = Smartphone();
    protocol.addPrimaryDevice(phone);
    protocol.addParticipantRole(ParticipantRole('Participant'));

    protocol.addTaskControl(
      ImmediateTrigger(),
      BackgroundTask(measures: [
        Measure(type: DeviceSamplingPackage.FREE_MEMORY)
          ..overrideSamplingConfiguration = IntervalSamplingConfiguration(
              interval: const Duration(seconds: 10)),
        Measure(type: DeviceSamplingPackage.BATTERY_STATE),
        Measure(type: DeviceSamplingPackage.SCREEN_EVENT),
        Measure(type: SensorSamplingPackage.STEP_COUNT),
        Measure(type: SensorSamplingPackage.AMBIENT_LIGHT)
          ..overrideSamplingConfiguration = PeriodicSamplingConfiguration(
            interval: const Duration(seconds: 20),
            duration: const Duration(seconds: 5),
          ),
      ]),
      phone,
    );

    return protocol;
  }

  [@override](/user/override)
  Future<bool> saveStudyProtocol(String studyId, StudyProtocol protocol) async {
    throw UnimplementedError();
  }
}

更多关于Flutter移动感知插件carp_mobile_sensing的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter移动感知插件carp_mobile_sensing的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter移动感知插件carp_mobile_sensing的示例代码案例。这个插件允许开发者从用户的移动设备上收集各种传感器数据,例如加速度计、陀螺仪、位置信息等。

首先,确保你已经在pubspec.yaml文件中添加了carp_mobile_sensing依赖:

dependencies:
  flutter:
    sdk: flutter
  carp_mobile_sensing: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

以下是一个简单的示例应用,展示了如何使用carp_mobile_sensing插件来收集加速度计数据:

import 'package:flutter/material.dart';
import 'package:carp_core/carp_core.dart';
import 'package:carp_mobile_sensing/carp_mobile_sensing.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late SensingManager _sensingManager;

  @override
  void initState() {
    super.initState();
    // 初始化SensingManager
    _sensingManager = SensingManager()
      ..registerTasks([
        // 注册一个加速度计任务
        AccelerometerTask(
          name: 'accelerometer',
          sampleRate: SampleRate(type: SampleRateType.fixed, value: 5.0), // 每秒5次采样
        ),
      ]);

    // 开始收集数据
    _sensingManager.start();
  }

  @override
  void dispose() {
    // 停止收集数据并清理资源
    _sensingManager.stop();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('CARP Mobile Sensing Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Data collection is running...'),
              ElevatedButton(
                onPressed: () {
                  // 示例:停止并重新开始数据收集
                  _sensingManager.stop();
                  _sensingManager.start();
                },
                child: Text('Restart Data Collection'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// 监听并处理数据
class AccelerometerTask extends Task {
  @override
  List<Probe> get requiredProbes => [
    AccelerometerProbe(
      name: 'accelerometer_probe',
      sampleRate: sampleRate,
    ),
  ];

  @override
  void onData(DataPoint data) {
    if (data is AccelerometerDataPoint) {
      // 处理收集到的加速度计数据
      print('Accelerometer Data: ${data.toJson()}');
    }
  }
}

注意

  1. AccelerometerTask类实际上应该继承自Task并覆盖其方法,但在这个例子中,为了简单起见,直接在MyApp类中注册了加速度计任务。在实际应用中,你可能需要创建自己的任务类来管理不同的数据收集逻辑。
  2. onData方法在这里没有直接在AccelerometerTask中实现,因为carp_mobile_sensing的API设计通常要求你通过ProbeSubscription或类似机制来订阅数据。在这个简化的例子中,我们省略了这部分实现,仅用于展示如何注册任务。
  3. 在实际应用中,你需要根据具体需求来处理数据,例如保存到数据库、发送到服务器等。

由于carp_mobile_sensing的API可能会随着版本更新而变化,建议查阅最新的官方文档以获取最准确的信息和最佳实践。

回到顶部