2025년 3월 30일·6분 읽기

현장 앱을 위한 Kotlin WorkManager 백그라운드 동기화 패턴

현장 앱을 위한 Kotlin WorkManager 백그라운드 동기화 패턴: 올바른 작업 종류 선택, 제약조건 설정, 지수 백오프 재시도 사용, 사용자에게 보이는 진행률 제공.

현장 앱을 위한 Kotlin WorkManager 백그라운드 동기화 패턴

현장 및 운영 앱에서 신뢰할 수 있는 백그라운드 동기화란 무엇인가

현장 및 운영 앱에서 동기화는 "있으면 좋은 기능"이 아닙니다. 기기에서 작업이 떠나 팀에 실제로 반영되는 방법입니다. 동기화가 실패하면 사용자는 빠르게 알아차립니다: 완료된 작업이 아직 "대기 중"으로 보이거나, 사진이 사라지거나, 같은 보고서가 두 번 업로드되어 중복이 생길 수 있습니다.

이런 앱은 일반 소비자 앱보다 더 까다롭습니다. 휴대폰은 최악의 조건에서 동작합니다. 네트워크는 LTE, 약한 Wi‑Fi, 무신호를 오갑니다. 배터리 절약 모드가 백그라운드 작업을 막기도 하고, 앱이 강제 종료되거나 OS가 업데이트되며 장치가 재부팅될 수 있습니다. 신뢰할 수 있는 WorkManager 설정은 이런 상황을 드라마 없이 견뎌야 합니다.

신뢰성은 보통 네 가지를 의미합니다:

  • 결국 일관성 있음: 데이터가 늦게 도착하더라도 수동 개입 없이 도착한다.
  • 복구 가능: 업로드 중 앱이 죽어도 다음 실행에서 안전하게 이어간다.
  • 관찰 가능: 사용자와 지원팀이 무슨 일이 일어나고 있는지, 무엇이 막혀 있는지 알 수 있다.
  • 비파괴적: 재시도가 중복을 만들거나 상태를 망가뜨리지 않는다.

"지금 실행"은 사용자가 트리거한 작은 작업에 적합합니다(예: 사용자가 작업을 닫기 전에 단일 상태 업데이트 전송). "대기"는 사진 업로드나 배치 업데이트처럼 무겁고 배터리를 소모하거나 네트워크 환경에서 실패하기 쉬운 작업에 적합합니다.

예시: 검사원이 신호가 없는 지하에서 12장의 사진과 함께 폼을 제출합니다. 신뢰할 수 있는 동기화는 모든 것을 로컬에 저장하고 큐에 넣은 뒤, 기기에 실제 연결이 생겼을 때 다시 업로드하여 검사원이 작업을 다시 하지 않아도 되게 합니다.

올바른 WorkManager 구성 요소 선택하기

작업의 가장 작고 명확한 단위를 선택하는 것부터 시작하세요. 이 결정은 나중의 어떤 영리한 재시도 로직보다도 신뢰성에 더 큰 영향을 줍니다.

One-time vs periodic work

변경 때문에 해야 하는 작업(예: 새 폼 저장, 사진 압축 완료, 사용자가 동기화 버튼을 누름)은 OneTimeWorkRequest를 사용하세요. 제약조건과 함께 즉시 enqueu e하고 장치가 준비될 때 WorkManager가 실행하게 하세요.

정기적인 유지관리(업데이트 확인, 야간 정리 등)는 PeriodicWorkRequest를 사용하세요. 주기 작업은 정확하지 않습니다. 최소 간격이 있고 배터리 및 시스템 규칙에 따라 지연될 수 있으니 중요한 업로드의 유일한 경로로 삼으면 안 됩니다.

실용적인 패턴은 "곧 동기화해야 하는" 항목은 일회성 작업으로 처리하고, 정기 작업은 안전망으로 두는 것입니다.

Worker, CoroutineWorker, 또는 RxWorker 선택

Kotlin을 사용하고 suspend 함수가 있다면 CoroutineWorker를 선호하세요. 코드가 짧아지고 취소 동작이 기대대로 동작합니다.

