⚠️ この記事はアイデア出しを筆者が行い、文章の生成に生成AIを使用しています。内容のレビューは筆者が行っています。

「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つ。

1点目は、expo-background-taskやexpo-notificationsのバックグラウンド系機能はExpo Goでは動作せず、開発ビルド(EAS Build等)が必要になる場合がある。マネージドワークフローだからといって、ネイティブの制約(iOSのバックグラウンド実行時間制限など)そのものが消えるわけではない。

2点目は、expo-background-taskはタスクの実行タイミングをOS側(Android: WorkManager、iOS: BGTaskScheduler)に委ねる設計であり、「指定した間隔で必ず実行される」ことを保証しない点。これは元をたどればAlarmManagerのバッチング思想と同じで、「正確な時刻より省電力を優先する」というOS側のポリシーがExpoのAPI設計にもそのまま反映されている形になる。

つまり、Expo層で見えているAPIの粒度は違っても、「システムに実行タイミングの主導権を渡し、アプリ側は条件(電源状態・ネットワーク状態など)を指定するだけにとどめる」という設計思想自体は、ネイティブAndroid開発と地続きだと言える。

まとめ

領域 主な対策
通信 タイミング分散(AlarmManagerのバッチング)、電源/接続状態に応じた制御、圧縮・キャッシュ、プッシュ優先、リトライ間隔の調整
ディスプレイ 不要な点灯を避ける、WakeLockは必要最小限かつ確実に解放
位置情報 精度要件に見合ったAPI選択(LocationServices優先、NETWORK_PROVIDERの活用)
実装の受け皿 個別対応よりJobSchedulerに集約するのが基本線

省電力設計の本質は、「本当にその精度・その頻度・その正確さが必要か」を機能ごとに問い直すことに尽きる。多くのAPIは、この問いに「いいえ」と答えられる場面のために存在している。

参考


要確認事項

Must(公開前に一次ソースで裏取りすべき)

  • FCMデータメッセージのペイロード上限「4096バイト(4KB)」、通知メッセージ上限「2KB」 → 確認先: Firebase公式ドキュメント(掲載URL、ただし今後の仕様変更の有無を再確認)
  • FCMメッセージの有効期限デフォルト「4週間(約28日)」 → 確認先: Firebase Cloud Messaging公式ドキュメント(time_to_liveパラメータの項)
  • AlarmManager.set()のバッチング挙動がAPI 19(Android 4.4)以降という記述 → 確認先: Android Developers公式ドキュメント(掲載URLで概ね裏取り済みだが、targetSdkVersionによる条件分岐の細部は本文で簡略化している点に注意)

Should(余裕があれば確認、無理なら「未確認」と本文に一言添える)

  • LocationServicesがLocationManager単体よりバッテリー効率が良いという一般化 → 端末・OSバージョンによる差の可能性
  • Android N以降のブロードキャスト配信変更の対象イベント一覧 → 本文では深追いしていないため、記事化時に一次情報での網羅確認が必要