안드로이드 화면 꺼짐 방지 FLAG_KEEP_SCREEN_ON 및 WakeLock 적용 방법

이래저래 시험 타이머나 운동 기록, 지도 내비게이션 같은 앱을 개발하다 보면 사용자가 화면을 가만히 쳐다보고 있어야 하는 상황이 자주 생깁니다. 이때 대충 구현하면 스마트폰 설정에라 맞춰진 화면 자동 꺼짐 시간 때문에 화면이 픽 꺼져버려서 사용자가 불편을 겪게 됩니다.

처음에는 그냥 "화면 안 꺼지게 백그라운드에서 계속 돌리면 되겠지" 하고 접근하기 쉬운데, 운영하다 보면 배터리가 광탈하거나 앱을 껐는데도 화면이 계속 켜져 있는 등 온갖 버그성 문제가 터지곤 합니다. 저의 경우도 타이머 앱을 만들 때 화면 유지와 백그라운드 상태 관리를 제대로 분리하지 못해 고생했던 기억이 있습니다.

자꾸 까먹기도 하고 나중에 프로젝트할 때 바로 복사해서 쓰려고 실무 기준으로 핵심만 정리해 둡니다.

헷갈리기 쉬운 핵심 개념: 화면 유지 vs CPU 작업 유지

가장 먼저 확인해야 할 점은 화면만 계속 켜둘 것인가, 아니면 화면이 꺼져도 내부 CPU 연산(작업)을 계속 돌릴 것인가를 명확히 구분하는 것입니다. 이걸 혼동하면 안 써도 되는 무거운 권한을 쓰거나 배터리 이슈가 발생합니다.

저의 경우 아래 표를 기준으로 상황에 맞게 기술을 선택해 적용하고 있습니다.

구분목적대표 방식적합한 상황주의할 점
화면 유지현재 화면을 계속 보이게 함FLAG_KEEP_SCREEN_ON타이머, 영상, 지도 화면화면을 벗어나면 해제 필수
CPU 작업 유지화면이 꺼져도 작업 계속 실행WakeLock, Foreground Service녹음, 위치 추적, 긴 다운로드배터리 소모 및 구글 정책 검토
예약/재시도 작업조건 충족 시 백그라운드 실행WorkManager서버 데이터 동기화, 업로드즉시 실행 보장 안 됨
상태 복구앱 재진입 시 시간 재계산저장 시각 + 현재 시각 비교시험 타이머, 카운트다운화면 유지와 별개로 설계 필요

단순히 타이머 숫자를 화면에 계속 보여줘야 하는 수준이라면 복잡하게 생각할 것 없이 FLAG_KEEP_SCREEN_ON 하나면 충분합니다.

가장 안전한 FLAG_KEEP_SCREEN_ON 적용 코드

이 방식은 Activity Window에 플래그를 주는 방식입니다. 위험 권한을 따로 요구하지도 않고, 해당 화면(Activity)이 보일 때만 동작하므로 배터리 측면에서 가장 안전합니다.

그냥 onCreate에 때려 박기보다는, 타이머가 실제로 '실행 중'일 때만 화면을 켜두고 일시정지나 종료 시에는 플래그를 걷어내는 방식으로 구현하셔야 합니다. 아래는 제가 실무에서 사용하는 구조입니다.

Kotlin
class TimerActivity : AppCompatActivity() {

    private var isTimerRunning = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_timer)

        renderTimerState()
    }

    private fun startTimer() {
        isTimerRunning = true
        updateKeepScreenOn()
        // 타이머 실행 로직...
    }

    private fun pauseTimer() {
        isTimerRunning = false
        updateKeepScreenOn()
        // 타이머 일시정지 로직...
    }

    private fun finishTimer() {
        isTimerRunning = false
        updateKeepScreenOn()
        // 타이머 종료 로직...
    }

    private fun updateKeepScreenOn() {
        if (isTimerRunning) {
            // 화면 유지 플래그 추가
            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        } else {
            // 화면 유지 플래그 제거
            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        }
    }

    override fun onPause() {
        super.onPause()
        // 사용자가 홈 버튼을 누르거나 다른 화면으로 벗어나면 무조건 플래그 해제
        window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    }

    override fun onResume() {
        super.onResume()
        // 다시 앱으로 돌아왔을 때 타이머 상태에 맞춰 플래그 재설정
        updateKeepScreenOn()
    }

    private fun renderTimerState() {
        updateKeepScreenOn()
    }
}

주의하셔야 합니다: 단순히 onCreate에서 플래그를 켜고 방치하면, 타이머가 다 끝났는데도 사용자가 수동으로 화면을 끄기 전까지 계속 켜져 있는 불상사가 생깁니다. "언제 켜는가"보다 **"언제 끄는가"**를 더 꼼꼼하게 처리해야 욕을 먹지 않습니다.