Worker는 간단한 블로킹 코드에 맞지만, 너무 오래 블로킹하지 않도록 주의해야 합니다.

RxWorker는 앱이 이미 RxJava에 크게 의존하는 경우에만 의미가 있습니다. 그렇지 않으면 불필요한 복잡성입니다.

단계별로 체인할지, 하나의 워커로 단계 처리할지

단계를 개별적으로 성공/실패 시키고 각 단계별 재시도 및 로그를 분리하고 싶다면 체인이 좋습니다. 단계들이 데이터를 공유하고 하나의 트랜잭션처럼 처리되어야 한다면 하나의 워커로 여러 단계를 처리하는 편이 낫습니다.

간단한 규칙:

  • 다른 제약조건이 필요한 단계(예: Wi‑Fi 전용 업로드 후 가벼운 API 호출)면 체인하세요.
  • 하나의 "전부 또는 없음" 동작이 필요하면 하나의 워커를 사용하세요.

WorkManager는 작업을 영속화하고 프로세스 종료와 재부팅을 견디며 제약조건을 준수한다는 것을 보장합니다. 즉시 실행이나 사용자가 앱을 강제 중지한 후 실행되는 것은 보장하지 않습니다. Android 현장 앱을 만든다면(예: AppMaster로 생성된 Kotlin 앱 포함) 지연이 발생해도 안전하고 예상 가능한 방식으로 동기화를 설계하세요.

동기화를 안전하게 만들기: 멱등, 증분, 재개 가능

현장 앱에서는 작업이 여러 번 재실행됩니다. 휴대폰은 신호를 잃고 OS가 프로세스를 종료하며 사용자는 아무 일도 일어나지 않은 것 같아 동기화를 두 번 누르기도 합니다. 백그라운드 동기화가 반복 실행에 안전하지 않으면 중복 레코드, 누락된 업데이트, 끝없는 재시도가 발생합니다.

서버 호출을 두 번 실행해도 안전하도록 하세요. 가장 단순한 접근은 항목별 멱등 키(예: 로컬 레코드에 저장된 UUID)를 사용하는 것입니다. 서버는 같은 요청이면 같은 결과로 처리합니다. 서버를 바꿀 수 없다면 안정적인 자연 키와 upsert 엔드포인트를 사용하거나 버전 번호를 포함해 서버가 오래된 업데이트를 거부하도록 하세요.

워커가 충돌 후에도 추측 없이 재개할 수 있도록 로컬 상태를 명시적으로 추적하세요. 간단한 상태 머신이면 충분합니다:

  • queued
  • uploading
  • uploaded
  • needs-review
  • failed-temporary

동기화를 증분으로 유지하세요. "모두 동기화" 대신 lastSuccessfulTimestamp나 서버 발급 토큰 같은 커서를 저장하세요. 작은 페이지(예: 20–100개)씩 읽고 적용한 뒤 배치가 로컬에 완전히 커밋된 후에만 커서를 옮기세요. 작은 배치는 타임아웃을 줄이고 진행 상황을 가시화하며 중단 후 반복해야 할 작업량을 제한합니다.

사진이나 큰 페이로드 같은 업로드도 재개 가능하도록 만드세요. 파일 URI와 업로드 메타데이터를 영구 저장하고 서버가 확인하기 전까지는 업로드 완료로 표시하지 마세요. 워커가 재시작되면 마지막 알려진 상태부터 계속하도록 하세요.

예시: 기술자가 지하에서 12개의 폼을 작성하고 8장의 사진을 첨부했습니다. 기기가 다시 연결되면 워커는 배치로 업로드하고 각 폼에는 멱등 키가 있으며 동기화 커서는 각 배치가 성공한 후에만 전진합니다. 앱이 중간에 죽으면 워커를 다시 실행해 남은 큐 항목을 중복 없이 마무리합니다.

실제 장치 조건에 맞는 제약조건 설정

