Google Play의 새 배터리 정책, WorkManager의 숨겨진 wake lock, 그리고 다시 foreground service로 돌아간 이야기.
무슨 일이 있었나
2025년 11월, Google이 Android Vitals에 새로운 core vitals 지표를 추가했다. "Excessive partial wake locks" — 과도한 부분적 wake lock 사용.
기준은 이렇다. 24시간 중 partial wake lock 누적 사용 시간이 2시간을 넘으면 해당 세션을 "excessive"로 판단한다. 28일 동안 전체 사용자 세션의 5%가 이 기준을 넘으면, Play Store 추천에서 제외되고 스토어 페이지에 "배터리를 많이 소모하는 앱"이라는 경고가 붙는다.
2026년 3월 1일부터 시행. Samsung과 공동 개발한 지표라고 한다.

금융이나 보안처럼 백그라운드에서 오래 살아야 하는 앱을 만드는 입장에서, 이건 무시할 수 없는 변경이었다. Play Console을 열어봤다.
Play Console을 열어보니
Android Vitals > 개요 하단에 배터리 관련 섹션이 생겨있었다. "불필요한 부분적인 wake lock" 항목.
확인해보니 실제로 집계가 잡혀 있었다. 2026년 3월 30일 기준 2.4%. 아직 5% 기준 아래이긴 한데, 편하게 있을 수 있는 수치는 아니다.

