React Native智能闹钟:传感器数据优化唤醒时机
概述
传统闹钟在固定时间唤醒,可能打断深度睡眠,导致睡眠惯性(Sleep Inertia)——昏昏沉沉、反应迟钝的状态。
智能闹钟通过分析传感器数据:
- 检测睡眠阶段(浅睡/深睡/REM)
- 在最佳唤醒时间(浅睡期)唤醒
- 减少睡眠惯性,提升起床体验
技术原理
睡眠阶段
code
睡眠周期(约90分钟):
浅睡期 (N1/N2) ████░░░░████░░░░████ ← 最佳唤醒时机
深睡期 (N3) ████████████████░░░░ ← 避免唤醒
REM期 ░░░░░░░░░░░░░░░░░░░░░ ← 可唤醒
Code collapsed
检测原理
| 传感器 | 检测指标 | 睡眠阶段特征 |
|---|---|---|
| 加速度计 | 身体活动 | 浅睡期活动较多 |
| 陀螺仪 | 翻身频率 | REM期翻身少 |
| 光线传感器 | 环境亮度 | 深睡期对光不敏感 |
| 麦克风 | 呼吸声 | 深睡期呼吸规律 |
| 屏幕触摸 | 交互频率 | 浅睡期更可能交互 |
环境设置
安装依赖
code
# Expo传感器模块
expo install expo-sensors
expo install expo-device
expo install expo-av
expo install expo-notifications
expo install expo-background-fetch
expo install expo-task-manager
# React Native传感器
npm install react-native-sensors
npm install react-native-push-notification
Code collapsed
权限配置
code
// app.json
{
"expo": {
"ios": {
"infoPlist": {
"NSMotionUsageDescription": "用于监测睡眠时的身体活动",
"NSMicrophoneUsageDescription": "用于分析睡眠质量(可选)"
}
},
"android": {
"permissions": [
"BODY_SENSORS",
"RECORD_AUDIO",
"USE_FULL_SCREEN_INTENT",
"WAKE_LOCK"
]
}
}
}
Code collapsed
传感器数据采集
加速度计数据
code
// hooks/useAccelerometer.ts
import { useEffect, useState, useRef } from 'react';
import { Accelerometer } from 'expo-sensors';
export interface AccelerometerData {
x: number;
y: number;
z: number;
timestamp: number;
}
export function useAccelerometer(interval: number = 1000) {
const [data, setData] = useState<AccelerometerData[]>([]);
const [isRecording, setIsRecording] = useState(false);
const subscriptionRef = useRef<any>();
const startRecording = () => {
setIsRecording(true);
setData([]);
subscriptionRef.current = Accelerometer.addListener(({ x, y, z }) => {
const timestamp = Date.now();
setData(prev => [...prev, { x, y, z, timestamp }]);
});
Accelerometer.setUpdateInterval(interval);
};
const stopRecording = () => {
setIsRecording(false);
subscriptionRef.current?.remove();
};
// 清理
useEffect(() => {
return () => {
subscriptionRef.current?.remove();
};
}, []);
return {
data,
isRecording,
startRecording,
stopRecording
};
}
Code collapsed
睡眠监测服务
code
// services/SleepMonitorService.ts
import { Accelerometer } from 'expo-sensors';
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const SLEEP_MONITOR_TASK = 'SLEEP_MONITOR_TASK';
interface SleepData {
timestamp: number;
movement: number; // 运动强度
position: string; // 体位
light: number; // 环境亮度
}
class SleepMonitorService {
private sleepData: SleepData[] = [];
private isMonitoring = false;
private updateInterval: number = 60000; // 每分钟采样
async initialize() {
// 注册后台任务
await TaskManager.defineTask(SLEEP_MONITOR_TASK, this.handleSleepTask.bind(this));
// 注册周期性任务
await BackgroundFetch.registerTaskAsync(SLEEP_MONITOR_TASK, {
minimumInterval: this.updateInterval,
stopOnTerminate: false,
startOnBoot: true,
});
}
async startMonitoring() {
this.isMonitoring = true;
this.sleepData = [];
// 启动传感器监听
Accelerometer.addListener(({ x, y, z }) => {
if (!this.isMonitoring) return;
// 计算运动强度
const movement = Math.sqrt(x*x + y*y + z*z);
// 确定体位
const position = this.detectPosition(x, y, z);
// 记录数据
this.sleepData.push({
timestamp: Date.now(),
movement,
position,
light: 0 // 需要光线传感器
});
});
Accelerometer.setUpdateInterval(this.updateInterval);
}
stopMonitoring() {
this.isMonitoring = false;
}
private detectPosition(x: number, y: number, z: number): string {
// 简化体位检测
if (z > 0.8) return 'back';
if (z < -0.8) return 'front';
if (x > 0.8) return 'left';
if (x < -0.8) return 'right';
return 'upright';
}
private async handleSleepTask() {
// 后台任务处理
const now = new Date();
console.log(`睡眠监测: ${now.toLocaleTimeString()}`);
return BackgroundFetch.BackgroundFetchResult.NewData;
}
analyzeSleepStages(): Array<{time: Date, stage: string}> {
/**
* 分析睡眠阶段
*
* 算法:
* 1. 低活动 + 稳定体位 = 深睡期
* 2. 中等活动 + 频繁翻身 = REM期
* 3. 高活动 = 浅睡期
*/
const stages = [];
const windowSize = 5; // 5分钟窗口
for (let i = 0; i < this.sleepData.length; i += windowSize) {
const window = this.sleepData.slice(i, i + windowSize);
if (window.length === 0) continue;
// 计算平均活动
const avgMovement = window.reduce((sum, d) => sum + d.movement, 0) / window.length;
// 计算翻身次数
const positionChanges = window.filter((d, idx) =>
idx > 0 && d.position !== window[idx - 1].position
).length;
// 判断睡眠阶段
let stage = 'unknown';
if (avgMovement < 0.1 && positionChanges < 2) {
stage = 'deep_sleep'; // 深睡
} else if (avgMovement < 0.2 && positionChanges < 3) {
stage = 'light_sleep'; // 浅睡
} else if (avgMovement >= 0.2) {
stage = 'rem'; // REM或浅睡
}
stages.push({
time: new Date(window[0].timestamp),
stage
});
}
return stages;
}
getSleepData(): SleepData[] {
return this.sleepData;
}
}
export const sleepMonitorService = new SleepMonitorService();
Code collapsed
智能唤醒算法
唤醒窗口计算
code
// services/SmartAlarmService.ts
interface AlarmConfig {
targetTime: Date; // 目标唤醒时间
windowMinutes: number; // 唤醒窗口大小(默认30分钟)
requireQuiet: boolean; // 是否需要安静环境
}
interface WakeUpTime {
time: Date;
confidence: number; // 唤醒质量评分 (0-1)
reason: string; // 选择此时间的理由
}
class SmartAlarmService {
/**
* 计算最佳唤醒时间
*/
calculateOptimalWakeUp(
sleepStages: Array<{time: Date, stage: string}>,
config: AlarmConfig
): WakeUpTime {
const windowStart = new Date(config.targetTime);
windowStart.setMinutes(windowStart.getMinutes() - config.windowMinutes);
// 在唤醒窗口内寻找浅睡期
const candidates = sleepStages.filter(stage => {
const stageTime = new Date(stage.time);
return stageTime >= windowStart &&
stageTime <= config.targetTime &&
(stage.stage === 'light_sleep' || stage.stage === 'rem');
});
if (candidates.length === 0) {
// 没有找到浅睡期,使用目标时间
return {
time: config.targetTime,
confidence: 0.3,
reason: '无可选浅睡期,使用预设时间'
};
}
// 选择最接近目标的浅睡期
const optimal = candidates.reduce((best, current) => {
const currentDiff = Math.abs(
new Date(current.time).getTime() - config.targetTime.getTime()
);
const bestDiff = Math.abs(
new Date(best.time).getTime() - config.targetTime.getTime()
);
return currentDiff < bestDiff ? current : best;
});
// 计算置信度
const timeDiff = Math.abs(
new Date(optimal.time).getTime() - config.targetTime.getTime()
);
const confidence = Math.max(0, 1 - timeDiff / (config.windowMinutes * 60000));
return {
time: new Date(optimal.time),
confidence,
reason: '检测到浅睡期'
};
}
/**
* 计算需要的前置唤醒时间
* 考虑用户入睡时间
*/
calculateWakeWindow(
targetTime: Date,
sleepCycleLength: number = 90 // 睡眠周期长度(分钟)
): {start: Date, end: Date} {
const end = targetTime;
const start = new Date(targetTime);
start.setMinutes(start.getMinutes() - 30); // 30分钟窗口
return { start, end };
}
}
export const smartAlarmService = new SmartAlarmService();
Code collapsed
闹钟调度
code
// hooks/useSmartAlarm.ts
import { useState, useEffect } from 'react';
import * as Notifications from 'expo-notifications';
import { Platform } from 'react-native';
import { sleepMonitorService } from '../services/SleepMonitorService';
import { smartAlarmService } from '../services/SmartAlarmService';
export function useSmartAlarm() {
const [alarmTime, setAlarmTime] = useState<Date | null>(null);
const [optimalWakeTime, setOptimalWakeTime] = useState<Date | null>(null);
const [isMonitoring, setIsMonitoring] = useState(false);
// 配置通知
useEffect(() => {
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
}, []);
const setAlarm = async (time: Date) => {
setAlarmTime(time);
// 开始睡眠监测
await sleepMonitorService.startMonitoring();
setIsMonitoring(true);
// 计算唤醒窗口
const window = smartAlarmService.calculateWakeWindow(time);
// 设置初始通知
await scheduleNotification(time, '智能闹钟已设置');
// 启动动态调整任务
scheduleDynamicAlarm(time, window);
};
const scheduleDynamicAlarm = async (targetTime: Date, window: {start: Date, end: Date}) => {
// 每分钟检查并调整唤醒时间
const checkInterval = setInterval(async () => {
const now = new Date();
// 如果已过窗口结束时间
if (now > window.end) {
clearInterval(checkInterval);
await sleepMonitorService.stopMonitoring();
setIsMonitoring(false);
return;
}
// 分析当前睡眠阶段
const sleepStages = sleepMonitorService.analyzeSleepStages();
// 计算最佳唤醒时间
const optimal = smartAlarmService.calculateOptimalWakeUp(sleepStages, {
targetTime,
windowMinutes: 30,
requireQuiet: true
});
// 如果找到更好的时间,更新闹钟
if (optimal.confidence > 0.7 && optimal.time !== optimalWakeTime) {
setOptimalWakeTime(optimal.time);
await scheduleNotification(optimal.time, '智能唤醒', optimal.reason);
}
}, 60000); // 每分钟检查
};
const cancelAlarm = async () => {
await Notifications.cancelAllScheduledNotificationsAsync();
await sleepMonitorService.stopMonitoring();
setIsMonitoring(false);
setAlarmTime(null);
setOptimalWakeTime(null);
};
return {
alarmTime,
optimalWakeTime,
isMonitoring,
setAlarm,
cancelAlarm
};
}
// 调度通知
async function scheduleNotification(time: Date, title: string, body?: string) {
await Notifications.scheduleNotificationAsync({
content: {
title,
body: body || time.toLocaleTimeString('zh-CN'),
sound: true,
priority: Notifications.AndroidNotificationPriority.HIGH,
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.DATE,
date: time,
},
});
}
Code collapsed
UI组件
闹钟设置界面
code
// components/SmartAlarmSettings.tsx
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Platform } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
import Slider from '@react-native-community/slider';
import { useSmartAlarm } from '../hooks/useSmartAlarm';
export function SmartAlarmSettings() {
const { alarmTime, optimalWakeTime, isMonitoring, setAlarm, cancelAlarm } = useSmartAlarm();
const [selectedTime, setSelectedTime] = useState(new Date());
const [showPicker, setShowPicker] = useState(false);
const [windowSize, setWindowSize] = useState(30); // 唤醒窗口大小
const handleSetAlarm = async () => {
await setAlarm(selectedTime);
};
return (
<View style={styles.container}>
<Text style={styles.title}>智能闹钟</Text>
{/* 时间选择器 */}
{Platform.OS === 'ios' ? (
<DateTimePicker
value={selectedTime}
mode="time"
display="default"
onChange={(event, date) => setSelectedTime(date || selectedTime)}
style={styles.picker}
/>
) : (
<>
<TouchableOpacity onPress={() => setShowPicker(true)}>
<Text style={styles.timeDisplay}>
{selectedTime.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})}
</Text>
</TouchableOpacity>
{showPicker && (
<DateTimePicker
value={selectedTime}
mode="time"
display="default"
onChange={(event, date) => {
setShowPicker(false);
if (date) setSelectedTime(date);
}}
/>
)}
</>
)}
{/* 唤醒窗口设置 */}
<View style={styles.sliderContainer}>
<Text style={styles.label}>
唤醒窗口: {windowSize} 分钟
</Text>
<Slider
style={styles.slider}
minimumValue={10}
maximumValue={60}
step={5}
value={windowSize}
onValueChange={setWindowSize}
/>
<Text style={styles.hint}>
在目标时间前{windowSize}分钟内寻找最佳唤醒时机
</Text>
</View>
{/* 当前状态 */}
{alarmTime && (
<View style={styles.statusContainer}>
<Text style={styles.statusText}>
目标时间: {alarmTime.toLocaleTimeString('zh-CN')}
</Text>
{optimalWakeTime && (
<Text style={styles.optimalText}>
最佳唤醒: {optimalWakeTime.toLocaleTimeString('zh-CN')}
</Text>
)}
<Text style={styles.monitoringText}>
状态: {isMonitoring ? '监测中...' : '等待'}
</Text>
</View>
)}
{/* 操作按钮 */}
<View style={styles.buttons}>
<TouchableOpacity
style={[styles.button, styles.setButton]}
onPress={handleSetAlarm}
>
<Text style={styles.buttonText}>设置闹钟</Text>
</TouchableOpacity>
{alarmTime && (
<TouchableOpacity
style={[styles.button, styles.cancelButton]}
onPress={cancelAlarm}
>
<Text style={styles.buttonText}>取消</Text>
</TouchableOpacity>
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 30,
},
picker: {
width: '100%',
height: 200,
},
timeDisplay: {
fontSize: 48,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 20,
},
sliderContainer: {
backgroundColor: '#fff',
borderRadius: 10,
padding: 15,
marginVertical: 20,
},
label: {
fontSize: 16,
fontWeight: 'bold',
},
slider: {
width: '100%',
height: 40,
},
hint: {
fontSize: 12,
color: '#666',
marginTop: 5,
},
statusContainer: {
backgroundColor: '#fff',
borderRadius: 10,
padding: 15,
marginVertical: 20,
},
statusText: {
fontSize: 16,
},
optimalText: {
fontSize: 16,
color: '#4CAF50',
marginTop: 5,
},
monitoringText: {
fontSize: 14,
color: '#666',
marginTop: 10,
},
buttons: {
flexDirection: 'row',
justifyContent: 'space-around',
},
button: {
paddingHorizontal: 40,
paddingVertical: 15,
borderRadius: 25,
},
setButton: {
backgroundColor: '#4CAF50',
},
cancelButton: {
backgroundColor: '#f44336',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
});
Code collapsed
关键要点
- 传感器数据驱动决策:加速度计+陀螺仪
- 睡眠周期检测:浅睡期最佳唤醒
- 动态调整唤醒时间:实时优化
- 后台任务支持:持续监测
- 用户可配置窗口:平衡精确性vs便利性
常见问题
精度如何?
研究显示智能闹钟减少约60-80%的睡眠惯性。但个体差异大:
- 年轻人效果更好
- 规律作息更准确
- 需要佩戴设备更精确
电池消耗?
优化策略:
- 降低采样频率(每分钟而非每秒)
- 使用后台任务而非持续监听
- 屏幕关闭时降低处理频率
iOS限制?
iOS后台限制严格:
- 使用BackgroundFetch
- 请求"始终允许"运动权限
- 考虑使用HealthKit集成
参考资料
- 睡眠科学研究
- 睡眠惯性研究
- Expo传感器文档
- 通知调度文档
发布日期:2026年3月8日 最后更新:2026年3月8日