제약조건은 백그라운드 동기화가 배터리를 소모하거나 데이터 요금을 태우거나 최악의 시점에 실패하지 않도록 하는 가드레일입니다. 장치가 현장에서 어떻게 동작하는지를 반영하는 제약조건을 원하지, 개발자 책상 위에서의 동작을 반영하는 것은 아닙니다.

사용자를 보호하면서도 대부분의 날에 작업이 실행될 수 있도록 작은 집합으로 시작하세요. 실용적인 기본값은 네트워크 연결 필요, 배터리 낮음 상태일 때 실행하지 않기, 저장공간 부족 상태에서는 실행하지 않기입니다. 작업이 무겁하고 시간에 민감하지 않다면 충전 상태를 요구하세요. 다만 많은 현장 장비는 근무 중에 거의 플러그인되지 않습니다.

과도한 제약조건은 "동기화가 전혀 실행되지 않음" 보고의 흔한 이유입니다. 비과금 Wi‑Fi, 충전 중, 배터리 충분 등 모든 조건을 요구하면 완벽한 순간만 기다리게 됩니다. 오늘 데이터가 필요하면 이상적인 조건을 기다리기보다 더 자주 작은 작업을 실행하는 편이 낫습니다.

캡티브 포털(호텔이나 공공 Wi‑Fi의 동의 페이지)도 현실 문제입니다: 기기는 연결되었다고 하지만 사용자가 "수락"을 눌러야 인터넷이 되는 경우입니다. WorkManager는 이 상태를 안정적으로 감지할 수 없습니다. 일반적인 실패로 취급하세요: 동기화를 시도하고 빠르게 타임아웃 처리한 뒤 나중에 재시도하세요. 요청 중에 이를 감지할 수 있으면 앱 내에 "Wi‑Fi에 연결되어 있으나 인터넷이 없음" 같은 간단한 메시지를 표시하세요.

작은 업로드와 큰 업로드에 대해 다른 제약조건을 사용해 앱이 반응성을 유지하게 하세요:

  • 작은 페이로드(상태 전달, 폼 메타데이터): 어떤 네트워크든 허용, 배터리 낮음 아님.
  • 큰 페이로드(사진, 비디오, 맵 데이터): 가능한 한 비과금 네트워크, 그리고 충전 상태 고려.

예시: 기술자가 사진 2장과 함께 폼을 저장했습니다. 폼 필드는 어떤 연결에서도 전송하되 사진 업로드는 Wi‑Fi나 더 좋은 순간을 기다리도록 큐에 넣으세요. 사무실에서는 작업이 빠르게 보이지만 장비는 백그라운드에서 모바일 데이터를 낭비하지 않습니다.

사용자에게 거슬리지 않는 지수 백오프 기반 재시도

Build your field app faster
Build a field-ready app with a real backend and native mobile clients, without hand-coding everything.
Try AppMaster

재시도는 현장 앱이 차분하게 느껴질지 혹은 망가진 것처럼 느껴질지를 결정합니다. 예상되는 실패 유형에 맞는 백오프 정책을 선택하세요.

네트워크에 대해서는 지수 백오프가 일반적으로 가장 안전한 기본값입니다. 연결이 불안할 때 서버를 공격하거나 배터리를 소모하지 않도록 재시도 간격을 빠르게 늘립니다. 선형 백오프는 일시적 문제(예: 불안정한 VPN)에 맞을 수 있지만 약한 신호 지역에서는 너무 자주 재시도하는 경향이 있습니다.

재시도 결정은 단순히 "무언가 실패함"이 아니라 실패 유형에 기반해야 합니다. 간단한 규칙 세트:

  • 네트워크 타임아웃, 5xx, DNS, 연결 없음: Result.retry()
  • 인증 만료(401): 토큰을 한 번 갱신해 보고 실패하면 실패 처리하고 사용자에게 로그인 하도록 요청
  • Validation 또는 4xx(잘못된 요청): Result.failure()와 함께 지원을 위한 명확한 오류 메시지
  • 충돌(409, 이미 전송된 항목): 멱등 설계라면 성공으로 처리

