본문 바로가기

iOS에서 백그라운드 위치 업데이트가 중단되는 이유

Capacitor 앱에서 iOS가 백그라운드 위치 업데이트를 중단하는 이유: 서스펜션, 백그라운드 모드, 권한, 자동 일시정지, 정확도 및 각 원인 해결 방법

위치 추적을 수행하는 백그라운드 태스크가 실행 중인 iPhone 앱 일러스트

iOS는 Core Location이 실패해서가 아니라 앱을 서스펜드(suspend)하기 때문에 백그라운드 위치 업데이트를 중단합니다. 앱이 위치 백그라운드 모드를 선언하고, 해당 CLLocationManager을 활성화하고, 포그라운드 상태일 때 업데이트를 시작하지 않는 한, iOS는 사용자가 앱을 떠난 후 몇 초 만에 프로세스를 동결시켜 위치 정보를 큐에 쌓거나 버립니다. 이 가이드는 Capacitor 앱에서 확인되는 모든 원인과 각각의 해결책을 나열하고, @capgo/background-geolocation 이 이를 어떻게 처리하는지 보여줍니다.

iOS가 백그라운드 앱을 처리하는 방식

사용자가 홈 버튼을 스와이프하거나 전화를 잠그면, iOS는 앱에 짧은 시간 창을 준 후 서스펜드합니다. 서스펜드된 앱은 CPU 시간을 전혀 받지 못합니다. Capacitor 앱의 경우 이는 네이티브 측과 WebView 모두 실행이 중단된다는 뜻이며, 따라서 JavaScript 콜백도 더 이상 발화되지 않습니다.

Apple은 위치를 능동적으로 사용하는 앱에 대해 예외를 둡니다. 앱이 백그라운드 위치로 설정되어 있고 포그라운드에서 활성 위치 세션이 시작되었다면, iOS는 앱을 실행 상태로 유지하고 업데이트를 계속 전달합니다. 아래 내용은 이 계약을 충족하는 방법과 시스템이 의도적으로 계약을 깨뜨리는 사례에 관한 것입니다.

원인 1: 위치 백그라운드 모드 누락

없이 location 에서 UIBackgroundModes이 없으면 code를 아무리 사용해도 업데이트가 계속되지 않습니다. 다음에 추가하세요: ios/App/App/Info.plist:

<key>UIBackgroundModes</key>
<array>
  <string>location</string>
</array>

Xcode의 Signing &amp; Capabilities > Background Modes > Location updates에서도 활성화할 수 있습니다. 이는 plist 키이지 entitlement가 아니므로 프로비저닝 프로필 변경이 필요하지 않습니다.

그 이면의 네이티브 프로퍼티는 allowsBackgroundLocationUpdates을(를) 고려해야 합니다. 이를 true plist에 백그라운드 모드가 없으면 앱이 크래시합니다. Capgo 플러그인은 다음을 전달하면 자동으로 설정해 줍니다 backgroundMessage 대상 start(), 따라서 출시 전에 plist 키가 반드시 존재해야 합니다.

앱 리뷰에서는 백그라운드 모드 사용이 정당화되는지 확인합니다. 리뷰 노트에서 어떤 기능이 이를 사용하는지 설명하고, 해당 기능이 리뷰어에게 보이도록 하세요.

원인 2: 백그라운드에서 업데이트가 시작됨

Apple의 규칙: 앱이 포그라운드에 있을 때 시작되었다면 백그라운드 업데이트가 계속됩니다. 시ilent push, 백그라운드 fetch 또는 BGTaskScheduler 태스크에서 세션을 시작하려고 하면 표준 위치 업데이트에는 작동하지 않습니다.

사용자 동작 또는 앱 재개 시 추적을 시작하세요:

import { App } from '@capacitor/app';
import { BackgroundGeolocation } from '@capgo/background-geolocation';

let tracking = false;

export async function startTracking() {
  if (tracking) return;
  tracking = true;
  await BackgroundGeolocation.start(
    {
      backgroundMessage: 'Recording your route',
      backgroundTitle: 'Route recording',
      requestPermissions: true,
      distanceFilter: 10,
      url: 'https://api.example.com/v1/points',
    },
    (location, error) => {
      if (error) return console.error(error);
      if (location) console.log(location.latitude, location.longitude);
    },
  );
}

// Re-arm when the user comes back, if the session should be active
App.addListener('appStateChange', ({ isActive }) => {
  if (isActive && shouldBeTracking()) startTracking();
});

declare function shouldBeTracking(): boolean;

원인 3: backgroundMessage 이 전달되지 않았습니다

이것은 Capgo 플러그인에 특화된 내용이며 가장 흔한 버그 리포트입니다. 만약 backgroundMessage 이 undefined이면, 플러그인은 위치 관리자를 포그라운드 전용으로 설정합니다: allowsBackgroundLocationUpdates stays false 앱이 일시 중단되면 업데이트도 중단됩니다. 두 플랫폼 모두에 메시지를 전달하세요. Android에서는 이 메시지가 포그라운드 서비스 알림 텍스트가 됩니다.

