본문 바로가기

School Study/Mobile System Programming

Power Manager 알아보기

모바일 기기의 특성상 휴대가 가능해야 한다.
즉, 기기 동작을 위해 외부 전원이 아니라 내장 전원을 가지고 있어야 한다.

예전에 비해 오늘날에 더 빨리 배터리가 소모된다고 느꼈는데
오늘날 전원을 많이 사용하는 앱이 많아지면서 더 빨리 소모될 수 있다고 생각된다.

그래서 오늘날 앱을 만들 때 배터리를 효율적으로 활용할 수 있도록 제작해야 한다.

배터리 용량의 단위는 mAh 이다.
이는 한 시간에 표시된 숫자만큼 전류를 제공할 수 있다는 의미를 가진다.

오늘날 대부분의 스마트폰은 배터리가 내장형이라 확인이 불가능하지만
예전에는 위 사진과 같이 배터리를 분리해서 확인할 수 있었다.

왼쪽 배터리의 경우 한 시간 동안 최대 1800mAh를 제공할 수 있다.
주의할 점은 시간당 해당 전류를 항상 공급한다는 것이 아니라는 것이다.

오른쪽 배터리의 경우 전류로 변환하려면 5.55Wh / 3.7V를 하게 되면 Ah 단위로 나오게 될 것이고
1000을 곱해주면 mAh 단위로 나올 것이다.

Wh = mAh x V x 1000

결국 배터리의 파워는 전력을 뜻한다.
전력이란 단위 시간당 전류가 할 수 있는 일의 양으로 단위는 W(와트)이다.

전력량은 일정한 시간 동안 사용한 전력의 양이다.
(1kWh : 1kW의 소비전력을 가지는 전기 제품을 1시간 동안 사용했을 때의 전력량)

그렇다면 배터리 사용시간을 계산해보자.

만약 배터리 용량이 1800mAh이며, 안드로이드 시스템과 애플리케이션의 전류 소모량이 600mA라면
해당 배터리는 최소 3시간을 사용할 수 있을 것이다.

파워 관리(power management)를 위해서 다양한 요소들을 고려해야 한다.

파워 관리란 배터리로 동작하는 모바일 기기의 사용시간을 증가시키기 위해서 소프트웨어 및 하드웨어 측면에서
활용되는 방법/기술/시스템을 뜻한다.

스마트폰에서 배터리를 가장 많이 사용하는 요소는 일반적으로 Display이다.

배터리 사용시간을 늘리기 위해서 기본적으로 단위 부피당 용량을 늘리는 것이 중요했지만 꾸준한 성능 향상에도 불구하고 모바일 기기 동작을 위한 배터리 소모량도 꾸준히 증가하기 때문에 한계가 있다.

그래서 하드웨어 측면과 소프트웨어 측면에서 파워 관리가 들어가야 한다.

- 하드웨어 측면

1) CPU DVFS : CPU의 동작 전압과 주파수를 요구되는 연산량에 따라 조절하여 파워 소모량을 최적화하는 방법
즉, 연산량이 적으면 동적 전압과 주파수를 낮춰 파워 소모량을 줄일 수 있다.

오른쪽 그림을 보면 요구되는 power에 따라 OPP 단계를 두어 파워 관리를 하는 것을 확인할 수 있다.

CPU DVFS

2) big.LITTLE Architecture 
상대적으로 전력 소모가 적은 저성능 프로세서 코어(LITTLE)와 전력 소모가 많은 고성능 코어(big)를 함께 탑재하는 구조
즉, 상황에 따라 CPU 연산 작업이 big, 또는 LITTLE 코어에 동적으로 할당되어 처리된다.

big.LITTLE Architecture

 

우리가 다룰 수 있는 부분은 앱 개발을 하면서 파워 관리를 고려하는 소프트웨어 측면이다.
조금 더 개념적인 부분을 살펴보자

모바일 기기의 기본적인 파워 상태는 아래와 같다.

* Run : 시스템이 동작 중인 상태로 전류 소모가 최고에 이르는 상태다.

* Standby : 시스템이 비활성화된 상태다.

* Sleep : 시스템이 Standby 상태에서 지속되면 대부분의 주변장치의 전원 공급을 차단한다.(CPU도 sleep상태로 진입)

* Shutdown : 시스템 파워가 off 된 상태