영구 오류가 무한히 반복되지 않도록 제한하세요. 최대 시도 횟수를 설정하고 그 이후에는 중지하고 조용하지만 실행 가능한 메시지를 표시하세요(반복 알림이 아님).

시도 횟수가 늘어날수록 동작을 변경할 수도 있습니다. 예를 들어 2번 실패 후에는 더 작은 배치로 전송하거나 큰 업로드는 건너뛰고 다음 성공할 때까지 미루는 방식입니다.

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .setBackoffCriteria(
    BackoffPolicy.EXPONENTIAL,
    30, TimeUnit.SECONDS
  )
  .build()

// in doWork()
if (runAttemptCount \u003e= 5) return Result.failure()
return Result.retry()

이렇게 하면 재시도는 예의 바르게 동작합니다: 깨어나는 횟수 감소, 사용자 방해 감소, 연결이 복구되었을 때 빠른 회복.

사용자에게 보이는 진행 상황: 알림, 포그라운드 작업, 상태

Keep full control with source
Get real source code output so your team can review, extend, and self-host when needed.
Generate Code

현장 앱은 종종 사용자가 예상치 못한 순간에 동기화합니다: 지하, 느린 네트워크, 배터리 거의 없음. 동기화가 사용자가 기다리는 작업에 영향을 준다면(업로드, 보고서 전송, 사진 배치 등) 이를 보이게 하고 이해하기 쉽게 만드세요. 조용한 백그라운드 작업은 작은, 빠른 업데이트에 적합합니다. 더 긴 작업은 정직하게 보여줘야 합니다.

포그라운드 작업이 필요한 경우

작업이 장시간 실행되거나 시간에 민감하거나 명확히 사용자 행동과 연결되어 있다면 포그라운드 실행을 사용하세요. 최신 Android에서는 큰 업로드가 포그라운드로 실행하지 않으면 중단되거나 지연될 수 있습니다. WorkManager에서는 ForegroundInfo를 반환해 시스템이 진행 중인 알림을 표시하도록 합니다.

좋은 알림은 세 가지 질문에 답해야 합니다: 무엇을 동기화하는가, 얼마나 진행되었는가, 그리고 어떻게 중단하는가. 데이터 요금이 우려되거나 지금 당장 휴대폰이 필요하면 사용자가 취소할 수 있도록 명확한 취소 액션을 추가하세요.

신뢰할 수 있는 진행률

진행률은 모호한 퍼센트가 아니라 실제 단위에 기반해야 합니다. setProgress로 진행률을 업데이트하고 UI(또는 상태 화면)에서 WorkInfo로 읽으세요.

예를 들어 사진 12장과 폼 3개를 업로드한다면 "15개 중 5개 업로드 완료"처럼 표시하고 남은 항목을 보여 주세요. 마지막 오류 메시지는 지원을 위해 보관하세요.

진행률에 포함할 유의미한 항목:

  • 완료된 항목 수와 남은 항목 수
  • 현재 단계(예: "사진 업로드", "폼 전송", "마무리")
  • 마지막 성공 동기화 시간
  • 마지막 오류(짧고 사용자 친화적)
  • 명확한 취소/중지 옵션

AppMaster로 내부 도구를 빠르게 만들더라도 같은 규칙을 지키세요: 사용자는 동기화가 실제로 자신이 하려는 일과 일치할 때 신뢰합니다.

고유 작업, 태그, 중복 동기화 작업 피하기

중복 동기화 작업은 배터리 소모, 모바일 데이터 낭비, 서버 측 충돌을 일으키는 가장 쉬운 방법 중 하나입니다. WorkManager는 고유 작업 이름과 태그라는 두 가지 간단한 도구를 제공합니다.

기본값으로 "sync"를 하나의 lane으로 취급하세요. 앱이 깨어나고 네트워크 변화가 일어나고 정기 작업이 동시에 트리거될 때마다 새로운 작업을 쌓는 대신 같은 고유 작업 이름을 enqueue 하면 동기화 폭주를 막을 수 있습니다.

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .addTag("sync")
  .build()

