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

React Native后台定时任务并播放音频的问题解决

React Native后台定时任务无法播放音频问题

我开发了一款React Native应用,要求每15分钟拉取数据并播放音频,无论应用处于前台还是后台。但目前遇到问题:应用进入后台后,15分钟后无法播放音频,仅在重新打开应用时才会播放。以下是我使用的代码:

import React, {useState, useEffect, useCallback} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  FlatList,
  StatusBar,
  ListRenderItem,
} from 'react-native';
import {Header, Colors} from 'react-native/Libraries/NewAppScreen';
import BackgroundFetch from 'react-native-background-fetch';
import {Button} from '@rneui/base';
import Sound from 'react-native-sound';

// Define the type for event
interface Event {
  taskId: string;
  timestamp: string;
}
const getTime = () => {
  let currentDate = new Date();

  // Get the current hours, minutes, and seconds
  let hours = currentDate.getHours();
  let minutes = currentDate.getMinutes();
  let seconds = currentDate.getSeconds();

  // Format the time as HH:MM:SS
  let currentTime = `${hours.toString().padStart(2, '0')}:${minutes
    .toString()
    .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;

  return currentTime;
};

const App: React.FC = () => {
  const [events, setEvents] = useState<Event[]>([]);
  const [data, setData] = useState<any>(getTime());

  useEffect(() => {
    initBackgroundFetch();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const addEvent = useCallback(async (taskId: string): Promise<void> => {
    console.log('Hello from a background task', getTime());
    setEvents(prevEvents => [
      ...prevEvents,
      {
        taskId: taskId,
        timestamp: getTime(),
      },
    ]);
  }, []);

  const initBackgroundFetch = async () => {
    const onEvent = async (taskId: string) => {
      console.log('started task time', getTime());
      await addEvent(taskId);
      BackgroundFetch.finish(taskId);
      console.log('finished task time', getTime());
      triggerBackgroundFetch();
    };

    const onTimeout = async (taskId: string) => {
      console.warn('[BackgroundFetch] TIMEOUT task: ', taskId);
      BackgroundFetch.finish(taskId);
    };

    let status = await BackgroundFetch.configure(
      {
        minimumFetchInterval: 15, // <-- minutes (15 is minimum allowed)
        forceAlarmManager: false, // Set true to bypass JobScheduler.
        stopOnTerminate: false,
        startOnBoot: true,
      },
      onEvent,
      onTimeout,
    );

    console.log('[BackgroundFetch] configure status: ', status);
  };

  const renderItem: ListRenderItem<Event> = ({item}) => (
    <Text>
      [{item.taskId}]: {item.timestamp}
    </Text>
  );
  const triggerBackgroundFetch = async () => {
    setData(Math.random() * 50);
    var whoosh = new Sound('sound.mp3', Sound.MAIN_BUNDLE, error => {
      if (error) {
        console.log('failed to load the sound', error);
        return;
      }

      // Play the sound with an onEnd callback
      whoosh.play(success => {
        if (success) {
          console.log('successfully finished playing');
        } else {
          console.log('playback failed due to audio decoding errors');
        }
      });
    });
  };

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView>
        <ScrollView
          contentInsetAdjustmentBehavior="automatic"
          style={styles.scrollView}>
          <Header />
          <View style={styles.body}>
            <View style={styles.sectionContainer}>
              <Text style={styles.sectionTitle}>BackgroundFetch Demo</Text>
              <View>
                <Text>random:{data}</Text>
              </View>
            </View>
          </View>
          <Button onPress={triggerBackgroundFetch}>Call API</Button>
        </ScrollView>
        <View style={styles.sectionContainer}>
          <FlatList
            data={events}
            renderItem={renderItem}
            keyExtractor={item => item.timestamp}
          />
        </View>
      </SafeAreaView>
    </>
  );
};

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
  body: {
    backgroundColor: Colors.white,
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
    color: Colors.dark,
  },
});

export default App;

问题原因与解决办法

1. 后台音频权限缺失

iOS和Android系统默认限制后台应用的音频播放能力,必须显式声明权限:

  • iOS:在Info.plist中添加UIBackgroundModes数组,包含audio字段,告诉系统应用需要后台音频权限。
  • Android:在AndroidManifest.xml中添加FOREGROUND_SERVICE权限,同时将音频播放逻辑绑定到前台服务,避免系统因资源回收杀死进程。

2. BackgroundFetch的资源限制

react-native-background-fetch的后台任务执行窗口极短(通常仅数秒),且系统会限制后台任务的资源访问,包括音频硬件。单纯用它触发音频播放不可靠,需要结合专门的后台音频框架。

3. 音频会话未激活

后台状态下,音频会话默认处于暂停状态,需要在播放前强制激活:

  • iOS:通过原生AVAudioSession设置playback类别并主动激活会话。
  • Android:使用AudioManager请求音频焦点,确保播放权限。

4. 代码修改建议

替换为专业后台音频框架

推荐使用react-native-track-player,它原生支持后台音频播放,适配双平台权限:

  1. 安装依赖:
npm install react-native-track-player
  1. 按官方文档完成iOS/Android的后台权限配置(包括Info.plist和AndroidManifest.xml的修改)
  2. 修改播放逻辑:
import TrackPlayer from 'react-native-track-player';

// 初始化播放器
useEffect(() => {
  TrackPlayer.setupPlayer().then(() => {
    TrackPlayer.updateOptions({
      stopWithApp: false,
      capabilities: [],
      compactCapabilities: [],
    });
  });
  initBackgroundFetch();
}, []);

const triggerBackgroundFetch = async () => {
  setData(Math.random() * 50);
  // 重置队列并添加音频播放
  await TrackPlayer.reset();
  await TrackPlayer.add({
    id: 'background-sound',
    url: require('./sound.mp3'),
    title: 'Background Audio',
  });
  await TrackPlayer.play();
};

调整BackgroundFetch执行顺序

确保音频播放逻辑在任务结束前完成,避免任务被系统终止:

const onEvent = async (taskId: string) => {
  console.log('started task time', getTime());
  await addEvent(taskId);
  // 先执行音频播放,再标记任务完成
  await triggerBackgroundFetch();
  BackgroundFetch.finish(taskId);
  console.log('finished task time', getTime());
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 14:20:16