WakeLock은 웬만하면 피하고, 쓸 때는 신중하게

WakeLock은 기기의 CPU를 강제로 깨워두는 강력한 도구입니다. 가끔 화면 꺼짐 방지용으로 WakeLock을 난사하는 코드가 보이는데, 이건 release 처리가 누락되거나 예외로 튕겼을 때 백그라운드에서 배터리를 좀먹는 주범이 됩니다.

실무에서 검토할 때 WakeLock은 정말 마지막 수단으로 생각하시는 게 좋습니다. 굳이 써야 한다면 반드시 제한 시간(Timeout)을 걸고, try-finally 블록으로 확실하게 해제해 줘야 안전합니다.

Kotlin
class ShortTaskRunner(context: Context) {
    private val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
    private val wakeLock = powerManager.newWakeLock(
        PowerManager.PARTIAL_WAKE_LOCK,
        "example:short-task" // 디버깅용 태그
    )

    fun runShortTask() {
        // 최대 10분 제한을 두고 락을 획득 (release 누락 방지용 최소한의 안전장치)
        wakeLock.acquire(10 * 60 * 1000L)
        try {
            // 화면이 꺼져도 끊기면 안 되는 짧고 명확한 백그라운드 작업 수행
        } finally {
            // 작업이 끝나거나 에러가 나도 무조건 release 호출
            if (wakeLock.isHeld) {
                wakeLock.release()
            }
        }
    }
}

시험 타이머 앱을 만들 때 권장하는 정석 구조

시험 타이머나 공부 타이머처럼 1시간, 2시간 길게 흘러가는 앱이라면 "화면을 무조건 켜두거나 서비스를 계속 돌려서 초 단위를 재야지" 하면 안 됩니다. OS가 배터리 절약을 위해 언제 앱을 죽일지 모르기 때문입니다.

저의 경우는 아래 방식으로 구조를 쪼개서 설계합니다.

  1. 타이머를 시작할 때 기준 시각(startedAt), 전체 시간(durationMs), 종료 예정 시각(expectedEndAt)을 로컬 DB나 Preferences에 바로 저장한다.

  2. 사용자가 앱을 열고 타이머 화면을 보고 있는 동안에는 FLAG_KEEP_SCREEN_ON을 켜서 화면 유지를 도와준다.

  3. 사용자가 홈 버튼을 누르거나 화면을 수동으로 꺼서 백그라운드로 가면, 백그라운드 코드를 돌리는 대신 그냥 내버려 둔다.

  4. 사용자가 한참 뒤에 다시 앱을 켰을 때(onResume), [현재 시각 - expectedEndAt]을 계산해서 남은 시간을 UI에 다시 그려준다.

  5. 만약 백그라운드에 있을 때 시간이 다 끝나는 시점이라면 AlarmManager를 통해 종료 알림 팝업만 정확하게 띄워준다.

이렇게 설계하면 화면 유지는 필요한 순간에만 깔끔하게 쓰고, 백그라운드에서는 배터리를 단 1%도 쓰지 않으면서 타이머 오차는 0초로 완벽하게 제어할 수 있습니다.

실무 운영 전 체크리스트

  • [ ] 화면 유지가 필요한 특정 Activity 단위로만 플래그를 제어하고 있는가?

  • [ ] 타이머가 일시정지 되거나 완료되었을 때 화면 유지 플래그를 꺼주는가?

  • [ ] onPause() 생명주기에서 플래그 해제 처리를 누락하지 않았는가?

  • [ ] 혹시 단순 화면 꺼짐 방지 목적으로 무거운 WakeLock을 남용하진 않았는가?

  • [ ] WakeLock 사용 시 try-finally 문으로 release()를 100% 보장했는가?

  • [ ] 샘플 코드나 로그에 실제 패키지명, 서버 주소, 개인식별 값을 남겨두지 않았는가?

짧은 마무리

안드로이드 개발을 하다 보면 화면 유지 같은 기능은 구현하기 너무 쉬워서 대충 넘어가기 쉽습니다. 하지만 실무 환경이나 실제 배포 운영 단계로 가면 배터리 소모 주범으로 찍혀 삭제 유도 1순위 앱이 되기 딱 좋은 기능이기도 합니다.

"계속 켜두는 것"보다 "필요 없을 때 확실히 꺼서 시스템에 제어권을 돌려주는 것"이 훨씬 중요하다는 사실을 기억하시면 안정적인 구조를 만드는 데 도움이 될 것입니다.

댓글

이 블로그의 인기 게시물

안드로이드 백그라운드 타이머 구현 및 Foreground Service 알림 설계 정리

관광 앱 다국어 데이터 모델 및 검색 색인 설계 (자꾸 까먹어서 정리)