파워 상태가 Sleep만 되더라도 상당한 배터리 절약을 기대할 수 있다. 따라서 상황에 따라 적절히 Sleep 상태로 들어갈 수 있도록 구성하면 효율적인 파워 관리를 기대할 수 있다.

구체적으로 파워 관리의 측면을 살펴보면 2가지로 볼 수 있다.

1) 화면 제어 : 일정시간 동작하지 않으면 Display를 꺼서 배터리 절약 가능
2) Sleep 제어 : 일정 시간 동작하지 않으면 Sleep 상태로 보내 배터리 절약 가능

기본적인 파워 절약 정책은 다음과 같다.

Run → Standby → Sleep 

하지만 동영상 플레이어, 네이게이션 등 동작이 없지만 사용자가 계속 화면을 보고 있는 상태인 앱도 존재한다.
따라서 이러한 정책을 사용할 수 없는 앱도 존재한다. 또한 음악 플레이어와 같이 화면이 꺼지더라도
background 작업을 계속 수행해야 하는 앱도 존재한다.

안드로이드 PowerManger 클래스

안드로이드 자체적으로 안드로이드 디바이스의 파워 상태를 제공할 수 있도록 API를 제공한다.

https://developer.android.com/reference/android/os/PowerManager.html?hl=ko

 

PowerManager  |  Android Developers

PowerManager public final class PowerManager extends Object java.lang.Object    ↳ android.os.PowerManager This class gives you control of the power state of the device. Device battery life will be significantly affected by the use of this API. Do not acqui

developer.android.com

[사용법]
• Context.getSystemService() 메서드를 이용하여 PowerManager 객체를 생성
• PowerManager의 newWakeLock() 메소드를 이용하여 PowerManager.WakeLock 객체 생성
• WakeLock의 메소드를 이용하여 파워 상태 (주로 CPU의 상태) 제어  acquire(), release()

PowerManger를 사용하기 위한 Permission은 아래와 같다.
• android.permission.WAKE_LOCK

메니페스트 파일에 다음과 같이 추가
• <uses-permission android:name=" android.permission.WAKE_LOCK" />

*** 주의사항 ***
• PowerManager API 사용으로 인해 배터리 사용 시간에 큰 영향을 받을 수 있음
• 반드시 필요한 경우에만 wakelock을 사용하고, 가장 낮은 레벨로 사용하 며, 사용 후 반드시 release 해야 함

1
2
3
4
5
6
7
8
9
10
11
12
PowerManager pm;
PowerManager.WakeLock wl;
 
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Tag: partial wake lock");
 
wl.acquire();
 
// CPU on
// doing a job you want
 
wl.release();
cs

이와 같은 간단한 코드로 PowerManger를 통해 wake lock을 사용할 수 있다.

newWakeLock 메서드를 살펴보자.

public PowerManager.WakeLock newWakeLock (int levelAndFlags, String tag)
이와 같은 방법으로 WakeLock 객체를 생성할 수 있다.

levelAndFlags 파라미터를 살펴보면
 • 레벨 (PowerManager 클래스에 정의된 constant) - 반드시 하나의 레벨만 지정해야 함
  - PARTIAL_WAKE_LOCK : 화면이 꺼지더라도 CPU는 계속 돌아가는 상태
  - FULL_WAKE_LOCK
  - SCREEN_DIM_WAKE_LOCK
  - SCREEN_BRIGHT_WAKE_LOCK

• 플래그 (PowerManager 클래스에 정의된 constant) – 스크린 동작에만 영향, partial wake lock의 사용에는 아무 영향 없음 
  - ACQUIRE_CAUSES_WAKEUP : wake lock이 acquire()되면 스크린과 키보드가 바로 켜진다.
  - ON_AFTER_RELEASE : wake lock이 release 된 후 화면 타이머를 reset 해 화면 꺼지는 시간을 조금 늦춘다.

level 파라미터 상태

 

화면과 관련된 wake lock은 배터리 소모 이슈가 많아 정책이 변경되면서 현재 deprecated 상태이다.
• FULL_WAKE_LOCK
• SCREEN_DIM_WAKE_LOCK
• SCREEN_BRIGHT_WAKE_LOCK

wake lock을 사용하지 않고 화면을 on 상태로 유지하는 방법은
Activity에서 FLAG_KEEP_SCREEN_ON을 사용하면 된다.(service 혹은 다른 app component에서 사용하면 안 됨)

