web-dev-qa-db-ja.com

アプリがバックグラウンドで制限されているときにフォアグラウンドサービスを開始するAndroid Pie

Android 9.0(Pie)を使用すると、ユーザーが設定を介してアプリのバックグラウンド作業を制限することができます。Pieデバイスでフォアグラウンドサービスを開始しようとすると、アプリで次の例外が発生します。アプリのアクティビティが完全にフォアグラウンドにある場合でも、アプリがバックグラウンドで制限されている場合。

RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()

開始されたサービスでstartForeground()を呼び出しますが、サービスを開始しようとするとシステムログに次のように表示されます。

system_process W/ActivityManager: Service.startForeground() not allowed due to bg restriction

フォアグラウンドサービスについて 文書化された手順 を実行しているときに例外を受け取るのは奇妙に思えます。また、アプリがフォアグラウンドにある場合、システムはアプリがフォアグラウンドサービスを開始することを拒否しています。私はこれに関連する多くのドキュメントをまだ見つけていませんが、これはドキュメント化された動作ですか?そして、少なくともアプリがバックグラウンドで制限されているかどうかを認識し、フォアグラウンドでサービスを開始しようとしない方法はありますか?

enter image description here

私のサービスコードは基本的に以下のようになります。ターゲットAPIは27です。

class MyService : Service() {

    override fun onCreate() {
        super.onCreate()
        // create notification
        startForeground(NOTIFICATION_ID, notification)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent == null || intent.action == null || intent.action == ACTION_STOP) {
            quit()
        }
        doWork()
        return START_NOT_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        quit()
    }

    private fun quit() {
        stopForeground(true)
        stopSelf()
    }

    companion object {

        fun start(context: Context) {
            val intent = Intent(context, MyService::class.Java)
            intent.action = ACTION_START
            ContextCompat.startForegroundService(context, intent)
        }

        fun stop(context: Context) {
            val intent = Intent(context, MyService::class.Java)
            intent.action = ACTION_STOP
            ContextCompat.startForegroundService(context, intent)
        }
    }
}
5

クラッシュは、MyService.stop()ContextCompat.startForegroundService()への2回目の呼び出しから実際に発生することがわかりました。 context.stopService()を呼び出すのではなく、サービスを停止するための「停止」アクションを含むインテントを送信するために使用していました。最初は、サービスでstopForeground()を手動で呼び出す必要がありましたが、context.stopService()を呼び出すと、フォアグラウンドサービスが停止し、通知が削除されたようです。クラッシュにつながるので、サービスの停止を処理する方法をリファクタリングすることにしました。

更新:問題の追加の部分は、インテントを使用してサービスを開始および停止しようとしたことでもあったと思います。特に、サービスの開始と停止が速すぎる場合があるためです。非常に役立つ Ian Lakeのスレッド サービスに関する次の推奨事項を示します。

サービスにメッセージを渡す方法としてstartServiceを使用しないことを強くお勧めします。 EventBusまたはLocalBroadcastReceiverを使用すると、ライフサイクルアクションと混同せずにメッセージを渡すためのはるかに優れた方法です。また、外部コンポーネントがstopService()を直接呼び出すことも避けたいと思います。サービス自体に独自のライフサイクルを管理させ、送信したイベントに反応させます。

1