근데 이상한 점이 하나 있었다. 코드에서 PowerManager를 직접 쓴 적이 없다. PARTIAL_WAKE_LOCK을 acquire하는 코드가 앱 어디에도 없다. Google의 공식 문서에는 wake lock의 원인으로 PowerManager.newWakeLock()을 명시하고 있는데, 우리 앱에서 이걸 호출하는 곳이 없다.
그러면 이 2.4%는 어디서 나온 거지?
범인은 WorkManager였다
Play Console 상세 페이지에서 wake lock 이름별 breakdown을 확인할 수 있다. 거기서 눈에 띈 태그가 하나 있었다.
WorkManager: ProcessorForegroundLck
WorkManager? 설마 싶어서 AOSP 소스를 직접 뜯어봤다.
androidx.work.impl.Processor.java의 startForeground() 메서드:
@Override
public void startForeground(@NonNull String workSpecId,
@NonNull ForegroundInfo foregroundInfo) {
synchronized (mLock) {
Logger.get().info(TAG, "Moving WorkSpec (" + workSpecId + ") to the foreground");
WorkerWrapper wrapper = mEnqueuedWorkMap.remove(workSpecId);
if (wrapper != null) {
if (mForegroundLock == null) {
mForegroundLock = WakeLocks.newWakeLock(mAppContext, FOREGROUND_WAKELOCK_TAG);
mForegroundLock.acquire();
}
mForegroundWorkMap.put(workSpecId, wrapper);
Intent intent = createStartForegroundIntent(mAppContext,
wrapper.getWorkGenerationalId(), foregroundInfo);
ContextCompat.startForegroundService(mAppContext, intent);
}
}
}
WakeLocks.newWakeLock() → acquire(). WorkManager가 foreground worker로 전환할 때 내부적으로 partial wake lock을 잡고 있었다. 개발자가 명시적으로 wake lock을 요청하지 않아도, setForegroundAsync()를 쓰는 순간 WorkManager 내부에서 wake lock이 잡힌다.
이 wake lock은 worker의 작업이 끝나면 해제된다. 짧은 작업이라면 문제가 안 될 수 있다. 근데 우리 앱처럼 장시간 foreground에서 살아야 하는 구조라면? worker가 살아있는 동안 wake lock도 계속 유지된다. 그게 2시간을 넘기면 Android Vitals에 excessive로 집계된다.
결국 문제는 "WorkManager를 쓴다"가 아니라 "오래 살아야 하는 작업을 WorkManager의 foreground worker로 돌린다"는 조합에 있었다.
문서가 흩어져 있다
여기까지 오는 데 시간이 꽤 걸렸다. 왜냐면 이 사실을 파악하려면 최소 4~5개의 문서를 조합해야 하기 때문이다.
하나씩 짚어보면 이렇다.
Android Vitals — excessive wake lock 문서에는 PARTIAL_WAKE_LOCK 사용에 대한 이야기만 적혀있다. WorkManager 관련 언급은 없다.
wake lock 디버그 가이드까지 들어가면 이런 문장이 나온다:
"일부 API는 앱에 기여하는 절전 모드 해제 잠금을 획득합니다. 코드를 명시적으로 작성하지 않아도 앱이 절전 모드 해제 잠금을 사용할 수 있습니다."
이걸 좀 더 파면 "다른 API에서 생성된 wake lock 식별" 문서에서 WorkManager, JobScheduler 등이 내부적으로 wake lock을 사용한다는 내용이 나온다.
WorkManager long-running worker 문서에는 한 단계 더 직접적으로, WorkManager가 foreground service를 만들 때도 내부적으로 JobScheduler를 사용하고, Android 16부터는 long-running worker가 앱의 job quota를 소진할 수 있으니 direct foreground service를 고려하라고 적혀있다.
Android 16 behavior changes 문서에도 같은 내용이 다른 맥락으로 한 번 더 나온다. foreground service와 동시에 실행되는 작업도 job runtime quota를 준수해야 한다고.
정리하면, 플랫폼 변경 요약 + WorkManager 문서 + Vitals 문서 + 디버그 가이드 + AOSP 소스를 전부 봐야 전체 그림이 보이는 구조다. 어느 하나만 보면 알 수 없다.
실무에서 이런 식으로 문서가 흩어져 있으면 진짜 골치 아프다.
왜 WorkManager로 갔었는지
여기서 약간의 배경 설명이 필요하다. 우리 앱은 원래 foreground service를 사용하고 있었다. 근데 foreground service는 Android 12 이후로 제약이 계속 쌓여왔다.
Android 12부터 앱이 백그라운드에 있을 때 foreground service 시작이 원칙적으로 막혔다. 예외가 있을 때만 가능하게 바뀐 거다.
실제로 문제가 터졌다. BroadcastReceiver에서 서비스를 foreground로 전환하려고 할 때 ForegroundServiceStartNotAllowedException이 발생했다. Activity나 Notification 같은 UI가 존재하는 상태에서의 foreground service 전환은 예외에 해당하지만, BroadcastReceiver에서 직접 전환하는 건 해당하지 않았다.
foreground service는 원래부터 제약이 많은 API다. 시작 타이밍에 제한이 있고, 타입 선언이 필요하고, 몇 초 안에 startForeground()를 호출하지 않으면 예외가 터진다. 앞으로도 이런 제약이 계속 강화될 거라고 판단했고, WorkManager가 이런 복잡한 제약을 내부적으로 처리해줄 거라고 기대해서 일괄 전환했다. 당시로서는 합리적인 판단이었다.
근데 지금 와서 보면, WorkManager가 "제약을 대신 처리해준다"는 건 맞는데, 그 대가로 내부에서 wake lock을 잡는다는 걸 몰랐다. 제약을 피하려고 간 곳에서 다른 문제가 생긴 셈이다.
근데 지금은 상황이 다르다
foreground service의 제약은 Android 12 이후로도 계속 추가됐다. Android 14에서는 foreground service 타입 선언이 사실상 필수가 됐고, Android 15에서는 dataSync, mediaProcessing 같은 특정 타입에 시간 제한이 들어갔다. foreground service를 쓰기가 점점 까다로워진 거다.
근데 WorkManager 쪽도 상황이 좋지 않다.
Android 16에서는 foreground service와 함께 돌아가는 job도 quota를 소모하게 됐다. WorkManager가 내부적으로 JobScheduler를 쓰니까, long-running worker를 돌리면 앱 전체의 job quota가 줄어든다. 그리고 공식 문서가 이렇게까지 적고 있다: long-running worker의 경우 foreground service를 고려하라.
startForegroundService() 후 몇 초 안에 startForeground()를 호출하지 않으면 ForegroundServiceDidNotStartInTimeException이 나는 문제도 있는데, 이건 WorkManager의 foreground worker에서도 발생할 수 있고, 공식 문서는 관련 충돌이 WorkManager 2.10.5에서 수정됐다고까지 적고 있다.
정리하면 이렇다. foreground service의 제약이 많아서 WorkManager로 갔는데, WorkManager도 wake lock 문제 + job quota 문제 + foreground service 계열 예외 문제에서 자유롭지 않다. 양쪽 다 제약이 있는 상황에서, 어느 쪽이 우리 앱에 더 맞는지를 다시 판단해야 했다.
판단: 다시 foreground service로
그러면 다시 foreground service로 돌아가는 게 맞을까? 제약이 많아서 떠났던 건데.
상황을 다시 보니 우리 앱의 flow에서는 foreground service가 가능하다. 웹이 intent scheme으로 앱 Activity를 띄우고, 그 Activity 안에서 서비스를 시작하는 구조다. 사용자가 UI를 조작한 직후에 서비스가 시작되니까, Android 12+의 백그라운드 foreground service 시작 제한에 해당하지 않는다. 원래 WorkManager로 전환했던 이유였던 BroadcastReceiver 경유 문제가, 이 flow에서는 발생하지 않는다.
foreground service의 제약(타입 선언, 시작 타이밍, 5초 안에 startForeground() 호출 등)은 여전히 있다. 근데 WorkManager를 쓰더라도 어차피 같은 계열의 문제(ForegroundServiceDidNotStartInTimeException 등)가 발생할 수 있고, 거기에 wake lock 문제까지 추가로 생긴 거라면, 차라리 foreground service를 직접 제어하는 게 낫다.
foreground service 자체의 소스코드를 확인해봤는데, Service의 startForeground() 내부에는 WorkManager처럼 wake lock을 자동으로 잡는 코드가 없었다. 즉 foreground service로 전환하면 ProcessorForegroundLck은 당연히 사라지고, 서비스 코드 내에서 wake lock을 직접 잡지 않는 이상 excessive partial wake lock에 집계되지 않을 가능성이 높다.
다만 이게 100% 안전하다고 단언하기는 어렵다. 서비스 내부에서 사용하는 다른 API(네트워크 I/O, 센서 등)가 간접적으로 wake lock을 잡을 수도 있고, OEM별로 동작이 다를 수도 있다. 전환 후에도 Play Console에서 수치를 지속적으로 모니터링해야 한다.
핵심은 이거다. WorkManager의 foreground worker는 개발자가 요청하지 않아도 내부에서 알아서 wake lock을 잡는다. 이걸 끌 수 있는 옵션도 없다. 반면 foreground service는 wake lock을 잡을지 말지를 개발자가 직접 결정할 수 있다. "더 안전한 도구"로 바꾸는 게 아니라, wake lock에 대한 통제권을 가져오는 것이다.