이를 활용하면 따로 permission이 필요하지도 않으며 안드로이드 플랫폼이 알아서 관리해주기 때문에
따로 release가 필요없다.

 

 FLAG_KEEP_SCREEN_ON 사용법 (2가지)

• Activity onCreate에서 코드로 설정하는 방법

1
2
3
4
5
6
7
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
cs

 

• 레이아웃 XML 파일에서 설정하는 방법

1
2
3
4
5
6
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true">
    ...
</RelativeLayout>
cs

 

이제 실제로 PowerManger를 활용해 wakeLock을 사용했을 때와 사용하지 않은 경우 차이를 확인해보자.

아래 예제는 1초마다 카운트를 세는 애플리케이션이다. 카운트를 세면서 화면이 꺼진 경우 정상적으로 카운트가 되는지 wakeLock을 사용해보고, 사용하지 않고 비교해보면 wakeLock의 효과를 느낄 수 있을 것이다.

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public class MainActivity extends AppCompatActivity {
 
    TextView m1000msCountTv = null;
 
    Timer mTimer = new Timer();
    TimerTask m1000msCountTimerTask = null;
 
    //***********************************
    // wake lock을 사용하는 경우 필요한 코드
    PowerManager pm;
    PowerManager.WakeLock wl;
    //***********************************
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        m1000msCountTv = (TextView)findViewById(R.id.ms_1000_countdown_text);
 
        //**************************************************************
        // wake lock을 사용하는 경우 필요한 코드
        pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Tag: partial wake lock");
        //wl.acquire();
        //**************************************************************
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
 
        // 타이머는 작업 스레드이기 때문에 액티비티가 종료될 때
        // 반드시 중단하여 스레드를 제거시키도록 한다
        mTimer.cancel();
 
        //***********************************
        // wake lock을 사용하는 경우 필요한 코드
        //wl.release();
        //***********************************
    }
 
    public void onClick(View v) {
        switch(v.getId()) {
            case R.id.start:
            {
                startTimerTask();
                break;
            }
            case R.id.stop:
            {
                stopTimerTask();
                break;
            }
        }
    }
 
    private void startTimerTask() {
        // 1. TimerTask 실행 중이라면 중단한다
        stopTimerTask();
 
        // 2. TimerTask 객체를 생성한다
        // 1000 밀리초마다 카운팅 되는 태스크를 등록한다
        m1000msCountTimerTask = new TimerTask() {
            int mCount = 0;
            @Override
            public void run() {
                mCount++;
                m1000msCountTv.post(new Runnable() {
                    @Override
                    public void run() {
                        m1000msCountTv.setText("Count: " + mCount);
                    }
                });
            }
        };
 
        // 3. TimerTask를 Timer를 통해 실행시킨다
        // 1초 후에 타이머를 구동하고 1000 밀리초 단위로 반복한다
        //     schedule(TimerTask task, long delay, long period)
        mTimer.schedule(m1000msCountTimerTask, 10001000);
        //*** Timer 클래스 메소드 이용법 참고 ***//
        // http://developer.android.com/intl/ko/reference/java/util/Timer.html
        //***********************************//
    }
 
    private void stopTimerTask() {
        // 1. 모든 태스크를 중단한다
        if(m1000msCountTimerTask != null) {
            m1000msCountTimerTask.cancel();
            m1000msCountTimerTask = null;
        }
 
        // 2. 카운팅 초기화값을 텍스트뷰에 표시
        m1000msCountTv.setText("Count: 0");
    }
}
cs
 
 
activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" tools:context=".MainActivity">
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/ms_1000_countdown_text"
        android:layout_gravity="center_horizontal"
        android:text="Count: 0"
        android:textSize="40sp"/>
 
    <Button
        android:id="@+id/start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Count"
        android:onClick="onClick"/>
 
    <Button
        android:id="@+id/stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Count"
        android:onClick="onClick"/>
</LinearLayout>
cs

 

PowerManager를 활용해 wakeLock을 걸지 않았는데 화면이 꺼져도 정상적으로 동작하는 경우가 있다.
우리가 스마트폰을 연결해 코드를 업로드하면서 USB 단자를 통해 컴퓨터와 연결되어 있을텐데
이 경우 자체적으로 wakeLock이 걸려있어 정상적으로 동작하게 보이는 것이다.

따라서 wakeLock을 테스트할 때는 컴퓨터와 연결을 분리한 후 테스트해보자.