Android PowerManager.WakeLock
일반적으로 Android 는 애플리케이션이나 서비스에 전력이 필요하지 않으면 CPU도 전력을 소모하지 않게 설계되어 있습니다.
따라서 화면이 꺼진 뒤 일정 시간이 지나면 CPU 가 딥 슬립(deep sleep) 상태로 전환됩니다.
딥 슬립 상태로 전환되면 백그라운드에서 동작하는 서비스도 CPU를 사용하지 못하기 때문에, 아래와 같은 일이 발생할 수 있습니다.
1. BroadcastReceiver 클래스의 onReceive() 메서드가 호출 되었지만, 관련 로직을 수행하지 않음.
2. AlarmManager 클래스를 사용하여 트리거가 발생 하였지만, CPU 가 실행상태가 아니므로 이후 로직을 사용하지 않음.
이를 방지하기 위해 Android 에서는 PowerManager.WakeLock 클래스를 제공하여 CPU가 딥 슬립상태에서 실행 상태로 전환되어 유지될 수 있도록 합니다. 다만, Device 의 베터리 수명은 이 API 를 어떻게 사용하느냐에 따라 크게 달라지기 때문에, PowerManager.WakeLock 은 정말 필요한 경우가 아니면 사용하지 말아야 하고, 만약 사용 하더라도 가능한 최소 수준을 사용하고, 최대한 빨리 release 해주어야 합니다.
WakeLock 을 사용하기 위한 API 는 newWakeLock() 입니다. 이 함수는 PowerManager.WakeLock object 를 생성 합니다. 이 객체를 사용하여 Device 의 Power 를 제어할 수 있습니다.
Application 에서 이를 사용하는 방법은 아주 간단 합니다.
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
wl.acquire();
..screen will stay on during this section..
wl.release();
newWakeLock 에 사용되는 Flag 의 종류는 아래와 같이 4가지가 있습니다.
PARTIAL_WAKE_LOCK 을 사용하면 스크린 상태에 관계 없이, 사용자가 전원 버튼을 눌러 단말을 sleep 시켜도 계속 CPU는 실행상태에 있습니다.
다른 PARTIAL_WAKE_LOCK 을 제외한 다른 Flag 를 사용하면 사용자가 전원버튼을 눌렀을 때 CPU는 sleep 상태로 들어갈 수 있습니다.
WakeLock 클래스의 로직은 IOS 미만 버전과 이상 버전의 로직이 다릅니다.
IOS 이전에는 WakeLock.acquire(timeout) 메서드에 치명적인 문제점이 존재하는데, 이 메서드는 timeout 시간만큼 CPU 를 사용하고 나면 자동으로 WakeLock.release() 메서드가 호출되어 CPU 사용을 마칩니다. 그런데 timeout 시간보다 먼저 작업이 끝나서 명시적으로 WakeLock.release() 메서드를 호출 하더라도 timeout 시간이 지나면 다시한번 WakeLock.release() 메서드가 호출되는데, 그러면 이미 CPU 사용을 마쳤는데도 다시한번 CPU 사용을 마치려 시도하면서 충돌이 발생합니다.
FATAL EXCEPTION: main
java.lang.RuntimeException: WakeLock under-locked
at android.os.PowerManager$WakeLock.release(PowerManager.java:307)
at android.os.PowerManager$WakeLock.release(PowerManager.java:282)
at android.os.PowerManager$WakeLock$1.run(PowerManager.java:202)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3691)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)
at dalvik.system.NativeStart.main(Native Method)
충돌이 발생하는 원인을 살펴보기 위해 WakeLock.acquire(timeout) 메서드의 코드를 살펴보면, 다음과 같이 postDelayed() 메서드를 사용하여 timeout 시간 뒤에 mReleaser 객체가 호출되게 됩니다. mReleaser 객체는 Runnable 객체로, WakeLock.release() 메서드를 호출합니다.
/**
* Makes sure the device is on at the level you asked when you created
* the wake lock. The lock will be released after the given timeout.
*
* @param timeout Release the lock after the give timeout in milliseconds.
*/
public void acquire(long timeout) {
synchronized (mToken) {
acquireLocked();
mHandler.postDelayed(mReleaser, timeout);
}
}
private final Runnable mReleaser = new Runnable() { 1254 public void run() { 1255 release(); 1256 } 1257 };
왜 postDelated() 메서드에 의해서 충돌이 발생하는지 ICS 미만 버전과 ICS 이상 버전에 있는 PowerManager.java 의 소스코드를 살펴보면 알 수 있습니다.
ICS 미만 버전의 PowerManager.java 에서 release() 메서드의 코드는 다음과 같습니다.
public void release(int flags)
{
synchronized (mToken) {
if (!mRefCounted || --mCount == 0) {
try {
mService.releaseWakeLock(mToken, flags);
} catch (RemoteException e) {
}
mHeld = false;
}
if (mCount < 0) {
throw new RuntimeException("WakeLock under-locked " + mTag);
}
}
}
위 두 소스 코드를 비교해 보면 ICS 이상 버전에 removeCallbacks(mRelease) 메서드가 추가된 것을 볼 수 있습니다 .
이처럼 timeout 시간보다 먼저 release() 메서드가 호출되었을 때에는 timeout 시간이 지난 뒤 다시한번 release() 메서드가 호출되지 않도록 하여 충돌이 발생하지 않습니다.
Handler.removeCallbacks() 메서드는 postDelayed() 메서드를 통해 메시지 큐에 등록된 Runnable 객체를 삭제합니다.
WakeLock 효율적으로 사용하기
WakeLock 은 배터리에 가장 영향을 많이 끼치는 기능 중 하나이므로, 사용할 수 밖에 없는 상황이라면 최대한 효율적으로 사용해야 합니다.
WakeLock 을 효율적으로 사용하는 방법은 다음과 같습니다.
1. 어떤 동작을 수행하려고 WakeLock 을 사용했다면 동작이 종료되었을 때 바로 release() 메서드를 호출한다.
2. 연산이 끝나면 항상 release() 메서드가 호출되고 Exception 등으로 인해 release() 메서드가 호출되지 못하는 일이 없도록 finally 키워드를 사용한다.
3. 패킷 발신, 수신에 사용한다면 발신할 때 한번 수신할때 다시한번 WakeLock을 호출 하여 두번의 호출이 생긴다. 만약 발신 후 수신까지 시간이 매우 짧다면 WakeLock.acquire(timeout) 메서드를 사용하여 호출 횟수를 줄일 수 있다.
4. 화면이 켜진 상태라면 CPU는 딥슬립 모드로 전환되지 않기 때문에 화면이 꺼진 상태에서만 WakeLock 을 사용하고, 화면이 켜진 상태에서는 WakeLock 을 사용하지 않는 것도 좋은 방법이다.
5. BroadcastReceiver 클래스를 사용하는 경우에도 onReceive(...) 메서드 이후의 로직이 언제나 정상적으로 수행되게 하기 위해서 WakeLock 을 사용한다. 하지만 Android Support Library 를 사용하면 WakefulBroadcastReceiver 클래스를 사용하여 직접 WakeLock 을 호출하지 않아도 된다.
WakefullBroadcastReceiver 클래스는 startWakefulService() 메서드로 서비스 (주로 IntentService) 에 처리해야 할 작업을 전달 해주고, 이러한 전달 과정에서 단말이 딥 슬립 상태로 돌아가지 않도록 내부적으로 PARTIAL_WAKE_LOCK 을 획득 합니다. 서비스에서 작업이 완료 되면 completeWakefullIntent() 메서드를 호출하여 WakeLock 사용을 종료하게 됩니다.
'Android > 기본 개념' 카테고리의 다른 글
해상도 별 이미지 생성 및 적용 방법 (0) | 2023.07.15 |
---|---|
Android NFC 기본 개념 및 활용 분야 (0) | 2018.03.06 |
Android 데이터 저장 방법 - SQLiteOpenHelper를 이용한 Database 생성 (0) | 2018.02.12 |
open failed: EACCES (Permission denied) 해결 방법 (0) | 2018.02.11 |
Android StrictMode로 Thread 감지하기 (0) | 2018.02.01 |