정리
돌아보면 문제의 본질은 "WorkManager가 나쁘다"가 아니었다. 오래 살아야 하는 작업을, wake lock을 자동으로 잡는 도구 위에 올렸다는 거다. 짧은 백그라운드 작업에 WorkManager를 쓰는 건 여전히 좋은 선택이다. 근데 장시간 foreground 유지가 필요한 구조에서는, wake lock이 알아서 잡히고 알아서 유지되는 게 오히려 독이 된다.
확인하면서 정리한 핵심 내용:
- Google Play의 새 배터리 정책은 2026년 3월 1일부터 시행. excessive partial wake lock이 28일간 사용자 세션의 5%를 넘으면 Play Store에 경고가 뜬다.
- WorkManager의 foreground worker는
setForegroundAsync()호출 시 내부적으로 partial wake lock을 잡는다. worker가 살아있는 동안 유지되고, 끝나면 해제된다. long-running이면 계속 유지. - 작업 스케줄링(JobScheduler, WorkManager 등)은 내부적으로 wake lock에 연관되어 있다.
PowerManager를 직접 쓰지 않아도 Android Vitals에 집계될 수 있다. - direct FGS 자체는 wake lock을 자동으로 생성하지 않는다. 다만 내부 구현에 따라 다른 경로로 wake lock이 발생할 수 있으므로, 전환 후에도 모니터링은 필요하다.
- FGS 2시간 실행 ≠ excessive wake lock 집계. partial wake lock이 실제로 잡혀 있어야 집계된다.
- 도구를 바꿔서 해결한 게 아니다. wake lock에 대한 통제권을 가져온 것이다.
- 이 전체 그림을 파악하려면 Google 문서 4~5개를 조합해야 한다. 하나만 봐서는 알 수 없다.
장기적으로 백그라운드 작업이 필요한 앱을 만들고 있다면, 한번 Play Console의 Android Vitals를 확인해보는 걸 권한다. ProcessorForegroundLck 같은 태그가 잡혀있다면, 이 글의 내용이 도움이 됐으면 한다.
참고 자료
'모바일' 카테고리의 다른 글
| Android NDK 입문 (2) - SIGSEGV, SIGABRT, SIGBUS 네이티브 크래시 시그널 정리 (0) | 2026.05.23 |
|---|---|
| Android NDK 입문 (1) - NDK를 왜 쓰는가, JNI와 네이티브 개발 기초 (0) | 2026.05.23 |
| Google Play 데이터 보안: AdMob 사용 시 "기기 또는 기타 ID 선언되지 않음" 경고 해결 (0) | 2026.05.23 |
| Android 개발자 인증 정리 - Play Console 등록 가이드 (0) | 2026.04.19 |
| 1인 개발 앱 출시 3개월 후기 - 다운로드 10회, 매출 0원에서 배운 것 (0) | 2026.02.10 |
