先日、開発していたアプリのtargetSdkVersionを26に移行しました。
すると、バックグラウンドでサービスを立ち上げる際に、アプリがクラッシュするようになったのです。
エラーには「Not allowed to start service ~」と書かれていました。
その際に使用していた端末はPixel(バージョン8.1.0)です。
何事かと思い、色々と確認したところ、Android8(Oreo)からはアプリがバックグラウンドの状態でのサービスに制限が加わったそうです。。
参考:バックグラウンド実行制限
今回は、その際に調べたことと、対応方法についてご紹介します。
Contents
Android8からはアプリがバックグラウンドの状態だとサービスに制限がかかる
最近のGoogleは、Androidのバッテリー消費を少なくするために、いろいろな節電方法を導入しています。
今回は、バックグラウンドにあるアプリで実行される動作を2つ制限しにきました。
アプリの動作は次の 2 つの方法で制限されます。
バックグラウンド実行制限 概要
バックグラウンドサービスの制限事項: アプリがアイドル状態にある場合、バックグラウンドサービスの使用を制限します。 これは、ユーザーが認識しやすいフォアグラウンドサービスには適用されません。
ブロードキャストの制限事項: 限定的な例外を除き、アプリはマニフェストを使用して暗黙的なブロードキャストを登録できません。 ただし、アプリは実行時にこれらのブロードキャストを登録でき、アプリを明確に対象とする明示的なブロードキャストについては、マニフェストを使って登録できます。
これにより、ユーザーエクスペリエンスを向上させられるそうです。
確かに、端末にかかる負荷が減ると動作が早くなったり、バッテリーが長く持つようになったりするので、1ユーザーとしては嬉しいことだと思います。
ただ、開発者としてはサービスの実行方法について検討しなおしたり、修正を加えたりしなければならないので、かなり大変です。
このバックグラウンド実行制限により、サービスはアプリがバックグラウンドのときに、以下のような制限を受けるようになります。
システムは、バックグラウンドアプリによるバッグラウンドサービスの作成を許可しません。
バックグラウンド実行制限 バックグラウンドサービスの制限事項
つまり、アプリをバックグラウンドにした状態でstartService()を呼んでしまうと、アプリがクラッシュしてしまうわけです。
ただし、アプリが以下のような作業をしている間は、この制限を受けないホワイトリストに入れられるそうです。
・高い優先度の Firebase Cloud Messaging(FCM)メッセージの処理。
バックグラウンド実行制限 バックグラウンドサービスの制限事項
・SMS/MMS メッセージなどのブロードキャストの受信。
・通知からの PendingIntent の実行。
これらの処理中は制限から除外されるということですね。
このあたりの細かい条件については、以下のQiitaの記事がとても参考になりますので、一度目を通しておくと良いですよ。
Android Oからのバックグラウンド・サービスの制限事項を実演する。
Android8の端末でバックグラウンドでサービスを使用する方法
JobSchedulerに置き換える
Googleは、バックグラウンドサービスをJobSchedulerに置き換えることを推奨しているようです。
多くの場合、アプリではバックグラウンドサービスをJobSchedulerジョブに置き換えることができます。
バックグラウンド実行制限 バックグラウンドサービスの制限事項
ただ、JobSchedulerはAPI level 21から加わったものなので、それ以下のバージョンも扱っている場合は、端末のバージョンによって処理を分岐しなければなりません。
Context.startForegroundService()メソッドを使用する
もう一つの方法として、作成したサービスをフォアグラウンドに紐づける、というものがあります。
Android 8.0 では、フォアグラウンドで新しいサービスを作成する Context.startForegroundService()メソッドが新たに導入されています。
バックグラウンド実行制限 バックグラウンドサービスの制限事項
システムによってサービスが作成されると、アプリは、サービスのstartForeground()メソッドを 5 秒以内に呼び出して、その新しいサービスの通知をユーザーに表示します。
つまり、アクティビティやフラグメント側でContext.startForegroundService()メソッドを呼び、サービス側のonStartCommand()でstartForeground()メソッドを呼びだせば良いそうです。
実際に試してみたところ、アプリがバックグラウンド状態でも問題なくサービスが実行できました。
しかし、その代わりなのか、通知バーに「アプリが電池を消費しています」という通知が表示されるようになってしまいました。
この通知が表示されてしまうと、ユーザーへの印象が少し悪くなりそうで怖いですね。
どちらを選ぶかは開発者次第といったところでしょうか。
終わりに
今回は、Andriod8でアプリがバックグラウンドの状態の場合、サービスが実行できない件についてご紹介しました。
対応方法としては、
・JobSchedulerを使う
・Context.startForegroundService()メソッドを使う
のどちらかのようです。
以上です。それでは。