WorkManager.getInstance(context)
  .enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, request)

정책 선택은 주요 행동 선택입니다:

  • KEEP: 이미 동기화가 실행 중(또는 큐에 있음)이면 새 요청을 무시합니다. 대부분의 "지금 동기화" 버튼과 자동 동기화 트리거에 사용하세요.
  • REPLACE: 현재 것을 취소하고 새로 시작합니다. 사용자가 계정을 바꾸거나 다른 프로젝트를 선택하는 등 입력이 실제로 바뀐 경우에 사용하세요.

태그는 제어와 가시성을 위한 손잡이입니다. sync 같은 안정적인 태그를 사용하면 특정 ID를 추적하지 않고도 취소, 상태 조회, 로그 필터링을 할 수 있습니다. 수동 "지금 동기화" 동작에서는 이미 작업이 실행 중인지 확인하고 또 다른 워커를 실행하는 대신 명확한 메시지를 보여줄 수 있습니다.

정기 동기화와 필요 시 동기화가 서로 싸우지 않게 하세요. 분리하되 조정하세요:

  • 예약 작업에는 enqueueUniquePeriodicWork("sync_periodic", KEEP, ...)를 사용하세요.
  • 필요 시 동기화에는 enqueueUniqueWork("sync", KEEP, ...)를 사용하세요.
  • 워커 내에서 업로드할 것이 없으면 빠르게 종료해 정기 실행이 저렴하게 유지되도록 하세요.
  • 선택사항으로 정기 워커가 동일한 일회성 고유 동기화를 enqueue 하게 해 실질 작업은 한 곳에서만 일어나게 할 수 있습니다.

이 패턴은 백그라운드 동기화를 예측 가능하게 만듭니다: 한 번에 하나의 동기화, 취소와 관찰이 쉬움.

단계별: 실전 백그라운드 동기화 파이프라인

Generate a backend that scales
Generate a production-ready backend in Go and keep your sync contract stable as requirements change.
Create Backend

작업 항목을 먼저 로컬에 보관하고 WorkManager가 조건이 맞을 때만 앞으로 이동시키는 작은 상태 머신으로 다루면 신뢰 가능한 파이프라인을 더 쉽게 만들 수 있습니다.

배포 가능한 간단한 파이프라인

  1. 로컬 "큐" 테이블로 시작하세요. 재개에 필요한 최소 메타데이터를 저장합니다: 항목 id, 타입(폼, 사진, 메모), 상태(pending, uploading, done), 시도 횟수, 마지막 오류, 다운로드용 커서 또는 서버 수정 번호.

  2. 사용자가 누르는 "지금 동기화"에 대해서는 실제 조건에 맞는 제약조건과 함께 OneTimeWorkRequest를 enqueue 하세요. 일반적인 선택은 네트워크 연결 및 배터리 낮음 아님입니다. 업로드가 무거우면 충전 상태도 요구하세요.

  3. 업로드, 다운로드, 조정(reconcile)의 명확한 단계가 있는 CoroutineWorker 하나를 구현하세요. 각 단계를 증분으로 유지하세요. pending으로 표시된 항목만 업로드하고, 마지막 커서 이후의 변경만 다운로드한 뒤 단순 규칙으로 충돌을 해결하세요(예: 할당 필드는 서버 우선, 로컬 초안 메모는 클라이언트 우선).

  4. 백오프 재시도를 추가하되 재시도할 것과 실패로 처리할 것을 신중히 구분하세요. 타임아웃과 500번대는 재시도하되 401(로그아웃)은 빠르게 실패시켜 UI에 원인을 알리세요.

  5. UI와 알림을 위해 WorkInfo를 관찰하세요. "3 of 10 업로드" 같은 단계별 진행률 업데이트를 사용하고 재시도, 로그인, Wi‑Fi 연결 같은 다음 동작을 안내하는 짧은 실패 메시지를 보여 주세요.