원인 4: 잘못된 권한 수준 또는 '한 번 허용'

많은 개발자가 백그라운드 트래킹에 항상 허용(Always)이 필요하다고 생각하지만, 그렇지 않습니다. Apple 문서에 따르면 '앱을 사용하는 동안 허용(When In Use)' 권한이 있는 앱은 백그라운드 업데이트가 켜져 있는 한 위치 서비스가 작동하는 동안 백그라운드에서 계속 실행됩니다.

항상 허용(Always)은 다른 상황, 즉 종료된 앱을 재실행할 때 중요합니다. 지역 모니터링, 중요 위치 변경(significant-change location), 방문(visits)만 앱을 재실행할 수 있고, 이도 항상 허용 권한이 있어야 가능합니다.

사용자의 두 가지 선택이 백그라운드 트래킹을 조용히 망가뜨립니다:

  • 한 번 허용(Allow Once). 이는 앱 사용이 중단되면 만료되는 임시 '앱을 사용하는 동안 허용' 권한입니다. 다음 세션은 다시 '결정되지 않음(not determined)' 상태에서 시작됩니다.
  • '앱을 사용하는 동안 허용'을 선택하면 '항상 허용' 프롬프트는 표시되지 않습니다. 항상 허용(Always)을 요청해도 iOS는 나중에 원하는 시점에 권한 업그레이드 프롬프트를 표시할 수 있고, 이는 앱이 화면에 떠 있지 않을 때가 많습니다.

세션을 시작하기 전에 실제 상태를 확인하세요:

import { BackgroundGeolocation } from '@capgo/background-geolocation';

const status = await BackgroundGeolocation.checkPermissions();

if (status.location !== 'granted') {
  await BackgroundGeolocation.requestPermissions({ permissions: ['location'] });
}

if (status.backgroundLocation === 'when_in_use') {
  // Background tracking still works for standard updates.
  // Explain why Always helps if you also rely on geofences.
}

iOS에서는, backgroundLocation 반환합니다 when_in_use 또는 granted / always, 그래서 추측 대신 정확한 설명을 보여줄 수 있습니다.

원인 5: Core Location이 업데이트를 자동으로 일시 중지함

CLLocationManager.pausesLocationUpdatesAutomatically 기본값은 true. iOS가 사용자가 이동하지 않는다고 판단하면 배터리 절약을 위해 업데이트를 일시 중지합니다. When In Use 앱의 경우 일시 중지되면 앱을 다시 실행할 때까지 위치 접근이 중단되고, Core Location은 자동으로 재개되지 않습니다.

Capgo 플러그인은 pausesLocationUpdatesAutomatically = false 을 네이티브 code에 설정하므로 이 부분은 처리됩니다. 직접 네이티브 code을 작성하거나 다른 플러그인을 사용하는 경우, 이 속성을 먼저 확인하세요.

원인 6: 기기가 정지 상태이고 필터가 큼

다음과 같은 distanceFilter 50m 미만인 경우, 책상 위에 놓인 폰은 업데이트를 생성하지 않습니다. 로그에서는 '추적이 중단된' 것처럼 보일 수 있지만, 이는 의도된 동작입니다.

  • 정지 상태에서도 heartbeats가 필요하면, 큰 거리 필터 대신 작은 값 또는 0을 distanceFilter.
  • 사용 minIntervalMs 사용하여 네이티브 POST 전송 빈도를 제한하세요.
await BackgroundGeolocation.start(
  {
    backgroundMessage: 'Vehicle tracking active',
    distanceFilter: 0,
    minIntervalMs: 120_000, // at most one native POST every 2 minutes
    url: 'https://api.example.com/v1/vehicle-points',
  },
  () => {},
);

원인 7: 정밀 위치가 꺼져 있음

iOS 14부터 사용자는 앱의 정밀 위치를 끌 수 있습니다. 업데이트는 계속 수신되지만, 근사값(수 km)이며 훨씬 드물게 도착합니다. 데이터에서 이를 감지하세요(큰 accuracy 값) 그리고 정밀 위치가 해당 기능에 왜 중요한지 사용자에게 설명하세요. 배달이나 피트니스 앱을 출시한다면, 나쁜 경험 이후가 아니라 온보딩 시점에 이를 보여주세요.

원인 8: 사용자가 앱을 종료함

사용자가 앱 스위처에서 앱을 스와이프하여 제거하면 iOS는 표준 위치 업데이트를 중단합니다. 어떤 플러그인도 이를 계속 실행할 수 없습니다. 이는 태스크 제거 후에도 포그라운드 서비스가 계속 실행될 수 있는 Android에서 온 팀들에게 흔한 의외의 상황입니다.

