Android
AndroidO

Android Oからのバックグラウンド・サービスの制限事項を実演する。

Android Oreo かつ targetSdkVersion:26以上のアプリから、バックグラウンド処理の実行が強く制限されます。特にバックグラウンド・サービスを扱うアプリにとっては、適切な修正を施さないと容易に強制終了されるような制限となっています。

制限に関する詳細は別記事に委ねるとして、ここではバックグラウンド・サービスに関する制限により強制終了されるケースを、掻い摘んで紹介します。

検証用のソースコードは以下に置いてます。
なお本記事の動作検証はDP3のエミュレータにて行いました。
https://github.com/nukka123/BackgroundLimitsDemo

開発者サイトのバックグラウンドの実行制限に関する内容は以下を参照ください。
https://developer.android.com/preview/features/background.html

ケーススタディ

OKケース: フォアグラウンド・サービス化で制限回避

  • アクティビティから startForegroundService() でサービスを開始する。
  • onStartCommand()時に startForeground() でフォアグラウンド化する。

移行ガイドで紹介されるフォアグラウンド・サービスの実施例です。問題なくサービスが稼働します。

startForegroundService()で開始されたサービスは、 サービスの終了迄 かつ 開始から5s以内 にstartForeground()でフォアグラウンド通知を実施する必要があります。通知を実施する箇所は、サービスの onCreate()又はonStartCommand() のタイミングが良いかと思います。

NGケース1: startForeground()を実施しない

  • アクティビティから startForegroundService() でサービスを開始する。
  • onStartCommand()時に startForeground() を実施しない。
  • 5sの経過を待つ。
    • ANRが発生する。

startForegroundService()でサービスを開始するものの、肝心のサービスクラス側ではフォアグラウンド通知を実施しないまま処理を継続させたパターンです。開始から5s経過時点でANRにより強制終了される事になります。

logcat
E/ActivityManager: ANR in com.example.demo
                   PID: 6844
                   Reason: Context.startForegroundService() did not then call Service.startForeground()

NGケース2: startForeground()を実施しないままサービス終了

  • アクティビティから startForegroundService() でサービスを開始する。
  • onStartCommand()時に startForeground() を実施しない。
  • stopSelf() でサービスを終了する。
    • 例外が発生し、強制終了される。

startFregroundService() で開始したサービスが、startForeground() を実施しないままサービスを終了することは約束違反です。例外が発生して強制終了される事になります。

サービスの開始時のパラメータ・チェックによりstopSelf()する場合は、本ケースの事象に注意する必要があります。

logcat
E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: com.example.demo, PID: 8056
                  android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()

NGケース3: アクティビティ終了から1分後

  • アクティビティから startService() でサービスを開始する。
  • アクティビティを終了する。
  • onStartCommand()時に startForeground() を実施しない。
  • 60sの経過を待つ。
    • システム側からstopSelf()が実行される。

可視アクティビティがある間は、アプリがフォアグラウンド状態のため、startService() でサービスを開始する事ができます。とはいえ、サービスはアクティビティとは別のライフサイクルであるため、サービスは起動中だけどアクティビティは終了されている状況は、当然に発生します。

DP3のEmuによる検証では、可視アクティビティが無くなってから1分経過すると、アプリがバックグラウンドにあると見なされるようです。この場合、バックグラウンド・サービスに対してシステム側からstopSelf()が実行されます。

logcat
W/ActivityManager: Stopping service due to app idle: u0a97 -1m0s492ms com.example.demo/.MyServiceA
D/MyServiceA: onDestroy

NGケース4: バックグラウンド状態からのバックグラウンド・サービスの開始 (サービス編)

  • アクティビティから 1分後に起動する ジョブサービスを予約する。
  • アクティビティを終了する。
  • 1分経過後にジョブサービスが開始される。
  • そのジョブサービスにて、更に startService() でサービスを開始する。
    • 例外が発生し、強制終了される。

可視アクティビティが無くなってから1分経過しているため、アプリがバックグラウンドにあると見なされます。この状態でバックグラウンド・サービスを開始する事は許されません。例外が発生して強制終了される事になります。

ちなみに、別アプリのサービスをstartService()で開始させる場合も、同様に例外が発生します。

logcat
W/ActivityManager: Background start not allowed: service Intent { cmp=com.example.demo/.MyServiceA (has extras) } to com.example.demo/.MyServiceA from pid=9320 uid=10097 pkg=com.example.demo
E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: com.example.demo, PID: 9320
                  java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.example.demo/.MyServiceA (has extras) }: app is in background uid UidRecord{a140e98 u0a97 TRNB bg:+1m9s400ms idle procs:1 seq(0,0,0)}

NGケース5: バックグラウンド状態からのバックグラウンド・サービスの開始 (レシーバ編)

  • BOOT_COMPLETEDを監視するレシーバを用意する。
  • 端末を再起動する。
  • 起動されたレシーバにて、startService() でサービスを開始する。
    • 例外が発生し、強制終了される。

AndroidOでは、マニフェストで定義されているブロードキャスト・レシーバーへの制限が強化されていますが、BOOT_COMPLETEDは制限の例外として、まだ使用可能のようです。

一方で、ブロードキャスト・レシーバで起動されたアプリは、基本的にバックグラウンド状態です。この状態でバックグラウンド・サービスを開始する事は許されません。例外が発生して強制終了される事になります。

開発者サイトでは以下の記載があるのですが、

アプリは、ユーザーに表示される次のようなタスクを処理しているときにホワイトリストに入れられます。
・ SMS/MMS メッセージなどのブロードキャストの受信。

Issueによれば、この記載は全てのブロードキャストを対象にした説明ではなく、一部のブロードキャストだけに限定される話のようです。特に明記がない限り、ホワイトリストに入らないと考えるべきです。

参考: BOOT_COMPLETED broadcast doesn't place app in whitelist for background services
https://issuetracker.google.com/issues/62821226

logcat
/ActivityManager: Background start not allowed: service Intent { cmp=com.example.demo/.MyServiceA (has extras) } to com.example.demo/.MyServiceA from pid=3290 uid=10097 pkg=com.example.demo
E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: com.example.demo, PID: 3290
                  java.lang.RuntimeException: Unable to start receiver com.example.demo.MyReceiver: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.example.demo/.MyServiceA (has extras) }: app is in background uid UidRecord{7f4a7ef u0a97 RCVR idle procs:1 seq(0,0,0)}

まとめ

Androidのバージョンアップに従いバッテリーを消費するアプリへの制限が強まってきましたが、AndroidOでは、とうとう行儀の悪いアプリを一掃させる段階に到達したようです。

targetSdkVersionを26に移行する際は、最早、従来の気分のままでサービスを扱う事はできません。移行ガイドを参照し、アプリをより良い形へ修正していきましょう。