val constraints = Constraints.Builder()
  .setRequiredNetworkType(NetworkType.CONNECTED)
  .setRequiresBatteryNotLow(true)
  .build()

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .setConstraints(constraints)
  .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
  .build()

큐를 로컬에 두고 워커 단계를 명확히 하면 작업은 일시 중단되었다가 재개되고 사용자에게 무엇이 일어났는지 추측하지 않고 설명할 수 있습니다.

흔한 실수와 함정(및 피하는 방법)

신뢰 가능한 동기화가 실패하는 가장 흔한 이유는 테스트 중에는 무해해 보이던 몇 가지 작은 선택들입니다. 목표는 가능한 한 자주 실행하는 것이 아니라 올바른 시점에 올바른 작업을 하고 할 수 없을 때는 깔끔하게 중지하는 것입니다.

주의할 함정

  • 제약조건 없이 대형 업로드를 바로 하는 것. 사진이나 큰 페이로드를 아무 네트워크, 아무 배터리 상태에서 바로 전송하면 사용자가 느낍니다. 네트워크 타입과 배터리 낮음 여부를 고려하고 큰 작업은 작은 청크로 나누세요.
  • 모든 실패를 영원히 재시도하는 것. 401, 만료된 토큰, 누락된 권한은 일시적 문제가 아닙니다. 이를 하드 실패로 표시하고 명확한 조치(재로그인)를 노출하세요. 타임아웃 같은 진짜 일시적 문제만 재시도하세요.
  • 우연히 중복을 만드는 것. 워커가 두 번 실행될 수 있다면 서버는 중복 생성을 보게 됩니다. 항목별로 안정적인 클라이언트 생성 ID를 사용하고 서버에서는 반복을 업데이트로 처리하세요.
  • 실시간이 필요한 곳에 정기 작업을 사용하는 것. 정기 작업은 유지관리용으로 적합하며 "지금 동기화"용으로는 적합하지 않습니다. 사용자 요청 동기화는 일회성 고유 작업으로 enqueue 하세요.
  • "100%"를 너무 일찍 보고하는 것. 업로드 완료는 데이터가 수락되어 조정된 것과 같지 않습니다. 단계별(queued, uploading, server confirmed)로 진행을 추적하고 서버 확인 후에만 완료로 표시하세요.

구체적 예: 기술자가 약한 신호의 엘리베이터 안에서 사진 3장과 폼을 제출하면 제약조건 없이 바로 시작하면 업로드는 멈추고 재시도가 폭주하며 앱 재시작 시 폼이 두 번 생성될 수 있습니다. 사용 가능한 네트워크로 제약하고, 단계를 나누어 업로드하며 각 폼에 안정적 ID를 키로 사용하면 이 시나리오는 서버에 하나의 깔끔한 레코드와 진실한 진행 메시지로 끝납니다.

배포 전 빠른 체크리스트

Add integrations the practical way
Connect auth, payments, messaging, and AI integrations without stitching together dozens of SDKs.
Build Integrations

출시 전에는 실제 현장 사용자가 기기를 어떻게 망가뜨릴지를 테스트하세요: 신호 끊김, 방전, 잦은 탭. 개발자 폰에서 괜찮아 보여도 스케줄링, 재시도, 상태 보고가 잘못되면 현장에서 실패할 수 있습니다.

