effidevFlutter · Cloudflare 엣지 · 클라우드 비용 최적화
한국어

Flutter iOS / Android 백그라운드 작업(WorkManager, BGTaskScheduler) 안 끊기고 안정 실행하기

Flutter iOS Android 백그라운드 작업 WorkManager BGTaskScheduler 안정화 아키텍처

모바일 앱 서비스(위치 추적, 오프라인 데이터 동기화, 주기적 백그라운드 데이터 패치, 로컬 알림 예약 등)를 개발할 때 가장 까다로운 요구사항 중 하나는 **“앱이 꺼져있거나 백그라운드에 있을 때도 백그라운드 작업이 죽지 않고 실행되는 것”**이다.

Flutter 앱 개발 시 단순히 Timer.periodic이나 일반 비동기 함수를 사용하면 앱이 백그라운드로 진입하는 순간 OS(Android/iOS)의 배터리 최적화 정책에 의해 즉시 서스펜드(Suspend)되거나 메모리에서 킬(Kill)당한다.

특히 Android의 제조사별(삼성, 샤오미) 강도 높은 Doze Mode 정책과 iOS의 엄격한 BGTaskScheduler 제약은 백그라운드 작업의 주기적 실행을 방해하는 큰 장애물이다.

이 글에서는 WorkManager 패키지를 활용해 Android와 iOS에서 모두 안정적으로 작동하는 백그라운드 작업 아키텍처, @pragma('vm:entry-point') 콜백 분리 기법, iOS Info.plist 설정, 그리고 시뮬레이터/ADB 디버깅 기법을 안내한다.

핵심 요약

  • OS별 백그라운드 구동 메커니즘: Android는 WorkManager (PeriodicWorkRequest, 제약조건 처리)를 사용하며, iOS는 BGTaskScheduler (BGAppRefreshTask, BGProcessingTask)를 기반으로 작동한다.
  • Isolate 분리 필수: 백그라운드 작업은 UI 메인 Isolate와 별개의 백그라운드 Isolate에서 독립 실행되므로, 최상위 콜백 함수에 @pragma('vm:entry-point') 어노테이션을 붙여 트리밍을 방지해야 한다.
  • iOS 실행 제약: iOS 백그라운드 작업은 정확한 정시 실행을 보장하지 않으며, OS 판단에 따라 30초 내외의 실행 시간이 부여된다.
  • 제조사 배터리 최적화 회피: Android에서는 제약 조건(Constraints(networkType: NetworkType.connected)) 설정과 함께 필요 시 Foreground Service를 병행해야 지속성을 확보할 수 있다.

1. Android WorkManager vs iOS BGTaskScheduler 비교

두 운영체제는 백그라운드 작업을 다루는 철학이 상이하다.

비교 항목 Android WorkManager iOS BGTaskScheduler
최소 주기 단위 최소 15분 (PeriodicWork) OS가 배터리 및 사용 패턴에 따라 결정
실행 시간 제한 조건 충족 시 최대 10분 (Foreground Service 전환 시 무제한) 작업당 최대 30초 수준의 엄격한 시간 제한
제약 조건(Constraints) 와이파이 연결, 충전 중, 배터리 여유 등 세분화 설정 가능 Background Modes 권한 등록 필요
실행 보장성 높은 편 (Doze Mode 탈출 작업 가능) 낮음 (배터리 부족 시 OS가 작업 취소)

2. Flutter workmanager 패키지 구성 및 코드 작성

1) 백그라운드 엔트리 포인트 정의 (callbackDispatcher)

백그라운드 작업은 UI 렌더링 컨텍스트가 없으므로 최상위(Top-level) 함수로 선언하고 @pragma('vm:entry-point')를 반드시 명시해야 한다.

import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';

// 백그라운드 Isolate의 엔트리 포인트
@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((taskName, inputData) async {
    switch (taskName) {
      case 'syncDataTask':
        debugPrint("백그라운드 데이터 동기화 시작: $inputData");
        // HTTP 요청 또는 로컬 DB 작업 수행
        final success = await _performDataSync();
        return Future.value(success);

      case 'simplePeriodicTask':
        debugPrint("주기적 백그라운드 작업 실행");
        return Future.value(true);

      default:
        return Future.value(true);
    }
  });
}

Future<bool> _performDataSync() async {
  // 실제 백그라운드 API 호출 또는 SQLite 저장 로직
  await Future.delayed(const Duration(seconds: 2));
  return true;
}

2) 앱 시작 시 WorkManager 초기화 및 작업 등록

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 1. WorkManager 초기화 (콜백 디스패처 등록)
  await Workmanager().initialize(
    callbackDispatcher,
    isInDebugMode: true, // 개발 중 로그 출력
  );

  runApp(const MyApp());
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  void _scheduleBackgroundTask() {
    // Android: 15분마다 주기적 작업 등록
    Workmanager().registerPeriodicTask(
      "periodic-sync-id",
      "syncDataTask",
      frequency: const Duration(minutes: 15),
      constraints: Constraints(
        networkType: NetworkType.connected, // 인터넷 연결 시에만 구동
        requiresBatteryNotLow: true,        // 배터리가 충분할 때만
      ),
      inputData: <String, dynamic>{'userId': 'user_1234'},
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('WorkManager 백그라운드 데모')),
      body: Center(
        child: ElevatedButton(
          onPressed: _scheduleBackgroundTask,
          child: const Text('백그라운드 작업 예약'),
        ),
      ),
    );
  }
}

3. iOS 필수 설정 (Info.plist)

iOS에서 WorkManagerBGTaskScheduler를 정상 작동시키려면 ios/Runner/Info.plist에 BGTaskPermittedIdentifiers 및 Background Modes를 등록해야 한다.

<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>processing</string>
</array>

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>workmanager.background.task</string>
    <string>syncDataTask</string>
</array>

4. 백그라운드 작업 CLI 디버깅 및 시뮬레이션 기법

백그라운드 작업은 15분 이상 기다리지 않고 터미널 CLI 명령어로 즉시 강제 실행시켜 테스트해야 한다.

1) Android ADB 강제 실행 명령어

# WorkManager 작업 강제 트리거
adb shell cmd jobscheduler run -f dev.effidev.flutterapp 1001

2) iOS Xcode LLDB 시뮬레이션 명령어

iOS 시뮬레이터 실행 중 Xcode 디버거(LLDB)에서 아래 명령어를 입력하면 BGTaskScheduler 작업을 즉시 실행할 수 있다.

(lldb) e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"syncDataTask"]

결론

WorkManager와 백그라운드 Isolate 분리를 올바르게 적용하면, Android의 Doze Mode와 iOS의 BGTaskScheduler 제약 속에서도 킬당하지 않고 안정적인 백그라운드 작업을 유지할 수 있다.

네트워크 제약 조건 설정과 @pragma('vm:entry-point') 지침을 준수하여 신뢰성 높은 백그라운드 동기화 시스템을 구축해보자.