ウェイクロック: Android* アプリケーションでスリープしない問題の検出

同カテゴリーの次の記事

インテル® GPA:Windows* 7/8/8.1 OS サポート

この記事は、インテル® デベロッパー・ゾーンに掲載されている「Wakelocks: Detect No-Sleep Issues in Android* Applications」(http://software.intel.com/en-us/articles/wakelocks-detect-no-sleep-issues-in-android-applications) の日本語参考訳です。


概要

ウェイクロックを適切に使用していない Android* アプリケーションは、バッテリーを大幅に消耗する可能性があります。この記事では、ウェイクロックの誤用に関連したスリープしない問題を特定するヒントを提供します。

1. はじめに
2. ウェイクロック
2.1. ウェイクロックとは
2.2. Android* ユーザー・ウェイクロック
2.3. Android* カーネル・ウェイクロック
2.4. スリープしない問題
3. スリープしない問題の特定
3.1. adb の使用
3.2. BetterBatteryStats* アプリケーションの使用
4. テストケース
5. まとめ
6. 参考文献

1. はじめに

スマートフォンではバッテリーの消耗を抑えることは非常に重要です。できるだけバッテリーの自律性 (持続時間) を高めるため、Android* OS はシステムでユーザー操作がないことを検出するとすぐに、状況に応じてスリープモードになるように設計されています。一部のアプリケーションは、長時間ユーザー操作がなくても、デバイスをアクティブな状態で維持する必要があります。例えば、動画を観たり、音楽を聴いたり、GPS を使用したり、ゲームをプレイする場合などです。Android* は、そのような場合にデバイスをアクティブな状態のままにするメカニズムを OS やアプリケーションに提供しています。このメカニズムをウェイクロックと呼びます。詳細は、Christopher Bird 氏の記事「Wakelocks for Android」 (http://software.intel.com/en-us/articles/wakelocks-for-android) をお読みください。

このようなメカニズムを利用する際、コンポーネントのアクティビティーを制御するのはアプリケーション開発者の責任です。正しく使用しないと、アプリケーションはフォアグラウンドで実行していない場合であっても、バッテリーを大幅に消耗する可能性があります。

2. ウェイクロック

2.1. ウェイクロックとは

ウェイクロックとは、ホストデバイスの電力状態を制御するソフトウェア・メカニズムです。OS は、明示的な電力管理ハンドルと API をエクスポートして、特定のコンポーネントが明示的にその役割から解放されるまで、そのコンポーネントがいつアクティブな状態を維持するかを指示します。

ウェイクロック・メカニズムは、2 つのレベル (ユーザーとカーネル) で実装されます。以下の図は、Android* ウェイクロック実装の内部設計を示したものです。ユーザー・ウェイクロックは、電力管理サービスによって提供され、高レベルの OS サービスまたはアプリケーションで利用されます。ユーザー・ウェイクロックを用いることで、アプリケーションはデバイスの電力状態を制御できます。カーネル・ウェイクロックは、OS カーネルまたはドライバーによって管理されます。ユーザー・ウェイクロックはカーネル・ウェイクロックにマップされます。アクティブなカーネル・ウェイクロックは、システムがモバイルデバイスの消費電力を最も抑えることができる ACPI S3 (Suspended to RAM) ステートにサスペンドしないようにします。

2.2. Android* ユーザー・ウェイクロック

Android* フレームワークは、PowerManager.Wakelock クラスによりウェイクロック・メカニズムをエクスポートし、4 種類のユーザー・ウェイクロックを識別します。

フラグの値CPU画面キーボード

PARTIAL_WAKE_LOCK

オン

オフ

オフ

SCREEN_DIM_WAKE_LOCK

オン

暗い

オフ

SCREEN_BRIGHT_WAKE_LOCK

オン

明るい

オフ

FULL_WAKE_LOCK

オン

明るい

明るい

API レベル 17 での FULL_WAKE_LOCK は廃止されました。代わりに、FLAG_KEEP_SCREEN_ON をアプリケーションで使用する必要があります。

ウェイクロックを取得して、一部のコンポーネント (CPU、画面、およびキーボード) をアクティブな状態のままにすることができます。

PARTIAL_WAKE_LOCK を使うと、ディスプレイのタイムアウト設定や画面の状態に関係なく、あるいはユーザーが電源ボタンを押した場合であっても、CPU は実行し続けるため注意が必要です。デバイスはスタンバイ・モード (画面がオフ) のように見えても、実際にはアクティブな状態であり、気付かないうちに電力を消耗しています。

その他のすべてのウェイクロックでは、ユーザーが電源ボタンを押すとデバイスはスリープ状態になります。部分ウェイクロックを除くすべてのウェイクロックは、電源ボタンが押されると暗黙に解放されます。

アプリケーションでウェイクロックを保持する方法を以下に示します。基本的には取得/解放メカニズムです。アプリケーションは、コンポーネントをアクティブな状態で維持する必要がある場合、ウェイクロックを取得します。そして、その必要がなくなったらウェイクロックを解放しなければいけません。

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, “My Tag”);
wl.acquire();
..このセッション中、画面はオンのまま..
wl.release();

2.3. Android* カーネル・ウェイクロック

カーネル・ウェイクロックとは、カーネルによって保持される低レベルのウェイクロックです。カーネルから内部的に取得/解放することができます。そのため、アプリケーション開発者が直接これらのウェイクロックを制御する機会はあまりありませんが、アプリケーションの動作が間接的にこれらのウェイクロックを使用し、不用意にバッテリーを消耗してしまうことがあります。

カーネル・ウェイクロックには、次のようなものがあります。

Wlan_rx: Wi-Fi* を介してデータの送受信を行うときにカーネルによって保持されます。

PowerManagerService: すべての部分ウェイクロックのコンテナーです。

Sync: 同期処理中に保持されます。

Alarm_rtc: (アプリケーションまたはプロセスが定期的に何かを確認するときに) アラームを処理します。

Main: カーネルをアクティブな状態で維持します。システムがサスペンドモードに移行するときに、このウェイクロックは最後に解放されます。

2.4. スリープしない問題

アプリケーションは、システムが深いスリープモードに移行できるように、取得したウェイクロックをある時点ですべて解放する必要があります。ウェイクロックが解放されないと、この取得/解放メカニズムはいくつかの問題を引き起こします。ウェイクロックを取得したアプリケーションがすでにフォアグラウンドで実行していなくても、そのウェイクロックは保持されたままになります。ウェイクロックは、明示的に解放されるか、アプリケーションが強制終了されると解放されます。ウェイクロックを保持したままにすると、アクティビティーがない場合であっても、システムは深いスリープモードに移行することができず、電力を大幅に消耗し、不用意にバッテリーの自律性 (持続時間) を損います。これを、スリープしない問題と呼びます。Android* はイベント駆動型なので、アプリケーションがウェイクロックを取得したすべてのコードパス (つまり、解放する必要があるウェイクロック) を開発者が把握していないことがあります。これを、スリープしないコードパス問題と呼びます。

この種の別の問題として、ウェイクロックを取得する前に解放してしまう問題もあります。これは、ウェイクロックの取得と解放が別々のスレッドで行われるマルチスレッド・コードで発生します。これを、スリープしない競合状態と呼びます。

最後の問題は、スリープしないダイレーションと呼ばれ、ウェイクロックが実際に必要な期間よりも長く保持されます。

これらの問題についてなぜ理解する必要があるのでしょうか? P. Vekris 氏の調査では、ウェイクロックを使用する 328 のアプリケーションの 55% がスリープしない問題に対応していません [2012]。主要なアプリケーションのいくつかは、スリープしない問題を抱えたままリリースされています。アプリケーションが適切に実行するように、開発者はこの問題を認識する必要があります。

3. スリープしない問題の特定

スリープしない問題への対応方法は 2 つあります。コードパスをスキャンするスタティック解析を行うか、実行時にダイナミック解析を行います。この記事では、ランタイム解析に注目します。

この方法により、アプリケーションのウェイクロック問題をすべて特定できる保証はありません。ただし、実行時にウェイクロック問題が発生した場合、それらを見つけるのに役立ちます。ウェイクロック問題を特定するには、コードパスを確認して、ウェイクロックが解放されていない場所を見つける必要があります。スリープしない問題のテストでは、さまざまな場所から異なる方法でアプリケーションを終了し、ウェイクロックが保持されたままになるかどうかを確認します。

場合によっては、アプリケーションがすでにフォアグラウンドで実行されていなくても、システムが深いスリープモードに移行しないようにする必要があります。アプリケーションは、バックグラウンドでタスクを実行しなければいけないかもしれません。例えば、アプリケーションで長時間のダウンロード (動画やゲームなど) が必要な場合、 そのアプリケーションはユーザーによってバックグラウンドで実行される可能性がありますが、ダウンロードが完了するまでデバイスはアクティブな状態でなければいけません。この場合、アプリケーションは、ダウンロードが完了するまでウェイクロックを保持します。開発者は、ある時点でこのウェイクロックが解放されたかどうかを確認する必要があります。例えば、ダウンロード中にネットワークから切断され処理を続行できない場合、デバイスはアクティブな状態を維持すべきではありません。

要約すると、スリープしない問題の特定は状況に大きく依存します。この問題を簡単に見つけられる特定の基準はありません。良識によってのみこの問題を見つけられるでしょう。

3.1. adb の使用

ウェイクロックは、シェルコマンドで簡単に確認できます。

カーネル・ウェイクロックの詳細な情報を取得するには、次のコマンドを実行します。

adb shell cat /proc/wakelocks

name count expire_count wake_count active_since total_time
“PowerManagerService” 1502 0 0 0 337817677431
“main” 15 0 0 0 984265842688
“alarm” 1512 0 792 0 217778251643
“radio-interface” 16 0 0 0 16676538930
“alarm_rtc” 804 4 0 0 1204136324759
“gps-lock” 1 0 0 0 10753659786
name sleep_time max_time last_change
“PowerManagerService” 95729409122 140921663667 9723417252748
“main” 0 212424732355 9498127170228
“alarm” 217617362047 357976941 9723371461242
“radio-interface” 0 1659328496 9486387144974
“alarm_rtc” 1200253446201 66082936501 9483176054624
“gps-lock” 0 10753659786 37632803440

カーネル 3.4 以上を使用するイメージの場合、“adb shell cat /sys/kernel/debug/wakeup_sources” コマンドを使います。このコマンドを実行することで、詳細な情報が得られますが、この形式では分かりづらいでしょう。そこで、より分かりやすい方法を次に紹介します。

特定のアプリケーションを確認する簡単な方法は、“adb shell dumpsys power” コマンドを実行することです。以下は、このコマンドの典型的な出力です。コマンド発行時に使用されているユーザー・ウェイクロックがあることが分かります。このコマンドは、システムで使用されているユーザー・ウェイクロックの情報を表示します。

Power Manager State:
mIsPowered=true mPowerState=3 mScreenOffTime=1618305 ms
mPartialCount=3
mWakeLockState=SCREEN_ON_BIT
mUserState=SCREEN_BRIGHT_BIT SCREEN_ON_BIT
mPowerState=SCREEN_BRIGHT_BIT SCREEN_ON_BIT
mLocks.gather=SCREEN_ON_BIT
mNextTimeout=2382037 now=2378097 3s from now
mDimScreen=true mStayOnConditions=3 mPreparingForScreenOn=false mSkippedScreenOn=false
mScreenOffReason=0 mUserState=3
mBroadcastQueue={-1,-1,-1}
mBroadcastWhy={0,0,0}
mPokey=0 mPokeAwakeonSet=false
mKeyboardVisible=false mUserActivityAllowed=true
mKeylightDelay=6000 mDimDelay=587000 mScreenOffDelay=7000
mPreventScreenOn=false mScreenBrightnessOverride=-1 mButtonBrightnessOverride=-1
mScreenOffTimeoutSetting=600000 mMaximumScreenOffTimeout=2147483647
mLastScreenOnTime=27380
mBroadcastWakeLock=UnsynchronizedWakeLock(mFlags=0x1 mCount=0 mHeld=false)
mStayOnWhilePluggedInScreenDimLock=UnsynchronizedWakeLock(mFlags=0x6 mCount=0 mHeld=true)
mStayOnWhilePluggedInPartialLock=UnsynchronizedWakeLock(mFlags=0x1 mCount=0 mHeld=true)
mPreventScreenOnPartialLock=UnsynchronizedWakeLock(mFlags=0x1 mCount=0 mHeld=false)
mProximityPartialLock=UnsynchronizedWakeLock(mFlags=0x1 mCount=0 mHeld=false)
mProximityWakeLockCount=0
mProximitySensorEnabled=false
mProximitySensorActive=false
mProximityPendingValue=-1
mLastProximityEventTime=0
mLightSensorEnabled=true mLightSensorAdjustSetting=0.0
mLightSensorValue=11.0 mLightSensorPendingValue=10.0
mHighestLightSensorValue=47 mWaitingForFirstLightSensor=false
mLightSensorPendingDecrease=false mLightSensorPendingIncrease=false
mLightSensorScreenBrightness=42 mLightSensorButtonBrightness=0 mLightSensorKeyboardBrightness=0
mUseSoftwareAutoBrightness=true
mAutoBrightessEnabled=true
creenBrightnessAnimator:
animating: start:42, end:42, duration:480, current:42
startSensorValue:47 endSensorValue:11
startTimeMillis:2361638 now:2378092
currentMask:SCREEN_BRIGHT_BIT
mLocks.size=4:
SCREEN_DIM_WAKE_LOCK ‘StayOnWhilePluggedIn_Screen_Dim’ activated (minState=1, uid=1000, pid=388)
PARTIAL_WAKE_LOCK ‘StayOnWhilePluggedIn_Partial’ activated (minState=0, uid=1000, pid=388)
PARTIAL_WAKE_LOCK ‘HDA_PARTIAL_WAKE_LOCK’ activated (minState=0, uid=10046, pid=4690)
PARTIAL_WAKE_LOCK ‘AudioOut_2’ activated (minState=0, uid=1013, pid=157)
mPokeLocks.size=0:

アプリケーションがバックグラウンドに移行した後、保持されたままのウェイクロックがあるかどうかを特定するには、次の操作を行います。

  1. デバイスを USB に接続します。
  2. アプリケーションを起動して操作します。
  3. 電源ボタンを押してスリープモードにするか、アプリケーションを終了します。
  4. そのまま約 20 秒間待機します。
  5. コマンドラインで次のコマンドを実行します。
    > adb shell dumpsys power
  6. 次のような PARTIAL_WAKE_LOCK があるかどうかを確認します。
    PARTIAL_WAKE_LOCK ‘AudioOut_2’ activated(minState=0, uid=1013, pid=157)
  7. ステップ 5 を 15 秒ごとに 3 ~ 5 回繰り返します。結果が同じになる場合は、問題がある可能性があります。

3.2. BetterBatteryStats* アプリケーションの使用

BetterBatteryStats* (https://play.google.com/store/apps/details?id=com.asksven.betterbatterystats&hl=en (英語)) は、Sven Knispel 氏が開発した Android* アプリで、Google Play* から購入できます。このアプリは、バッテリーを消耗している場所 (特にウェイクロック問題) を特定する情報を収集します。

最初に、[Other] レポートを選択して、総時間に占める深いスリープモードとアクティブモードの割合を確認します。理想としては、デバイスが使われていない場合、そのほとんどの時間は深いスリープ状態にあるべきです。

アクティブな時間と画面がオンの時間を比較すると、実際にアクティビティーが発生した時間が分かります。通常、アクティブな時間と画面がオンの時間は同じくらいになるべきです。

時間経過に伴うバッテリーの状態、アクティブな状態、画面がオンの状態、Wi-Fi* 状態も確認できます。

次に、カーネル・ウェイクロックを確認します。各カーネル・ウェイクロックで費やされた時間と使用回数が表示されます。時間や回数が大きい場合、問題を示している可能性があります。このレポートから hotspot を特定のアプリケーションやプロセスに紐付けることはできませんが、特定のアプリケーションによって引き起こされる動作を見つけられます。

[Kernel Wakelock] レポートの [PowerManagerService] には、ユーザー・ウェイクロックで費やされた総時間が表示されます。ここが hotspot の場合、[Partial Wakelock] レポートでドリルダウンすることができます。

ほとんどの場合、部分ウェイクロックはそれを保持しているアプリケーションを示しており、 問題の原因を特定するのに役立ちます。場合によっては、ほかのアプリによってアクティビティーが起動されることもあります。例えば、ゲームで音の再生に Android* メディアギャラリーに割り当てられている AudioOut チャネルを使用しているかもしれません。適切にコーディングされていないと、このチャネルが閉じられなかった場合、ゲーム側の責任になります。決して Android* ギャラリーの責任にはなりません。

AlarmManager は、ウェイクアップがアラームによるものであること、またはアプリケーションが多数のアラーム変更を行ったことを [Alarms] レポートで示しますが、 ルートイメージがある場合のみ利用できます。

[Network] レポートも、ルートイメージがある場合のみ利用可能です。このレポートは、ネットワークのトラフィック量が多い場合に役立ちます。multipdp / svnet-dormancy カーネル・ウェイクロックは、ネットワーク使用量が多いことを示している可能性があります。

4. テストケース

ゲームをプレイする実際の状況について考えてみます。ゲームを起動して 5 分ほどプレイし、通常とは異なる方法で終了します。ここでは、ホームボタンを押して強制終了します。すると、音楽が停止され、ホーム画面が表示されます。このとき、ユーザーからは何も問題がないように見えます。ユーザー操作がないまま数分経過すると、画面は期待通りに暗くなります。そのままの状態で 30 分~ 1 時間ほど放置し、BestBatteryStats* を確認してみます。[Other] レポートから、画面はオフになっていたにもかかわらず、デバイスがアクティブな状態であったことが分かります。また、バッテリー消費量は 1 時間あたり 8.5% であることが分かります。つまり、この状態では、バッテリーがフルであってもデバイスは 12 時間しかもちません。

次に、カーネル・ウェイクロックを確認してみましょう。アクティビティーがないにもかかわらず、2 つのカーネル・ウェイクロックがあることが分かります。1 つは PowerManagerService で、このロックがあるということは、部分ユーザー・ウェイクロックもあります。もう 1 つは、AudioOutLock 待機ロックです。[Partial Wakelocks] レポートを見てみましょう。

メディア・ギャラリー・アプリケーションがオーディオチャネルを開いたままであることが分かります。メディア・ギャラリー・アプリケーションはユーザーによって明示的に使用されていませんが、 ゲーム音楽を再生するため、ゲームによって起動されました。しかし、ホームボタンによってアプリケーションが中断されたときに、オーディオチャネルを閉じるのを開発者が忘れていたのです。開発者は、このようなケースを考慮してアプリケーションを変更する必要があります。

5. まとめ

ウェイクロックは便利で強力なツールです。しかし、誤用により、デバイスのバッテリー寿命を大幅に縮め、ユーザー・エクスペリエンスに大きく影響する可能性があります。開発者は、QA 時にスリープしない問題がないかどうかコードを確認することが重要です。ツールを活用して、実際の使用ケースでアプリケーションのバッテリーへの影響を解析し、カスタマーデバイスへの影響をできるだけ抑えるべきです。

6. 参考文献

  • A.Pathak, et al., “What is keeping my phone awake?: characterizing and detecting no-sleep energy bugs in smartphone apps” http://reviews.mobisys2012.rice.edu/r/papers/comments/1n/what_is_keeping_my_phone_awake_characterizing_and/, MobiSys, 2012.
  • P.Vekris, et al., “Towards Verifying Android Apps for the Absence of No-Sleep Energy Bugs” http://mesl.ucsd.edu/yuvraj/docs/Vekris_HotPower12_TowardsVerifyingApps.pdf, HotPower, 2012.
  • Android Wakelock http://developer.android.com/reference/android/os/PowerManager.WakeLock.html
  • Christopher Bird, “Wakelocks for android” http://software.intel.com/en-us/articles/wakelocks-for-android, Intel Corporation, 2012.
  • BetterBatteryStats web site: http://better.asksven.org/

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事