다음 항목을 최소한 하나의 느린 기기와 하나의 최신 기기에서 확인하세요. 로그를 남기되 사용자가 보는 UI도 반드시 확인하세요.

  • 네트워크 없음 후 회복: 연결 끈 상태에서 동기화 시작 후 복구. 작업이 큐에 들어가고(빠르게 실패하지 않음) 중복 없이 나중에 재개되는지 확인.
  • 기기 재시작: 동기화 중 재부팅 후 앱 재오픈. 작업이 계속되거나 다시 예약되고 앱이 "동기화 중"에 갇히지 않는지 확인.
  • 낮은 배터리 및 낮은 저장공간: 배터리 절약 모드 활성, 배터리 임계값 이하로 떨어뜨리기(가능하면), 저장공간 거의 채우기. 작업이 기다려야 할 때 기다리고 조건이 좋아지면 중단 없이 계속되는지 확인.
  • 반복 트리거: "동기화" 버튼 여러 번 탭하거나 여러 화면에서 동기화를 트리거. 하나의 논리적 동기화 실행만 남고 병렬 워커가 같은 레코드를 놓고 경쟁하지 않는지 확인.
  • 설명 가능한 서버 실패: 500, 타임아웃, 인증 오류를 시뮬레이션. 재시도가 백오프하고 한도 후 중지하는지, 사용자가 "서버에 연결할 수 없음, 재시도 예정" 같은 명확한 메시지를 보는지 확인.

어떤 테스트라도 앱을 불분명한 상태로 남기면 버그로 간주하세요. 사용자는 느린 동기화를 용서하지만 데이터 손실이나 무슨 일이 일어났는지 모르는 것은 용서하지 않습니다.

예시 시나리오: 오프라인 폼 및 사진 업로드

Give ops a clear admin view
Add a web admin panel for ops teams so they can see what synced, what failed, and why.
Build Admin

기술자가 약한 커버리지 현장에 도착해 오프라인에서 서비스 폼을 작성하고 12장의 사진을 찍은 뒤 제출을 탭합니다. 앱은 먼저 모든 것을 로컬에 저장합니다(예: 로컬 데이터베이스): 폼 하나에 대한 레코드와 사진 하나당 레코드, 상태는 PENDING, UPLOADING, DONE, FAILED 등으로 명확히 합니다.

제출 시 앱은 중복 생성을 막기 위해 고유 동기화 작업을 enqueue 합니다. 일반적인 설정은 사진(크고 느린)을 먼저 업로드하고 첨부가 확인되면 폼 페이로드를 전송하는 WorkManager 체인입니다.

동기화는 현실적인 조건이 맞을 때만 실행됩니다. 예: 연결된 네트워크, 배터리 낮음 아님, 충분한 저장공간. 기술자가 지하에 있다면 아무 것도 배터리를 소모하며 백그라운드에서 반복되지 않습니다.

진행 상황은 명확하고 사용자 친화적입니다. 업로드는 포그라운드 작업으로 실행되어 "12장 중 3장 업로드 중" 같은 알림을 표시하고 명확한 취소 액션을 제공합니다. 취소하면 작업이 중단되고 남은 항목은 PENDING으로 남아 나중에 손실 없이 재시도할 수 있습니다.

재시도는 불안정한 핫스팟 이후에도 예의 있게 동작합니다: 첫 실패는 빠르게 재시도하지만 실패가 반복될수록 기다리는 시간이 늘어납니다(지수 백오프). 처음에는 반응이 빠르다가 점점 백오프해 배터리 소모와 네트워크 스팸을 줄입니다.

운영팀 관점에서 실용적 이득은 중복 제출 감소(항목이 멱등하고 고유 큐에 있기 때문), 명확한 실패 상태(어떤 사진이 실패했는지, 이유, 재시도 시점), 그리고 "제출됨"이 "안전하게 저장되어 동기화될 것"이라는 신뢰성입니다.

다음 단계: 신뢰성 먼저 배포하고 동기화 범위 확장

더 많은 동기화 기능을 추가하기 전에 "완료"가 무엇인지 명확히 하세요. 대부분의 현장 앱에서 "완료"는 "요청 전송"이 아니라 "서버가 수락하고 확인했음"이며 UI 상태가 현실과 일치하는 것입니다. "동기화됨"으로 표시된 폼은 앱 재시작 후에도 그대로 있어야 하고 실패한 폼은 다음에 무엇을 해야 할지 보여야 합니다.

사람들이 볼 수 있는 소수의 신호(그리고 지원팀이 물어볼 수 있는 정보)를 제공해 앱을 신뢰하기 쉽게 만드세요. 화면 전반에 걸쳐 단순하고 일관되게 유지하세요:

  • 마지막 성공 동기화 시간
  • 마지막 동기화 오류(짧은 메시지)
  • 보류 중 항목 수(예: 3개 폼, 12장 사진)
  • 현재 동기화 상태(Idle, Syncing, Needs attention)

