You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo/React Native 技术问询:如何在应用后台状态下播放定时提醒音(闹钟应用场景)

我之前在Expo托管项目里做过类似的闹钟应用,正好碰到过同样的问题!下面这个基于Expo官方工具的方案应该能解决你的需求,完全不需要eject:

核心解决方案:Expo Task Manager + Notifications + Audio 组合

Expo默认会暂停后台JS线程,但原生级别的定时任务和通知不受此限制,我们可以通过这三个官方库实现可靠的后台闹钟提醒:

1. 先配置项目依赖与权限

首先安装所需库(Expo项目无需额外原生配置):

expo install expo-task-manager expo-notifications expo-audio

然后在app.json中添加权限和资源配置:

{
  "expo": {
    "ios": {
      "infoPlist": {
        "UIBackgroundModes": ["audio", "remote-notification"]
      }
    },
    "android": {
      "permissions": ["POST_NOTIFICATIONS", "WAKE_LOCK"],
      "useNextNotificationsApi": true
    },
    "plugins": [
      [
        "expo-notifications",
        {
          "icon": "./assets/notification-icon.png",
          "color": "#ffffff",
          "sounds": ["./assets/alarm-sound.wav"]
        }
      ]
    ]
  }
}

2. 注册后台任务处理闹钟触发

创建一个原生级别的后台任务,确保到达时间后即使应用在后台/未运行,也能播放提醒音:

import * as TaskManager from 'expo-task-manager';
import * as Notifications from 'expo-notifications';
import * as Audio from 'expo-audio';

// 定义唯一的任务标识
const ALARM_TASK = 'ALARM_TRIGGER_TASK';

// 编写后台任务逻辑
TaskManager.defineTask(ALARM_TASK, async ({ data, error }) => {
  if (error) {
    console.error('闹钟任务执行失败:', error);
    return;
  }
  
  // 配置音频模式,确保后台也能播放
  await Audio.setAudioModeAsync({
    staysActiveInBackground: true,
    playsInSilentModeIOS: true,
    shouldDuckAndroid: false,
  });

  // 播放提醒音(循环直到用户关闭)
  const sound = new Audio.Sound();
  try {
    await sound.loadAsync(require('./assets/alarm-sound.wav'));
    await sound.playAsync();
    sound.setIsLoopingAsync(true);
  } catch (err) {
    console.error('播放提醒音失败:', err);
  }
});

// 初始化任务与权限
async function initAlarmService() {
  // 请求通知权限
  const notificationPerm = await Notifications.getPermissionsAsync();
  if (!notificationPerm.granted) {
    await Notifications.requestPermissionsAsync();
  }
  // 注册后台任务
  await TaskManager.registerTaskAsync(ALARM_TASK);
}

// 在App组件初始化时调用
useEffect(() => {
  initAlarmService();
}, []);

3. 调度定时闹钟

替换你原来的setTimeout逻辑,用原生定时触发任务和通知:

async function scheduleAlarm(ringsInMilliseconds) {
  // 1. 调度系统通知(确保用户能看到提醒,同时触发声音)
  await Notifications.scheduleNotificationAsync({
    content: {
      title: '闹钟提醒',
      body: '该起床啦!',
      sound: 'alarm-sound.wav', // 对应app.json配置的声音文件
      data: { type: 'alarm' },
    },
    trigger: {
      seconds: ringsInMilliseconds / 1000,
      repeats: false, // 重复闹钟可设为true
    },
  });

  // 2. 调度后台任务(双重保障,防止通知被用户忽略)
  await TaskManager.scheduleTaskAsync(ALARM_TASK, {
    delay: ringsInMilliseconds,
    period: null,
  });
}

4. 处理通知点击跳转页面

当用户点击通知时,自动打开闹钟响铃页面:

useEffect(() => {
  const notificationListener = Notifications.addNotificationResponseReceivedListener(response => {
    const data = response.notification.request.content.data;
    if (data.type === 'alarm') {
      navigation.push('AlarmRings');
    }
  });

  return () => notificationListener.remove();
}, [navigation]);

5. 在AlarmRings页面控制声音

响铃页面负责停止提醒音:

import { useRef } from 'react';
import { View, Text, Button } from 'react-native';
import * as Audio from 'expo-audio';

function AlarmRings({ navigation }) {
  const soundRef = useRef(null);

  useEffect(() => {
    // 检查是否已有声音在播放,统一控制
    async function checkAndControlSound() {
      const { playing } = await Audio.getAudioModeAsync();
      if (playing) {
        // 获取当前播放的声音实例(或直接初始化新的)
        soundRef.current = new Audio.Sound();
        await soundRef.current.loadAsync(require('./assets/alarm-sound.wav'));
      }
    }
    checkAndControlSound();

    // 组件卸载时停止声音
    return () => {
      if (soundRef.current) {
        soundRef.current.stopAsync();
        soundRef.current.unloadAsync();
      }
    };
  }, []);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text style={{ fontSize: 24, marginBottom: 20 }}>闹钟响了!</Text>
      <Button title="关闭闹钟" onPress={() => navigation.goBack()} />
    </View>
  );
}

关键注意事项

  • iOS限制:后台任务执行时间有限,但系统通知的声音是系统级别的,即使应用未运行也会播放;我们的后台任务用于保障声音循环播放。
  • Android厂商限制:部分国产手机有严格的后台杀进程策略,可能需要引导用户将应用加入后台白名单。
  • 不要用setTimeout:JS线程在后台会被暂停,只有原生级别的任务才能可靠触发。

内容的提问来源于stack exchange,提问作者janaabf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 21:57:32