省電力設計の本質:通信・位置情報・ディスプレイをどう絞るか
「Androidアプリ開発の極意」(木田学ほか著、テックファーム監修、技術評論社、2017年)を読み進めながら整理したメモがベースになっている。刊行から年数が経っている本だが、バッテリー消費を抑えるための設計判断そのものは古びていない部分が多い。 はじめに アプリが「重い」「電池が減る」と言われるとき、原因の多くはCPU負荷そのものより、通信・位置情報・ディスプレイの3つの使い方に集約される。個別の最適化テクニックを覚える前に、「なぜその実装がバッテリーを食うのか」という設計原理を押さえておくと、新しいAPIが出てきたときにも応用が効く。 基本方針はシンプルで、避けられるなら避ける、必要なら節約する設計にするの一言に尽きる。以下、この3領域それぞれで何が問題になり、Androidがどう対処してきたかを整理する。 通信:タイミングをずらし、量を減らす 決まった時間に一斉通信しない 複数のアプリが同じタイミングで定期通信を行うと、端末は本来スリープしていられる時間まで叩き起こされることになる。これに対するAndroidの答えがAlarmManagerのアラームバッチング機構だ。 AlarmManager.set()(setExact()ではない通常版)は、API 19(Android 4.4)以降、指定時刻ちょうどの起動が保証されない仕様に変わっている。システムが複数アプリのアラームをまとめて処理し、意図的に遅延・分散させることで端末のウェイクアップ回数そのものを減らす仕組みだ。厳密な時刻指定が必要なカレンダーの通知などはsetExact()を使う設計になっており、「本当に正確な時刻が必要か」を自問するだけで、使うAPIが変わる。 電源状態・接続状態に応じて挙動を変える 満充電/充電中/放電中/充電なし、バッテリー残量は取得できるので、これに応じて通信頻度を変える設計が可能 そもそもネットワークに繋がっていなければ、到達しないとわかっている通信を試みるだけ無駄になる。接続状態はAndroidでは容易に取得できる 3G等の従量課金回線では、通信量そのものを絞る設計も選択肢に入る(ただし経路をつど判定するコストとのトレードオフは意識しておきたい) バックグラウンド通信制限(データセーバー) Android N以降には「データセーバー」機能があり、有効化されているとフォアグラウンドでも通信を抑える方向に働く。アプリ側はこの挙動があり得ることを前提に設計しておく必要がある。 通信データ量そのものを減らす GZIP圧縮は最低限やっておく データはまとめて取得し、通信回数自体を減らす キャッシュを活用する レジュームダウンロード(範囲指定でのデータ取得)を使えば、中断位置から再取得して結合できる ポーリング vs プッシュ 定期的に通信して更新を確認する「ポーリング」に対し、サーバー側から必要なときだけ通知する「プッシュ」はバッテリー面で有利になりやすい。AndroidではFCM(Firebase Cloud Messaging)が定番で、以下の制約を踏まえて設計する。 項目 内容 データメッセージのペイロード上限 4096バイト(4KB) 通知メッセージのペイロード上限 2KB メッセージの有効期限(デフォルト) 4週間(約28日) ペイロードには最小限のデータ(IDなど)だけを載せ、本体データはAPI経由で取得する設計が定石になる。ペイロードに実データを詰め込みすぎると、将来的にサイズ上限に引っかかって通知が届かなくなるか、最悪クラッシュにつながる。 リトライ間隔を空ける 通信失敗時のリトライを短い間隔で繰り返すと、サーバー負荷とクライアントのバッテリー消費の両方を悪化させる。指数バックオフなど、間隔を徐々に空ける設計が基本になる。 通信量の主導権をユーザーに渡す 通信量に関わる挙動(画質、同期頻度など)をユーザー自身が選べるようにするのも有効な設計の一つ。 ディスプレイ:点灯そのものを減らす ディスプレイは単純に「光っている時間」がそのままバッテリー消費に直結する。不要な点滅を避けるのは当然として、注意すべきはWakeLockの扱いだ。 WakeLockは画面スリープを防ぐ機能で、バッテリーの敵として扱われがちだが、ダウンロード処理中など「画面がオフになると処理自体が失敗する」場面では必須になる。**「使わない」ではなく「必要な範囲だけ、必要な時間だけ保持して確実に解放する」**設計判断が求められる。 位置情報:APIの選び方で消費が変わる LocationServices(Google Play Services必須)を使う方が、LocationManager単体よりバッテリー効率が良い傾向にある LocationManagerを使う場合、プロバイダをNETWORK_PROVIDERに指定するとWiFi経由の測位のみになり、GPSを使うより省電力になる 精度が本当に必要な場面かどうかを見極め、過剰に高精度なプロバイダを選ばないことが基本方針になる。 実装をまとめて任せる:JobScheduler ここまでの対策(電源状態に応じた実行判断、リトライ間隔、実行タイミングの分散など)を個別に手組みするのではなく、JobScheduler一本に任せるという選択肢がある。バッテリー状態・実行遅延・リトライ間隔などを条件として設定でき、標準APIとして安定している。特別な理由がない限り、これを軸に設計するのが妥当だろう。 なお、Android N以降ではブロードキャスト配信の仕組み自体にも変更が入っている。CONNECTIVITY_ACTION(ネットワーク変更)のような特定のブロードキャストを受信対象アプリが一斉に受け取ることでパフォーマンスが悪化する問題への対処で、対象イベントは複数あるが、ここでは深追いしない。 Expo/React Nativeでは何に当たるか ここまでの話はAndroidネイティブAPIの話だが、Expo管理下のReact Nativeアプリでは、それぞれ対応するモジュールに置き換わる。「素のAndroid APIを直接叩けない」制約はあるが、内部的には同じ仕組みに乗っているケースが多い。 Androidネイティブの概念 Expoでの対応 備考 AlarmManager / JobScheduler expo-background-task SDK 53でexpo-background-fetchを置き換え。Android側は内部的にWorkManager(JobSchedulerベース)を利用 FCM(プッシュ通知) expo-notifications リモート通知の受信・権限管理をラップ。ペイロードサイズ等の制約はFCM側の仕様がそのまま適用される WakeLock expo-keep-awake useKeepAwake()フックまたは命令的APIで画面スリープを抑制。必要な範囲だけ有効化する設計方針は変わらない 接続状態の判定 expo-network オンライン/オフライン、接続種別(WiFi/セルラー等)の取得に使う バッテリー状態の取得 expo-battery 充電状態・残量の取得。電源状態に応じた通信頻度の出し分けに使える LocationManager / LocationServices expo-location 精度指定(Accuracy)でGPS/WiFi測位のどちらに寄せるかを制御できる 注意点は主に2つ。 ...