백그라운드 작업 — WorkManager와 Background Fetch

앱이 백그라운드에 있거나 종료된 상태에서도 데이터 동기화, 알림 체크, 캐시 정리 등의 작업을 실행해야 할 때가 있습니다.


백그라운드 작업 방법 비교

방법플랫폼특징
WorkManagerAndroid/iOS가장 안정적, OS가 스케줄링
Background FetchAndroid/iOS주기적 데이터 가져오기
Isolate앱 실행 중무거운 연산 분리
FCM서버 → 앱서버에서 트리거

WorkManager

YAML
dependencies:
  workmanager: ^0.5.0

초기화

DART
import 'package:workmanager/workmanager.dart';

// 최상위 함수 — 백그라운드에서 실행될 콜백
@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    switch (task) {
      case 'syncData':
        await _syncData();
        return true;
      case 'cleanCache':
        await _cleanCache();
        return true;
      default:
        return false;
    }
  });
}

Future<void> _syncData() async {
  // 데이터 동기화 로직
  print('백그라운드 데이터 동기화 실행');
}

Future<void> _cleanCache() async {
  // 캐시 정리 로직
  print('백그라운드 캐시 정리 실행');
}

// main에서 초기화
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Workmanager().initialize(
    callbackDispatcher,
    isInDebugMode: true,  // 디버그 알림 표시
  );

  runApp(const MyApp());
}

작업 등록

DART
class BackgroundTaskService {
  // 1회성 작업
  Future<void> registerOneOffTask() async {
    await Workmanager().registerOneOffTask(
      'sync-task-1',          // 고유 이름
      'syncData',             // task 이름 (callbackDispatcher에서 매칭)
      initialDelay: const Duration(minutes: 5),
      constraints: Constraints(
        networkType: NetworkType.connected,  // 네트워크 연결 시에만
        requiresBatteryNotLow: true,         // 배터리 충분할 때만
      ),
      inputData: {
        'key': 'value',
      },
    );
  }

  // 주기적 작업
  Future<void> registerPeriodicTask() async {
    await Workmanager().registerPeriodicTask(
      'periodic-sync',
      'syncData',
      frequency: const Duration(hours: 1),  // 최소 15분 (Android)
      constraints: Constraints(
        networkType: NetworkType.connected,
      ),
    );
  }

  // 작업 취소
  Future<void> cancelTask(String uniqueName) async {
    await Workmanager().cancelByUniqueName(uniqueName);
  }

  // 모든 작업 취소
  Future<void> cancelAllTasks() async {
    await Workmanager().cancelAll();
  }
}

Background Fetch

주기적으로 데이터를 가져오는 용도에 최적화된 패키지입니다.

YAML
dependencies:
  background_fetch: ^1.3.0
DART
import 'package:background_fetch/background_fetch.dart';

// 백그라운드 핸들러 (최상위 함수)
@pragma('vm:entry-point')
void backgroundFetchHeadlessTask(HeadlessTask task) async {
  final taskId = task.taskId;

  if (task.timeout) {
    // 타임아웃 처리
    BackgroundFetch.finish(taskId);
    return;
  }

  // 작업 수행
  await _fetchNewData();

  BackgroundFetch.finish(taskId);
}

class BackgroundFetchService {
  Future<void> initialize() async {
    final status = await BackgroundFetch.configure(
      BackgroundFetchConfig(
        minimumFetchInterval: 15,  // 분 단위
        stopOnTerminate: false,
        startOnBoot: true,
        enableHeadless: true,
        requiresStorageNotLow: false,
        requiresBatteryNotLow: false,
        requiresCharging: false,
        requiresDeviceIdle: false,
        requiredNetworkType: NetworkType.ANY,
      ),
      _onBackgroundFetch,   // 포그라운드/백그라운드
      _onBackgroundTimeout, // 타임아웃
    );

    print('BackgroundFetch 상태: $status');

    // 헤들리스 태스크 등록 (앱 종료 시에도 실행)
    BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
  }

  void _onBackgroundFetch(String taskId) async {
    print('백그라운드 fetch 실행: $taskId');
    await _fetchNewData();
    BackgroundFetch.finish(taskId);
  }

  void _onBackgroundTimeout(String taskId) {
    print('백그라운드 타임아웃: $taskId');
    BackgroundFetch.finish(taskId);
  }

  Future<void> _fetchNewData() async {
    // 새 데이터 가져오기
  }
}

플랫폼별 제약사항

iOS

  • 백그라운드 실행 시간이 약 30초 로 제한됩니다
  • OS가 실행 빈도를 조절합니다 (사용 패턴에 따라)
  • Info.plist에 백그라운드 모드 추가 필요:
XML
<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
  <string>processing</string>
</array>

Android

  • WorkManager의 최소 주기는 15분 입니다
  • 배터리 절약 모드에서 제한될 수 있습니다
  • Android 12+에서 정확한 알람은 별도 권한 필요

면접 포인트: iOS와 Android 모두 배터리 최적화를 위해 백그라운드 작업을 제한합니다. 정확한 실행 시간을 보장할 수 없으며, OS의 스케줄링에 의존합니다.


백그라운드 작업 주의사항

DART
// 1. 콜백은 최상위 함수여야 함
@pragma('vm:entry-point')
void myCallback() { ... }  // 클래스 메서드 불가

// 2. 플러그인 초기화가 필요할 수 있음
void callbackDispatcher() {
  Workmanager().executeTask((task, data) async {
    // Firebase 등 플러그인 초기화
    WidgetsFlutterBinding.ensureInitialized();
    await Firebase.initializeApp();

    // 작업 수행
    return true;
  });
}

// 3. 오래 걸리는 작업은 피하기 (iOS 30초 제한)

// 4. 작업 완료 알림
// finish() 호출을 잊으면 OS가 앱을 비정상으로 판단
BackgroundFetch.finish(taskId);

정리

  • WorkManager: 가장 안정적인 백그라운드 작업 관리 (1회성/주기적)
  • Background Fetch: 주기적 데이터 가져오기에 특화
  • 콜백 함수는 반드시 ** 최상위 함수 **로 선언해야 합니다
  • iOS는 30초, Android WorkManager 최소 주기는 15분 제한이 있습니다
  • 백그라운드 작업 완료 시 finish()를 반드시 호출하세요
  • 정확한 실행 시간은 보장되지 않으며 OS 스케줄링에 의존합니다
댓글 로딩 중...