옵션:

  • 세션 중 강제 종료를 하지 말라고 사용자에게 안내하세요. 대부분의 플릿 앱은 온보딩 시 이를 수행합니다.
  • 중요한 장소 주위에 Always 권한을 가진 지오펜스를 추가하여, 진입 또는 이탈 시 iOS가 백그라운드에서 앱을 다시 시작할 수 있도록 하세요.
  • 세션 중 서버가 포인트 수신을 중단하면 사용자에게 앱을 다시 열도록 푸시 알림을 보내세요.

원인 9: 네이티브는 실행 중인데 JavaScript가 실행되지 않음

다소 미묘한 문제입니다. 네이티브 code는 WebView가 일시 중단된 동안, 또는 살아 있지만 타이머나 네트워크 호출을 빠르게 처리하지 못하는 동안에도 위치를 수신할 수 있습니다. 만약 fetch 콜백에서 보내면 포인트가 늦게 도착하거나 아예 도착하지 않을 수 있습니다.

플러그인의 url 옵션을 사용하세요. 각 포인트는 네이티브 code에서 다음 항목과 동일한 JSON으로 POST됩니다. Location type 및 "source": "native", WebView와 독립적으로. 콜백은 UI 업데이트에만 사용하세요. 전송은 디스크 큐가 없는 best-effort 방식이므로, 포인트를 하나라도 잃으면 안 된다면 콜백이 발생할 때 로컬에도 저장한 뒤 나중에 동기화하세요.

위치 외 백그라운드 작업에 대해서는 Capacitor에서 백그라운드 태스크가 어떻게 작동하는지 읽어보시고 다음 내용을 확인하세요. @capgo/capacitor-background-task.

원인 10: 저전력 모드 및 열 상태

저전력 모드는 시스템 전체의 백그라운드 활동을 줄입니다. 위치는 계속 작동하지만, 세션이 의존하는 다른 요소(네트워크 재시도, 백그라운드 새로고침)는 느려집니다. 릴리스 승인 전에 저전력 모드를 켠 상태로 테스트하세요.

파란색 표시기

When In Use 권한을 가진 앱이 백그라운드에서 위치를 사용하면, iOS는 상태 바에 파란색 알약 모양 표시를 띄웁니다. Capgo 플러그인은 showsBackgroundLocationIndicator 대상 true 을 백그라운드 세션에 설정하는데, 이는 사용자가 기대하는 것이자 리뷰어가 확인하는 부분입니다. 숨기려 하지 마세요. 사용자는 이 표시를 신뢰합니다.

이런 버그를 잡아내는 테스트 계획

백그라운드 위치는 시뮬레이터 테스트만으로 부족합니다. 실제 iPhone에서 다음을 확인하세요:

  1. 디버그가 아닌 릴리스 구성으로 빌드하세요. Xcode에 연결된 디버그 빌드는 라이프사이클 처리가 다릅니다.
  2. 포그라운드에서 세션을 시작하고, 휴대폰을 잠근 뒤 20분 동안 걷거나 운전하세요.
  3. “한 번 허용”, 정밀 위치 꺼짐, 저전력 모드 켜짐 상태로도 반복하세요.
  4. 서버 로그에서 source: "native" 포인트를 확인하세요. 앱 내 로그만 봐서는 부족합니다.
  5. 세션 중 강제 종료 후 다음 실행 시 앱이 무슨 일이 있었는지 설명하는지 확인하세요.

ad hoc 빌드에 기기 UDID가 필요하면, iOS UDID 찾기 Safari에서 가져옵니다. Xcode 26이 설치된 Mac이 없다면, Capgo 빌드 클라우드에서 서명된 iOS 빌드를 생성할 수 있습니다.

문제 해결 체크리스트

증상 예상 원인 해결 방법
잠금 직후 몇 초 만에 중단 아니요 location 백그라운드 모드 여부 backgroundMessage plist 키를 추가하고 전달하세요 backgroundMessage
시작 시 크래시 allowsBackgroundLocationUpdates 백그라운드 모드 없이 추가 UIBackgroundModes → location
한 번 작동 후 안 됨 사용자가 '한 번 허용' 선택 설명 후 다시 요청하거나 보내기 openSettings()
주차하면 중단 대규모 distanceFilter 낮추고 사용 minIntervalMs
포인트가 멀리 떨어짐, 정확도 낮음 정밀 위치 비활성 사용자에게 활성화 요청
스와이프 종료 시 중단 사용자가 앱을 종료함 iOS에서 예상되는 동작, 재시작에 지오펜스 사용
네이티브는 포인트 수신, 서버는 미수신 JavaScript fetch 콜백에서 트레이드오프를 평가하려면 url 옵션

수정 배포

이 수정 사항 대부분은 Info.plist or native configuration, so they need a new App Store build. The JavaScript parts, such as permission explanations, onboarding copy and retry logic, can be shipped to existing users with Capgo Live Update 사용하기 네이티브 빌드가 배포된 후입니다.

플러그인 선택을 고민 중이라면, 저희 비교 Transistorsoft와 무료 Capgo 플러그인 이 비교에서 트레이드오프를 다룹니다.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.