앱이 백그라운드로 내려가 있는 동안에도 서버에서 새 데이터를 미리 받아두고 싶었다. 예전 같으면 expo-background-fetch를 열었을 텐데, SDK 53으로 올리고 나니 deprecated 딱지가 붙어 있었다. 대신 들어온 게 expo-background-task다.
왜 갈아탔나
expo-background-fetch는 iOS의 BGAppRefreshTask를 얇게 감싼 거라 안드로이드 쪽 동작이 애매했다. 새 expo-background-task는 안드로이드에서 WorkManager, iOS에서 BGTaskScheduler를 쓴다. OS가 배터리·네트워크 상태를 봐서 알아서 스케줄링하니, "정확히 15분마다"가 아니라 "대략 이 간격 근처에서 적당할 때" 돌아간다고 생각하는 게 맞다.
태스크 정의
작업 자체는 expo-task-manager로 이름을 붙여 등록한다. 이 정의는 컴포넌트 밖, 앱 최상단에서 한 번만 실행돼야 한다.
import * as TaskManager from 'expo-task-manager';
import * as BackgroundTask from 'expo-background-task';
const TASK = 'refresh-feed';
TaskManager.defineTask(TASK, async () => {
try {
const res = await fetch('https://example.com/api/feed');
const data = await res.json();
await cacheFeed(data); // AsyncStorage 등에 저장
return BackgroundTask.BackgroundTaskResult.Success;
} catch {
return BackgroundTask.BackgroundTaskResult.Failed;
}
});
등록과 해제
정의만으론 안 돌고, 따로 등록해줘야 OS 스케줄러에 올라간다. 보통 로그인 이후나 앱 시작 시점에 붙인다.
import * as BackgroundTask from 'expo-background-task';
async function register() {
const status = await BackgroundTask.getStatusAsync();
if (status === BackgroundTask.BackgroundTaskStatus.Available) {
await BackgroundTask.registerTaskAsync(TASK, {
minimumInterval: 15, // 분 단위, OS가 최소 기준으로만 참고
});
}
}
// 로그아웃 등에서 정리
async function unregister() {
await BackgroundTask.unregisterTaskAsync(TASK);
}
minimumInterval은 딱 맞춰지는 값이 아니라 하한선일 뿐이다. iOS는 사용자의 앱 사용 패턴을 학습해서 실제 실행 시점을 잡는다.
개발 중에 테스트하기
진짜 백그라운드 트리거를 기다리면 하세월이라, 개발 빌드에선 강제로 한 번 돌려볼 수 있다.
await BackgroundTask.triggerTaskWorkerForTestingAsync();
이걸로 태스크 로직이 제대로 도는지 먼저 확인하고, 실제 스케줄링은 실기기에 올려서 검증하는 흐름이 편했다.
핵심은 "정확한 타이머가 아니라 OS에 맡기는 힌트"라는 것 하나다. 몇 분마다 칼같이 돌 거라 기대하고 짜면 어긋나고, "언젠가 조용할 때 알아서 갱신된다" 정도로 설계하면 잘 맞는다. 실시간성이 필요하면 이건 도구가 아니고, 그땐 푸시 알림 쪽을 봐야 한다.