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

Expo React Native:如何实现后台运行计时器?

解决方案:Expo环境下React Native健身应用后台稳定计时器

针对你在Expo环境下开发健身应用时,锁屏后setInterval()失效、Expo Background Fetch间隔不稳定的问题,以下是几个可行方案:

方案一:基于系统时间戳的无后台任务方案(推荐)

如果你的需求只是准确计算组间训练的时长,不需要后台实时触发动作,这个方案是最优解——完全依赖系统时间,不受应用前后台状态影响,准确性拉满。

核心逻辑:记录训练的起始/暂停时间戳,每次回到应用时用当前系统时间减去记录的时间戳,计算已流逝时长。

示例代码:

import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

const [elapsedTime, setElapsedTime] = useState(0);
const [isRunning, setIsRunning] = useState(false);
let uiUpdateInterval = null;

// 开始计时
const startTimer = async () => {
  const startTime = Date.now();
  await AsyncStorage.setItem('timerStartTime', startTime.toString());
  setIsRunning(true);
};

// 暂停计时
const pauseTimer = async () => {
  const startTime = await AsyncStorage.getItem('timerStartTime');
  const totalElapsed = Date.now() - parseInt(startTime);
  await AsyncStorage.setItem('timerTotalElapsed', totalElapsed.toString());
  setIsRunning(false);
  uiUpdateInterval && clearInterval(uiUpdateInterval);
};

// 恢复计时
const resumeTimer = async () => {
  const savedElapsed = parseInt(await AsyncStorage.getItem('timerTotalElapsed') || '0');
  const newStartTime = Date.now() - savedElapsed;
  await AsyncStorage.setItem('timerStartTime', newStartTime.toString());
  setIsRunning(true);
};

// 实时更新UI显示
useEffect(() => {
  if (isRunning) {
    uiUpdateInterval = setInterval(async () => {
      const startTime = parseInt(await AsyncStorage.getItem('timerStartTime') || '0');
      const currentElapsed = Date.now() - startTime;
      setElapsedTime(currentElapsed);
    }, 1000);
  }
  return () => uiUpdateInterval && clearInterval(uiUpdateInterval);
}, [isRunning]);

方案二:Expo Task Manager + 前台服务

如果需要后台实时触发动作(比如到点提醒),可以用前台服务让应用进程保持活跃,解决Background Fetch间隔不稳定的问题。

1. 配置权限(app.json)

{
  "expo": {
    "ios": {
      "infoPlist": {
        "UIBackgroundModes": ["fetch", "processing"]
      }
    },
    "android": {
      "permissions": ["FOREGROUND_SERVICE"],
      "useNextNotificationsApi": true
    },
    "plugins": [
      [
        "expo-task-manager",
        {
          "iosBackgroundMode": ["fetch", "processing"]
        }
      ]
    ]
  }
}

2. 注册并启动前台任务

import * as TaskManager from 'expo-task-manager';
import * as BackgroundFetch from 'expo-background-fetch';
import * as Notifications from 'expo-notifications';
import { Platform, AppState } from 'react-native';

const TIMER_TASK_NAME = 'workout-timer-task';

// 定义后台任务
TaskManager.defineTask(TIMER_TASK_NAME, async () => {
  // 这里处理计时逻辑,比如读取存储的时间戳计算时长
  const startTime = parseInt(await AsyncStorage.getItem('timerStartTime') || '0');
  const elapsed = Date.now() - startTime;
  
  // 到点触发提醒(示例)
  if (elapsed >= 60000) {
    await Notifications.scheduleNotificationAsync({
      content: { title: '休息时间到!', body: '准备下一组训练' },
      trigger: null
    });
  }

  // 告诉系统任务完成
  return BackgroundFetch.Result.NewData;
});

// 初始化任务
async function initBackgroundTimer() {
  const status = await BackgroundFetch.getStatusAsync();
  if (status === BackgroundFetch.Status.DENIED) {
    await BackgroundFetch.requestPermissionsAsync();
  }

  await BackgroundFetch.registerTaskAsync(TIMER_TASK_NAME, {
    minimumInterval: 1,
    stopOnTerminate: false
  });

  // 监听应用状态,进入后台时确保任务活跃
  AppState.addEventListener('change', async (state) => {
    if (state === 'background' && Platform.OS === 'android') {
      // Android需要显示前台通知保持进程活跃
      await Notifications.setNotificationHandler({
        handleNotification: async () => ({
          shouldShowAlert: false,
          shouldPlaySound: false,
          shouldSetBadge: false
        })
      });
      await Notifications.scheduleNotificationAsync({
        content: { title: '训练计时中', body: '后台记录休息时间' },
        trigger: null
      });
    }
  });
}

// 组件挂载时初始化
useEffect(() => {
  initBackgroundTimer();
  return () => BackgroundFetch.unregisterTaskAsync(TIMER_TASK_NAME);
}, []);

方案三:降级到Bare React Native

如果Expo的限制无法满足需求,可以考虑eject到原生React Native,使用react-native-background-timer包,它能直接在后台运行稳定的定时器,不受锁屏影响。

示例代码:

import BackgroundTimer from 'react-native-background-timer';

// 启动后台定时器
const timerId = BackgroundTimer.setInterval(() => {
  console.log('后台计时触发');
  // 处理计时逻辑
}, 1000);

// 停止定时器
BackgroundTimer.clearInterval(timerId);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 09:15:34