관찰 가능성은 기능의 일부로 취급하세요. 약한 연결에서 누군가가 앱이 작동 중인지 모를 때 현장에서 수시간을 절약합니다.

백엔드와 관리 도구도 함께 만든다면 동기화 계약을 안정적으로 유지하는 데 도움이 됩니다. AppMaster (appmaster.io)는 production-ready 백엔드, 웹 관리 패널, 네이티브 모바일 앱을 생성할 수 있어 모델과 인증을 정렬하면서 까다로운 동기화 엣지에 집중할 수 있게 합니다.

마지막으로 소규모 파일럿을 진행하세요. 하나의 엔드투엔드 동기화 조각(예: 사진 1–2장 있는 검사 폼)을 선택해 제약조건, 재시도, 사용자 보이는 진행 상황을 완전히 작동하게 해서 출시하세요. 그 조각이 안정적이고 예측 가능해지면 기능을 하나씩 확장하세요.

자주 묻는 질문

What does “reliable background sync” actually mean in a field app?

Reliable background sync means work created on the device is saved locally first and will upload later without the user repeating steps. It should survive app kills, reboots, weak networks, and retries without losing data or creating duplicates.

When should I use OneTimeWorkRequest vs PeriodicWorkRequest for sync?

Use one-time work for anything triggered by a real event like “form saved,” “photo added,” or a user tapping Sync. Use periodic work for maintenance and as a safety net, but not as your only path for important uploads because its timing can drift.

Which Worker type should I choose: Worker, CoroutineWorker, or RxWorker?

If you’re in Kotlin and your sync code uses suspend functions, CoroutineWorker is the simplest and most predictable choice, especially for cancellation. Use Worker only for short blocking tasks, and use RxWorker only if the app is already built around RxJava.

Should I chain multiple workers or do everything in one worker?

Chain workers when steps have different constraints or should retry separately, like uploading large files on Wi‑Fi and then doing a small API call on any network. Use a single worker with clear phases when the steps share state and you want “all-or-nothing” behavior for one logical sync run.

How do I stop retries from creating duplicate records on the server?

Make each create/update request safe to run twice by using an idempotency key per item (often a UUID stored with the local record). If you can’t change the server, aim for upserts with stable keys or version checks so repeats don’t create new rows.

How do I make uploads resumable if the app is killed mid-sync?

Persist explicit local statuses like queued, uploading, uploaded, and failed so the worker can resume without guessing. Only mark an item done after the server confirms it, and store enough metadata (like file URI and attempt count) to continue after a crash or reboot.

What constraints are a good default for field app sync jobs?

Start with minimal constraints that protect users but still allow sync to run most days: require a network connection, avoid low battery, and avoid critically low storage. Be careful with “unmetered” and “charging” requirements because they can make sync never run for real field devices.

How should my app handle captive portals or “Wi‑Fi with no internet”?

Treat “connected but no internet” as a normal failure: time out quickly, return Result.retry(), and try later. If you can detect it during the request, show a simple message so the user understands why the device looks online but sync isn’t progressing.

What’s the safest retry strategy for spotty networks?

Use exponential backoff for network failures so retries become less frequent when coverage is bad. Retry timeouts and 5xx errors, fail fast on permanent problems like invalid requests, and cap attempts so you don’t loop forever when the user must take action (like signing in again).

How do I prevent “sync storms” and still show user-visible progress?

Enqueue sync as unique work so multiple triggers don’t start parallel jobs, and surface progress users can trust for long uploads. If the work is long-running or user-initiated, run it as foreground work with an ongoing notification that shows real counts and offers a clear cancel option.

쉬운 시작
멋진만들기

무료 요금제로 AppMaster를 사용해 보세요.
준비가 되면 적절한 구독을 선택할 수 있습니다.

시작하다