필자가 업무를 진행하면서 다양한 환경에 대해 고려하고 테스트하면서 업무를 진행했지만, 한 가지 놓쳤던 환경이 있다.
그것이 바로 BatterySaveMode(PowerSaveMode)인데, 한국 폰에서는 절전 모드라는 이름의 옵션으로 확인이 가능하다.
이 절전모드에 따라서 달라지는 부분으로 인해 오류가 발생하는 부분이 있었고, 다양한 환경에서 테스트를 해보다가 우연히 동료의 핸드폰에 절전모드가 되어있어 해당 문제의 원인을 발견하여 해결을 했다.
이번 글에서는 간단하게 절전 모드 옵션을 체크하는 방법과, 발생할 수 있는 문제에 대하여 확인해 보도록 하겠다.
우선,
절전 모드를 체크하는 방법과 옵션을 변경할 수 있는 방법을 알아보자.
절전모드를 체크하기 위해서는 PowerManager를 사용하여 체크를 해야 하므로, getSystemService를 사용하여 해당 객체를 가져오도록 하자.
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
해당 객체에서 절전모드의 여부를 확인하면 되는데, 필자는 이 시작 부분부터 헷갈렸던 것이 있다.
우리가 생각하는 절전 모드는 배터리를 절약하는 모드라고 볼 수 있는데, 이것이 PowerSaveMode 혹은 DozeMode로 볼 수 있다는 점이다.
우리가 의도한 모드는 PowerSaveMode이고, DozeMode는 화면이 꺼진 상태에서 일정시간 뒤 넘어갈 수 있는 절전 모드로 의도하고자 하는 바가 조금 다르다고 볼 수 있다.
간단하게 생각하면 DozeMode는 노트북에서 쉽게 볼 수 있는 SleepMode라고 생각해도 될 것이다.
이 DozeMode는 SDK 23, Android 6 버전인 marshmallow 버전부터 생긴 것으로 생각보다 꽤나 오래전에 생긴 옵션인데, 이것에 대해서 명확히 알고 있지 못했기 때문에 헷갈렸다.
이게 헷갈렸던 이유는 다음과 같다.
val isPowerSaveMode = powerManager.isPowerSaveMode
val isDozeMode = powerManager.isDeviceIdleMode
powerManager에서 가지고 올 수 있는 정보가 여러가지가 이 쓴데, 전자가 우리가 생각하는 절전 모드의 옵션 값이고, 후자가 필자가 착각했던 Doze 모드의 옵션 값이다.
이 두가지의 차이점을 알지 못하고 후자인 doze모드의 체크하는 부분만 확인하면서 왜 옵션 값이 바뀌지 않지?라는 생각을 했다.
아무튼, 다시 처음으로 돌아가서
fun checkPowerSaveMode(
context: Context,
) {
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val isPowerSaveMode = powerManager.isPowerSaveMode
if (isPowerSaveMode) {
Log.d("checkPowerSaveMode", "Power Save Mode is ON")
} else {
Log.d("checkPowerSaveMode", "Power Save Mode is OFF")
}
}
해당 함수를 통해 현재 기기가 절전 모드인지 아닌지의 여부를 확인할 수 있게 된다.
checkPowerSaveMode(
context = context,
)
단순하게 이와 같이 호출을 하면, 호출 당시의 절전 모드 여부를 파악하여 해당하는 로그를 찍어 줄 것이다.
하지만, 언제나 그렇듯이 해당 함수를 호출할 당시의 절전 모드 여부 파악도 중요하지만 절전 모드가 변경되었을 때도 실시간으로 확인하여 데이터를 변경할 수 있는 방법이 더 유용하게 쓰일 것이다.
절전 모드 여부를 실시간으로 받아와서 확인하기 위해서는 broadcastReceiver를 사용하면 된다.
절전 모드가 변경될 때 안드로이드 시스템은 PowerManager.ACTION_POWER_SAVE_MODE_CHANGED 라는 브로드 캐스트 인텐트를 보내기 때문에, 이것을 수신하여 데이터를 확인해 보면 되는 것이다.
즉,
private lateinit var powerSaveModeReceiver: BroadcastReceiver
BroadcastReceiver를 선언해주고,
powerSaveModeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) {
checkPowerSaveMode(
context = context,
)
}
}
이처럼 receive 함수를 override하여 받아오면 된다.
절전 모드가 변경될 때 PowerManager.ACTION_POWER_SAVE_MODE_CHANGED 라는 브로드 캐스트 인텐트를 보내기 때문에 인텐트에서 전달받은 action이 해당 값인지 확인 후, 맞으면 절전 모드가 변경되었다는 것으로 판단하고 한번 더 체크하도록 하면 된다.
이렇게 절전 모드 옵션 값에 대해 가져올 수 있도록 설정했으니, 필자가 어떠한 문제가 있어서 절전 모드 여부를 체크하게 됐는지 알아보고 수정할 차례이다.
결론부터 말하자면,
background 상태에서 동작하는 coroutineWorker를 사용하는 과정에서 발생한 문제 때문이다.
절전 모드가 활성화 되어 있을 때, coroutineWorker를 통해 동작을 수행시키고, 앱을 background 상태로 넘기면 해당 coroutineWorker가 비정상적으로 동작하는 이슈가 있었다.
따라서, 이번 예제에서도 동일하게 비정상적으로 동작하는지 확인해 보도록 하자.
val uniqueWorkTag = "unique_work_tag"
val uniqueWorkRequest = OneTimeWorkRequest.Builder(BackGroundWorker::class.java)
.addTag(uniqueWorkTag)
.build()
가장 기본적인 OneTimeWorkRequest를 만들고,
Button(
modifier = Modifier.padding(top = 10.dp),
onClick = {
WorkManager.getInstance(context).enqueue(uniqueWorkRequest)
}) {
Text(text = "Call Unique WorkManager")
}
버튼을 통해 해당 worker가 동작하도록 한다.
override suspend fun doWork(): Result {
...
for (item in 0..30) {
Log.d("doWork", "doWork() time : $item")
delay(1000L)
}
...
return Result.success()
}
해당 coroutineWorker는 실행되면 30초 동안 1초에 한 번씩 숫자를 log에 표기하도록 한다.
이렇게 구현을 해둔 다음에, 절전 모드를 키고 coroutineWorker를 수행한 후 background로 앱을 넘기고 로그를 확인해 보자.
정상적으로 동작한다.
뭔가 이상하다고 생각하고, 여러 번 테스트해보다가 문득 머리를 스쳐 지나간 것이 있었다.
절전 모드를 활성화시키면 시스템 효율을 높이기 위해 여러 가지 동작이 수행될 것이다.
그중에는 전력 소모를 줄이기 위해 조금 더 타이트하게 메모리 관리를 하게 되는데, 실제 운영 중인 앱의 서비스가 상당히 무겁게 돌아가는 서비스이고, 이 샘플 앱은 엄청 단순하며 coroutineWorker로 돌리는 작업도 가볍다.
즉, background 상태에서 메모리의 사용량이 일정 수치를 넘어갔을 때 작업을 중단하여 메모리 사용량을 낮은 상태로 유지시키는 것이 아닐까?
라는 생각이다.
그래서 확인해 보도록 하였다.
우선 현재 샘플 앱에서 coroutineWorker를 사용할 때의 메모리이다.
뭐 단순한 기능들의 샘플밖에 없기도 하고, 메모리를 잡아먹을만한 것들이 아무것도 존재하지 않기 때문에 이처럼 평균적으로 160mb 정도의 메모리를 사용하고 있었다.
같은 환경으로 실제 운영 중인 앱에서 coroutineWorker를 사용할 때를 체크해 보았다.
백그라운드에서 영상 파일을 컨트롤하는 부분이기 때문에 상당히 무거운 작업을 하고 있다.
그렇기 때문인지 해당 작업을 시작하고부터는 1.1gb의 메모리를 사용하고 있다.
해당 부분은 절전 모드를 해제한 상태에서 background로 체크했을 때의 메모리 사용량인데,
이제 절전 모드를 켜고 동일한 작업을 해보자.
우선 샘플 앱은 절전 모드를 켜지 않았을 때와 완전히 동일한 메모리 사용량을 보여준다.
절전 모드의 유무에 따라서 별도로 메모리를 정리할 필요가 없을 정도의 적은 용량을 사용하고 있기 때문이다.
하지만, 실제 운영 중인 앱에서 확인해 보면 다음과 같은 메모리 사용량을 볼 수 있다.
foreground 환경에서는 절전 모드가 아닐 때와 동일하게 1gb가 넘는 메모리를 사용하다가,
background 환경으로 넘기자마자 271mb로 확 낮아진 메모리 사용량을 보여준다.
즉,
절전 모드를 활성화시킨 상태에서 메모리 사용량이 일정량 이상 될 때,
앱을 백그라운드 상태로 넘기게 되면 알아서 효율을 높이기 위해 작업을 중단시켜 메모리 사용량을 줄인다는 것이 확인되었다.
따라서,
절전 모드를 활성화 비활성화 시키는 것에 대해 실제 foreground에서만 동작하는 경우에는 체크하지 않아도 사용자 flow에는 아무런 상관이 없다.
하지만 필자처럼 무거운 작업을 한다던지, 기본적으로 서비스가 무거울 경우에 background로 처리해야 할 것들이 있다면 절전 모드 여부를 확인하는 것이 좋을 것으로 보인다.
Button(
modifier = Modifier.padding(top = 10.dp),
onClick = {
if (isPowerSaveMode.value) {
WorkManager.getInstance(context).enqueue(uniqueWorkRequest)
} else {
startBatterySaverSetting(context)
}
}) {
Text(text = "Call Unique WorkManager")
}
필자는 이처럼 버튼을 눌렀을 때 실시간으로 절전 모드 여부를 체크하여 데이터를 저장하도록 하였고,
해당 값을 기반으로 worker를 실행시킬지 옵션을 켜서 절전 모드를 끄도록 할지 선택하여 보여주는 것으로 작업을 하였다.
이것으로 간단하게 절전 모드를 체크하는 방법과,
필자가 경험한 이슈를 토대로 유의해야 할 점을 알아보았다.
유의해야 할 점을 정리하자면,
절전 모드일 때,
coroutineWorker를 사용한 background 작업을 통해 비정상적인 동작을 야기할 수 있다는 것을 예제로 확인할 수 있었지만, background로 앱이 넘어가고 일정 시간이 지난 후에 메모리 사용량이 줄어드는 것이 아닌 2~3초 이내로 메모리 사용량이 줄어들면서 해당 worker가 멈춘 것이다.
이것으로 미루어보아 다른 작업을 수행중일 때 잠깐이라도 앱을 백그라운드로 넘어갔다가 돌아왔을 때를 생각하여 이후 data flow를 구현해 주는 것이 앱의 안정성을 높이는 것에 도움을 줄 것이다.
필자는 지금까지 정말 배터리가 부족할 때 급하게 변경하는 것을 제외하고 절전 모드를 켜본 적이 없었다.
그렇기 때문에 다양한 환경을 생각한다고 테스트를 했지만 절전 모드라는 것에 대해서는 완전히 놓치고 있었다.
이번 기회를 통해 메모리 사용량과 절전 모드에 대해서 다시 한번 생각할 수 있었고,
늘 절전 모드를 켜놓고 생활하는 동료 덕분에 빠르게 이슈를 확인할 수 있어 고마울 따름이었다.
이번 기회를 통해,
조금 더 넓은 시야로 놓치는 것 없이 확인하고 개발하여 조금 더 안정성 높은 서비스를 만들 수 있도록 해야겠다고 생각한다.
해당 게시글에 사용한 예제는 Github에 올려두었다.
https://github.com/HeeGyeong/ComposeSample
'Android > Utility' 카테고리의 다른 글
[Android] Lottie Animation을 적용해보자. (2) | 2024.09.17 |
---|---|
[Android] CoroutineWorker (WorkManager)를 사용하는 방법과 다양한 옵션. (0) | 2024.07.11 |
[Android] Network 연결 여부를 화면과 API에서 체크하는 방법. (0) | 2024.06.03 |
[Android] 정책 변경 후 구글 플레이스토어 개발자 계정 생성부터 신규 앱 배포까지 과정 정리 - 2주간 테스터 20명 유지하기 (24) | 2024.03.16 |
[Android] 음성 녹음을 하고, 저장해보자. (2) | 2024.01.31 |