その後、Context.startForegroundService()はService.startForeground()を呼び出しませんでした


258

使ってます ServiceAndroid O OSでClassをしています。

Serviceバックグラウンドで使用する予定です。

Androidのドキュメントと述べています

アプリがAPIレベル26以上をターゲットにしている場合、アプリ自体がフォアグラウンドにない限り、システムはバックグラウンドサービスの使用または作成に制限を課します。アプリがフォアグラウンドサービスを作成する必要がある場合、アプリはを呼び出す必要がありますstartForegroundService()

を使用するstartForegroundService()と、Serviceは次のエラーをスローします。

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

これの何が問題になっていますか?


IOW、最小限の再現可能な例を提供してください。これには、Javaスタックトレース全体と、クラッシュを引き起こしているコードが含まれます。
CommonsWare 2017年

3
API 26および27(27.0.3)のバグはまだ残っています。影響を受けるAndroidのバージョンは8.0および8.1です。startForeground()をonCreate()とonStartCommand()の両方に追加することで、クラッシュの数を減らすことができますが、一部のユーザーではクラッシュが発生します。atmを修正する唯一の方法は、build.gradleのtargetSdkVersion 25です。
アレックス


1
現在、この回避策を使用しています。androiddeveloper.com/blog/…の
Prasad Pawar

1
私は同じ問題を抱えています。この問題を修正しました。私はこのトピックでは、私の実装を共有stackoverflow.com/questions/55894636/...
Beyazid

回答:


99

Android 8.0の動作変更に関するGoogleのドキュメントから:

システムでは、アプリがバックグラウンドにあるときでも、アプリがContext.startForegroundService()を呼び出すことができます。ただし、アプリはサービスの作成後5秒以内にそのサービスのstartForeground()メソッドを呼び出す必要があります。

ソリューション:通話startForeground()onCreate()のためService、使用されContext.startForegroundService()

参照:Android 8.0(Oreo)のバックグラウンド実行制限


19
onStartCommandメソッドでこれを実行しましたが、まだこのエラーが発生します。に電話しstartForegroundService(intent)ましたMainActivity。サービスの開始が遅すぎる可能性があります。サービスがすぐに開始されることを約束する前に、5秒の制限は存在しないはずです。
Kimi Chiu

29
5秒間では十分ではありません。この例外はデバッグセッションで非常に頻繁に発生します。私は時々リリースモードでも起こると思います。致命的な例外であるため、アプリがクラッシュします。私はそれをThread.setDefaultUncaughtExceptionHandler()でキャッチしようとしましたが、それを取得してandroidを無視しても、アプリケーションがフリーズします。これは、この例外がhandleMessage()からトリガーされ、メインループが実質的に終了するためです...回避策を探しています。
サウザートン2018年

ブロードキャストレシーバーでContext.startForegroundService()メソッドを呼び出しました。この状況でどのように処理するのですか?onCreate()は放送受信機では使用できないため
Anand Savjani

6
@southerton サービスを閉じて再度開始すると、呼び出さずに移動する可能性があるため、onStartCommand()ではなくを呼び出す必要があると思います...onCreateonStartCommand()onCreate
android developer

1
Googleチームからの返信はこちらで確認できますissuetracker.google.com/issues/76112072#comment56
Prags

82

私は呼びました ContextCompat.startForegroundService(this, intent)、サービスを開始するには

稼働中 onCreate

 @Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}

47
私も。しかし、まだこのエラーが時々発生します。たぶん、AndroidはonCreate5秒以内にを呼び出すことを保証できません。したがって、彼らは私たちがルールに従うことを強制する前にそれを再設計する必要があります。
Kimi Chiu

5
onStartCommand()でstartForegroundを呼び出していましたが、時々このエラーが発生しました。私はそれをonCreateに移動しましたが、それ以来(クロスフィンガー)見ていません。
タイラー

4
これにより、Oreoに「APP_NAMEが実行中です。タップして閉じるか情報を表示してください」というローカル通知が作成されます。その通知の表示を停止する方法は?
Artist404

6
いいえ、Androidチームは、これは意図的な動作であると主張しました。そのため、アプリを再設計しました。ばかげてる。これらの「意図された動作」のため、常にアプリを再設計する必要があります。それは三度目です。
Kimi Chiu

12
この問題の主な原因は、フォアグラウンドに昇格する前にサービスが停止したことです。しかし、サービスが破壊された後もアサーションは停止しませんでした。をStopService呼び出しstartForegroundServiceた後に追加すると、これを再現することができます。
Kimi Chiu

55

この問題が発生している理由は、Androidフレームワークは5秒以内にサービスが開始されることを保証できないが、フレームワークはフォアグラウンド通知に厳密な制限があるため、フレームワークがサービスを開始しようとしたかどうかを確認せずに5秒以内に起動する必要があるためです。

これは間違いなくフレームワークの問題ですが、この問題に直面しているすべての開発者が最善を尽くしているわけではありません。

  1. startForeground通知は、両方でなければなりませんonCreateし、onStartCommandあなたのサービスがすでに作成され、何とか自分の活動が再びそれを起動しようとしている場合、ので、onCreate呼び出されません。

  2. 通知IDは0であってはなりません。そうでない場合、同じ理由でなくても同じクラッシュが発生します。

  3. stopSelf前に呼び出さないでくださいstartForeground

上記の3つすべてで、この問題は少し軽減できますが、まだ修正はできません。実際の修正または回避策は、ターゲットのSDKバージョンを25にダウングレードすることです。

そして、Googleが何が起こっているのかさえ理解することを拒否し、これが彼らの責任ではないと信じているため、Android Pがこの問題を抱えている可能性が高いことに注意してください。詳細については、#36#56をお読みください


9
onCreateとonStartCommandの両方がなぜですか?それをonStartCommandに入れることはできますか?
rcell

stopSelfは、startForeground =>の前または直後に呼び出すことはできません。これは、「startForeground」をキャンセルしたように見えるためです。
フランク

1
いくつかのテストを実行しました。サービスが既に作成されている場合にonCreateが呼び出されなくても、startForegroundを再度呼び出す必要はありません。したがって、onStartCommandではなく、onCreateメソッドでのみ呼び出すことができます。おそらく、最初の呼び出しが終了するまで、サービスの単一のインスタンスがフォアグラウンドにあると見なします。
Lxu

の最初の呼び出しの通知IDとして0を使用していstartForegroundます。これを1に変更すると、問題が(うまくいけば)修正されます
mr5

それで、解決策は何ですか?
IgorGanapolsky

35

すでに多くの回答が公開されていますが、真実は-startForegroundServiceはアプリレベルで修正できないため、使用を中止する必要があります。Context#startForegroundService()が呼び出されてから5秒以内にService#startForeground()APIを使用するというGoogleの推奨事項は、アプリが常に実行できるものではありません。

Androidは多くのプロセスを同時に実行し、5秒以内にstartForeground()を呼び出すことになっているターゲットサービスをLooperが呼び出す保証はありません。ターゲットサービスが5秒以内に呼び出しを受信しなかった場合、運が悪く、ユーザーはANR状態に陥ります。スタックトレースには、次のようなものが表示されます。

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
  at android.os.MessageQueue.next (MessageQueue.java:326)
  at android.os.Looper.loop (Looper.java:181)
  at android.app.ActivityThread.main (ActivityThread.java:6981)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

私が理解しているように、ルーパーはここでキューを分析し、「乱用者」を見つけ、単にそれを殺しました。現在、システムは満足で健康ですが、開発者とユーザーはそうではありませんが、Googleがシステムに対する責任を制限しているので、なぜ後者の2つを気にする必要がありますか?どうやらそうではありません。彼らはそれをより良くすることができますか?もちろん、たとえば、「アプリケーションはビジーです」ダイアログを表示して、ユーザーにアプリの待機または強制終了の決定を求めることもできますが、なぜそれを行うのはユーザーの責任ではありません。主なことは、システムが現在健全であるということです。

私の観察によると、これは比較的まれにしか発生しません。私の場合、1Kユーザーの場合、月に約1回のクラッシュが発生します。それを再現することは不可能であり、それが再現されたとしても、永久に修正する方法はありません。

このスレッドでは、「start」の代わりに「bind」を使用して、サービスの準備ができたらonServiceConnectedを処理することをお勧めしますが、startForegroundService呼び出しをまったく使用しないことを意味します。

Google側からの正しい正直な行動は、startForegourndServcieに欠陥があり、使用すべきではないことをすべての人に伝えることだと思います。

問題はまだ残っています:代わりに何を使うべきですか?幸いなことに、現在はフォアグラウンドサービスのより優れた代替手段であるJobSchedulerとJobServiceがあります。それが理由で、それはより良いオプションです:

ジョブの実行中、システムはアプリに代わってwakelockを保持します。このため、ジョブの実行中にデバイスが起動したままであることを保証するためのアクションを実行する必要はありません。

これは、ウェイクロックの処理についてもう気にする必要がないことを意味します。そのため、フォアグラウンドサービスとの違いはありません。実装の観点からは、JobSchedulerはサービスではなく、システムのサービスであり、おそらくキューを正しく処理し、Googleは自身の子を終了しません:)

Samsungは、Samsung Accessory Protocol(SAP)でstartForegroundServiceからJobSchedulerおよびJobServiceに切り替えました。これは、スマートウォッチなどのデバイスが電話などのホストと通信する必要があり、ジョブがアプリのメインスレッドを介してユーザーと対話する必要がある場合に非常に役立ちます。ジョブはスケジューラによってメインスレッドにポストされるため、それが可能になります。ただし、ジョブはメインスレッドで実行されており、重いものはすべて他のスレッドと非同期タスクにオフロードする必要があります。

このサービスは、アプリケーションのメインスレッドで実行されているハンドラーで各着信ジョブを実行します。これは、実行ロジックを選択した別のスレッド/ハンドラ/ AsyncTaskにオフロードする必要があることを意味します

JobScheduler / JobServiceに切り替える際の唯一の落とし穴は、古いコードをリファクタリングする必要があり、面白くないということです。私は過去2日間を費やして、新しいSamsungのSAP実装を使用しました。クラッシュレポートを見て、再度クラッシュが発生するかどうかをお知らせします。理論的にはそれは起こらないはずですが、気づかないかもしれない詳細が常にあります。

更新 Play Storeから報告されるクラッシュはありません。つまり、JobScheduler / JobServiceにはそのような問題はなく、このモデルへの切り替えは、startForegroundServiceの問題を永久に取り除くための適切なアプローチです。Google / Androidがそれを読んで、最終的にはコメント/アドバイス/公式ガイダンスをすべての人に提供してくれることを願っています。

アップデート2

SAPを使用していて、SAP V2がJobServiceをどのように利用しているかを質問する人のために、以下に説明します。

カスタムコードで、SAP(Kotlin)を初期化する必要があります。

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

次に、Samsungのコードを逆コンパイルして、内部で何が行われているのかを確認する必要があります。SAAgentV2で、requestAgentの実装と次の行を見てください。

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

ここでSAAdapterクラスに移動し、次の呼び出しを使用してジョブをスケジュールするonServiceConnectionRequested関数を見つけます。

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobServiceはAndroid版のJobServiceの単なる実装であり、これはジョブスケジューリングを行うものです。

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

ご覧のとおり、ここの最後の行では、AndroidのJobSchedulerを使用してこのシステムサービスを取得し、ジョブをスケジュールしています。

requestAgent呼び出しでは、重要なイベントが発生したときに制御を受け取るコールバック関数であるmAgentCallbackを渡しました。これは私のアプリでコールバックが定義されている方法です:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

ここのMessageJobsは、Samsungスマートウォッチからのすべてのリクエストを処理するために実装したクラスです。これは完全なコードではなく、単なるスケルトンです。

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

ご覧のとおり、MessageJobsにはMessageSocketクラスも必要です。これは、実装する必要があり、デバイスから送信されるすべてのメッセージを処理する必要があります。

結論として、それはそれほど単純ではなく、内部とコーディングを掘り下げる必要がありますが、それは機能し、そして最も重要なことです-クラッシュしません。


正解ですが、大きな問題があり、JobIntentServiceすぐIntentService下のオレオとして実行されますが、オレオ以上でジョブをスケジュールするため、JobIntentServiceすぐには開始されません。詳細
CopsOnRoad

1
@CopsOnRoadこれは非常に便利で、私の場合はすぐに始まります。私が書いたように、電話とスマートウォッチの間のリアルタイムの対話に使用されます。決して、ユーザーはこれらの2つの間でデータを送信するために15分間待機します。非常にうまく機能し、クラッシュすることはありません。
Oleg Gryb

1
クールに聞こえます。サンプルコードを共有できれば、あなたの答えは非常に価値があります。私はAndroidを使いJob...始めたばかりで、開始に時間がかかり、それがのアドバンスバージョンですAlarmManager
CopsOnRoad

2
おそらく時間が許せば、私はそれをカスタム固有のコードから
切り離し、

1
@batmaci-JobServiceはSAPによって内部的に使用されます。詳細については、OPのUpdate 2を参照してください。
Oleg Gryb

32

を呼び出しContext.startForegroundService(...)、次にが呼び出さContext.stopService(...)れる前に呼び出すと、アプリがクラッシュしますService.startForeground(...)

ここには明確な再現がありますForegroundServiceAPI26

私はこれでバグをオープンしました:Google issue tracker

これに関するいくつかのバグがオープンおよびクローズされました修正されません。

うまくいけば、明確な再現手順で鉱山を採掘するとうまくいきます。

Googleチームが提供する情報

Google課題トラッカーコメント36

これはフレームワークのバグではありません。それは意図的なものです。アプリがでサービスインスタンスを起動した場合startForegroundService()、それがなければなりませんフォアグラウンド状態にそのサービスインスタンスを移行し、通知を表示します。サービスインスタンスがstartForeground()呼び出される前に停止された場合、その約束は果たされません。これはアプリのバグです。

#31に関して、他のアプリが直接開始できるサービスを公開することは、基本的に安全ではありません。そのサービスのすべての開始アクションを「必須startForeground()」として扱うことで、少し緩和することができますが、明らかにそれはあなたが考えていなかったかもしれません。

Google課題トラッカーコメント56

ここで同じ結果につながるいくつかの異なるシナリオがあります。

明白な意味論上の問題、何かを開始するのは単なるエラーであるstartForegroundService()が、実際にそれを経由してフォアグラウンドに移行することを怠るstartForeground()まさにそれが意味論的な問題です。これは意図的にアプリのバグとして扱われます。フォアグラウンドに移行する前にサービスを停止すると、アプリエラーが発生します。それがOPの要であり、この問題が「意図したとおりに機能する」とマークされているのはこのためです。

ただし、この問題の誤った検出についての質問もあります。これ本物の問題として扱われていますが、この特定のバグトラッカーの問題とは別に追跡されています。私たちは不満に耳を傾けていません。


2
私が見た中で最も良いアップデートは、issuetracker.google.com / issues / 76112072#comment56です。です。Context.startForegroundServiceへの問題のある呼び出しを完全に回避するContext.bindServiceを使用するようにコードを書き直しました。私のサンプルコードは、github.com
paulpv /

はい、私が書いたように、バインドは機能するはずですが、JobServiveに既に切り替えており、これも壊れない限り何も変更しません ':)
Oleg Gryb

20

私はこれについて数日間研究し、解決策を得ました。Android Oでは、次のように背景の制限を設定できます

サービスクラスを呼び出しているサービス

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    SettingActivity.this.startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

そしてサービスクラスは

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}

3
少なくとも数千のユーザーがいるいくつかのアプリでそれを試しましたか?私が試したところ、一部のユーザーはまだクラッシュの問題を抱えています。
アレックス

いいえ、私は正確なコードを使用しており、クライアントがクラッシュすることはありません。
Ahmad Arslan

どのくらいのユーザーがいますか?私は、1日あたり10,000人のユーザーがいるアプリで、毎日10〜30以上のクラッシュ(エラーあり)を見ました。もちろん、それが唯一のAndroid 8.0とAndroid 8.1を持つユーザーのために起こる、場合にtargetSdkVersionは、私が25にtargetSdkVersionを設定した直後に27のすべての問題は0クラッシュに消えている
アレックス・

2200人のユーザーのみ:-/がクラッシュしませんでした
Ahmad Arslan

1
@Jeeva、本当じゃない。ATMは、私はそれは多くの実験の後に最良の方法を好きに見えるcompileSdkVersion 27とtargetSdkVersion 25使う....ホープ彼らは終了しますdeveloper.android.com/topic/libraries/architecture/...ため、2018年8月前にアンドロイド-developers.googleblog .com /
Alex

16

私が持っているウィジェットの装置が起きていると私はほんの数日でクラッシュの数千人を見ていた時に比較的頻繁に更新を行います。

問題のトリガー

Pixel 3 XLであっても、デバイスに大きな負荷がかかるとは思っていなかったのに、この問題に気づきました。そして、すべてのコードパスがでカバーされていましたstartForeground()。しかし、その後、多くの場合、私のサービスは非常に迅速に仕事を完了することがわかりました。私のアプリのトリガーは、システムが実際に通知を表示する前にサービスが終了していたことだと思います。

回避策/解決策

すべてのクラッシュを取り除くことができました。私がしたことは、への呼び出しを削除することstopSelf()でした。(通知が表示されたと確信できるまで停止を遅らせることを考えていましたが、必要がない場合はユーザーに通知を表示したくありません。)サービスが1分間アイドル状態になっているか、システムが例外をスローせずに通常どおりに破棄します。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}

11

私がこれに何時間も浪費したので、頭を上げただけです。でstartForeground(..)最初に呼び出していたにもかかわらず、私はこの例外を受け取り続けましたonCreate(..)。結局、問題の原因はの使用であることがわかりましたNOTIFICATION_ID = 0。他の値を使用すると、これが修正されるようです。


私の場合、ID 1を使用していたので、問題は解決しました
Amin Pinjari

4
ID 101を使用していますが、このエラーが時々発生します。
CopsOnRoad

9

この問題の回避策があります。私は自分のアプリ(300K + DAU)でこの修正を確認しました。これにより、この種のクラッシュの少なくとも95%を削減できますが、この問題を100%回避することはできません。

この問題は、Googleのドキュメントに記載されているように、サービスが開始した直後に必ずstartForeground()を呼び出す場合でも発生します。これは、多くのシナリオでサービスの作成と初期化の処理に既に5秒以上かかるため、startForeground()メソッドをいつどこで呼び出しても、このクラッシュは避けられないためです。

私の解決策は、サービスを作成して初期化する必要がある期間に関係なく、startForegroundService()メソッドの後5秒以内にstartForeground()が実行されるようにすることです。詳細なソリューションは次のとおりです。

  1. 最初はstartForegroundServiceを使用しないでください。auto_createフラグを指定してbindService()を使用してください。サービスの初期化を待ちます。これがコードです。サンプルサービスはMusicServiceです。

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
  2. 次に、MusicBinderの実装を示します。

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
  3. 最も重要な部分であるMusicServiceの実装であるforceForeground()メソッドは、startForeground()メソッドがstartForegroundService()の直後に呼び出されるようにします。

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
  4. アプリを開かずにウィジェットでフォアグラウンドサービスを開始する(ウィジェットボタンをクリックする)場合など、ステップ1のコードスニペットを保留中のインテントで実行する場合は、ブロードキャストスニペットでコードスニペットをラップできます。 、サービス開始コマンドの代わりにブロードキャストイベントを発生させます。

以上です。それが役に立てば幸い。幸運を。


8

このエラーは、IDが0に設定されているときにService.startForeground(int id、Notification notification)が呼び出されると Android 8以降でも発生します。

id int:NotificationManager.notify(int、Notification)によるこの通知の識別子。0であってはなりません


3
通知IDにも大きな数値を使用しないでください。1桁のIDを使用してください。stackoverflow.com/a/12228555/2527204
Marlon

7

ここを訪れる人は皆同じ問題に苦しんでいるので、他の誰も(この質問ではとにかく)試したことがない私の解決策を共有したいと思います。このメソッドを確認する停止したブレークポイント、それが機能していることを保証できます。

問題はService.startForeground(id, notification)、サービス自体から呼び出すことですよね?Androidフレームワークは残念ながら5秒Service.startForeground(id, notification)以内Service.onCreate()に呼び出すことを保証していませんが、とにかく例外をスローするので、私はこの方法を考え出しました。

  1. 呼び出す前に、サービスのバインダーを使用してサービスをコンテキストにバインドします Context.startForegroundService()
  2. バインドが成功した場合はContext.startForegroundService() 、サービス接続から呼び出し、すぐにサービス接続内で呼び出しService.startForeground() ます。
  3. 重要な注意:コールContext.bindService()内のメソッドのtry-catchを、いくつかの場面でのコールは、あなたが呼び出しに依存する必要があり、その場合には例外、投げることができるので、Context.startForegroundService()直接、それが失敗しないことを願っていますし。例として、ブロードキャストレシーバーコンテキストが挙げられますが、アプリケーションコンテキストを取得しても例外はスローされませんが、コンテキストを直接使用するとスローされます。

これは、サービスのバインド後、「startForeground」呼び出しをトリガーする前にブレークポイントで待機している場合でも機能します。3〜4秒待っても例外はトリガーされませんが、5秒後には例外がスローされます。(デバイスが5秒で2行のコードを実行できない場合は、それをゴミ箱に捨てます。)

したがって、サービス接続の作成から始めます。

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

サービス内で、サービスのインスタンスを返すバインダーを作成します。

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

次に、そのコンテキストからサービスをバインドしてみます。成功ServiceConnection.onServiceConnected()した場合は、使用しているサービス接続からメソッドを呼び出します。次に、上記のコードでロジックを処理します。コード例は次のようになります。

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

私が開発したアプリケーションで動作しているようですので、試してみても動作するはずです。


5

Android O API 26の問題

サービスをすぐに停止した場合(サービスが実際に実行されない(言い回し/理解)ので、ANR間隔よりかなり短い場合でも、stopSelfの前にstartForegroundを呼び出す必要があります。

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

このアプローチを試しましたが、それでもエラーが発生します:-

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

エラーが解決されるまでこれを使用しています

mContext.startService(playIntent);

3
単に大きくなるだけでなく、Util.SDK_INT> = 26である必要があります
Andris

4

私は同じ問題に直面しており、時間を費やした後、コードの下で試すことができるソリューションを見つけました。使用している場合は、ServiceこのコードをonCreateに配置します。使用しているIntent Service場合は、このコードをonHandleIntentに配置します。

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }

4

私はこの問題を調査しており、これは私がこれまでに発見したものです。このクラッシュは、次のようなコードがある場合に発生する可能性があります。

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

例外は、コードの次のブロックでスローされます。

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

Androidはメインスレッドハンドラーでのサービスの作成をスケジュールしていますが、競合状態であるで呼び出されるためonCreate()、このメソッドが実行される前に実行されます。これは、クラッシュの原因となる呼び出しの機会がなかったことを意味します。MyForegroundServicebringDownServiceLockedBinderThreadMyForegroundServicestartForeground

これを修正するには、それがのbringDownServiceLockedonCreate()に呼び出されないようにする必要がありMyForegroundServiceます。

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

スティッキーブロードキャストを使用することにより、我々は放送が失われませんことを確認してstopReceiver、すぐにそれはに登録されているように意図停止を受信onCreate()しますMyForegroundService。この時までにstartForeground(...)。また、次回にstopReceiverに通知されないようにするために、このスティッキーブロードキャストを削除する必要があります。

この方法sendStickyBroadcastは廃止予定であり、この問題を解決するための一時的な回避策としてのみ使用していることに注意してください


サービスを停止する場合は、context.stopService(serviceIntent)の代わりに呼び出す必要があります。
makovkastar 2018年

4

非常に多くの答えがありますが、私の場合はどれもうまくいきませんでした。

このようなサービスを始めました。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

そして、onStartCommandの私のサービスで

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

NOTIFICATION_IDをゼロ以外に設定することを忘れないでください

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

SOはすべて完璧でしたが、8.1でもクラッシュしました。原因は以下のとおりです。

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

通知を削除してストップフォアグラウンドを呼び出しましたが、通知が削除されたサービスがバックグラウンドになり、バックグラウンドからAndroid Oでバックグラウンドサービスを実行できません。プッシュ受信後に開始されました。

だから魔法の言葉は

   stopSelf();

これまでのところ、サービスがクラッシュする理由はすべて、上記のすべての手順に従って実行してください。


if(Build.VERSION.SDK_INT> = Build.VERSION_CODES.O){stopForeground(true); } else {stopForeground(true); }これ以外の場合、これは何ですか?どちらの場合も同じことをやっていました、stopForeground(true);
user3135923

stopSelf()を配置するつもりだったと思います。stopForeground(true)の代わりにelseブロックの内部;
Justin Stanley、

1
はい、@ JustinStanleyはstopSelf();を使用します。
砂浜:6:

startForeground呼び出されるべきではありませんonCreateか?
Aba

NotificationManagerではなく、Notificationクラスを使用しています。どうすれば実装できますか?
Prajwal W

3

https://developer.android.com/reference/android/content/Context.html#startForegroundService(android.content.Intent)

startService(Intent)と似ていますが、サービスが実行を開始すると、サービスがstartForeground(int、android.app.Notification)を呼び出すという暗黙の約束があります。サービスには、これを行うためのANR間隔に相当する時間が与えられます。それ以外の場合、システムは自動的にサービスを停止し、アプリのANRを宣言します。

通常のstartService(Intent)とは異なり、このメソッドは、サービスをホストしているアプリがフォアグラウンド状態かどうかに関係なく、いつでも使用できます。

Service.startForeground(int, android.app.Notification)onCreate()でを呼び出して、呼び出されることを確認してください。それを妨げる可能性のある条件がある場合は、通常の方法を使用しContext.startService(Intent)Service.startForeground(int, android.app.Notification)自分で呼び出すことをお勧めします。

が破棄さContext.startForegroundService()れるService.startForeground(int, android.app.Notification)前にを呼び出したことを確認するためにウォッチドッグが追加されたようです...


3

onCreate()メソッド内でStartForgroundServicesを呼び出さないでください。ワーカースレッドを作成した後、onStartCommand()でStartForgroundサービスを呼び出す必要があります。そうしないと、常にANRが取得されるため、onStartCommand()のメインスレッドに複雑なログインを記述しないでください。;

public class Services extends Service {

    private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

編集:注意!startForeground関数は最初の引数として0を取ることはできません。例外が発生します!この例には誤った関数呼び出しが含まれています。0を自分の定数に変更してください。0にすることはできません。またはMax(Int32)より大きくすることはできません。


2

startForegroundin を呼び出した後でも、が呼び出される直前にService呼び出すと、一部のデバイスでクラッシュします。そこで、追加のフラグを指定してサービスを開始することで、この問題を修正しました。stopServiceonCreate

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

そしてonStartCommandにチェックが追加され、停止が開始されたかどうかを確認します。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //call startForeground first
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

PSサービスが実行されていない場合、最初にサービスを開始しますが、これはオーバーヘッドです。


2

ターゲットSDK 28以降を使用する場合は、Android 9デバイスに以下の権限を追加する必要があります。そうしないと、例外が常に発生します。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

1

関数をPendingIntent呼び出す前に、nullか どうかをチェックするだけcontext.startForegroundService(service_intent)です。

これは私のために働く

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}

これは実際には、サービスがnullの場合、startForegroundServiceではなくstartServiceにキックするだけです。ifステートメントはネストする必要があります。そうしないと、アプリがクラッシュして、ある時点で新しいバージョンのサービスを使用しようとします。
a54studio

1

ServiceまたはIntentServiceが作成された直後にstartForegroundメソッドを呼び出すだけです。このような:

import android.app.Notification;
public class AuthenticationService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}

1
致命的な例外:メインのandroid.app.RemoteServiceException:startForegroundの不正な通知:java.lang.RuntimeException:サービス通知のチャネルが無効です:Notification(channel = null pri = 0 contentView = null vibrate = null sound = null defaults = 0x0 flags = 0x40 color = 0x00000000 vis = PRIVATE)android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1821)at android.os.Handler.dispatchMessage(Handler.java:106)at android.os.Looper.loop(Looper。 java:164)android.app.ActivityThread.main(ActivityThread.java:6626)
Gerry

1

わかりました。これについて他の人にも役立つかもしれないことに気づきました。これは、私が見ている発生を修正する方法を理解できるかどうかを確認するためのテストからの厳密なものです。簡単にするために、プレゼンターからこれを呼び出すメソッドがあるとします。

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

これは同じエラーでクラッシュします。メソッドが完了するまでサービスは開始されないため、サービスは開始されませんonCreate()

そのため、メインスレッドからUIを更新したとしても、その後にそのメソッドを妨げる可能性があるものがあれば、時間どおりに開始されず、恐ろしいフォアグラウンドエラーが発生します。私の場合、いくつかのものをキューにロードし、それぞれをに呼び出しましたstartForegroundServiceが、いくつかのロジックがバックグラウンドでそれぞれに関与していました。したがって、ロジックが連続して呼び出されてからロジックがそのメソッドを完了するのに時間がかかりすぎると、クラッシュ時間が発生します。老人startServiceはそれを無視してそのままにして、毎回それを呼んだので、次のラウンドは終わりました。

これにより、バックグラウンドでスレッドからサービスを呼び出した場合、開始時に完全にバインドしてすぐに実行できないのではないかと思い、実験を開始しました。これはすぐには起動しませんが、クラッシュしません。

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

メインスレッドがタイムリーに処理できるようになるまで強制的に待機させるのではないかと思われますが、クラッシュしない理由を知るつもりはありません。メインスレッドに関連付けるのは理想的ではないことはわかっていますが、私の使用法ではバックグラウンドで呼び出すため、クラッシュするのではなく完了するまで待機するかどうかは気にしません。


あなたはバックグラウンドからサービスを開始していますか、それとも活動中ですか?
Mateen Chaudhry

バックグラウンド。問題は、Androidがサービスが割り当てられた時間に開始されることを保証できないことです。あなたが望むなら、彼らはそれを無視するためにエラーを投げるだろうと思うでしょうが、残念ながらAndroidではありません。それは致命的でなければなりません。これは私たちのアプリでは役に立ちましたが、完全には修正されませんでした。
a54studio

1

@humazedの回答にコードを追加しています。したがって、最初の通知はありません。回避策かもしれませんが、私にとってはうまくいきます。

@Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

通知に小さなアイコンと色でtransparentColorを追加しています。それが動作します。


0

startService(intent)代わりにを使ってサービスを開始し、直後にContext.startForeground()呼び出す問題を修正しました。さらに、起動時にサービスを開始すると、起動ブロードキャストでサービスを開始するアクティビティを開始できます。これは恒久的な解決策ではありませんが、機能します。startForegound()super.OnCreate()


一部のXiaomiデバイスでは、アクティビティをバックグラウンドから開始できない
Mateen Chaudhry

0

でのデータの更新 onStartCommand(...)

onBind(...)

onBind(...)開始するためのより良いライフサイクルイベントであるstartForeground対のonCreate(...)ためonBind(...)にあるパスIntentで重要なデータを含むことがBundle初期化するために必要ではService。しかし、必要に応じていないonStartCommand(...)ときに呼び出されService、その後の時間の後に初めて作成またはと呼ばれています。

onStartCommand(...)

startForegroundin onStartCommand(...)は、Service作成された後に更新するために重要です。

いつ ContextCompat.startForegroundService(...)の後に呼び出されますService作成されているonBind(...)onCreate(...)呼ばれていません。したがって、更新されたデータはをonStartCommand(...)介してIntent Bundleに渡され、内のデータを更新できますService

サンプル

私はこのパターンを使用して実装しています PlayerNotificationManagerして、Coinverse暗号通貨ニュースアプリにています。

アクティビティ/ Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}

-1

サービス

class TestService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")

        val nBuilder = NotificationCompat.Builder(this, "all")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("TestService")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        startForeground(1337, nBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val rtn = super.onStartCommand(intent, flags, startId)

        if (intent?.action == STOP_ACTION) {
            Log.d(TAG, "onStartCommand -> STOP")
            stopForeground(true)
            stopSelf()
        } else {
            Log.d(TAG, "onStartCommand -> START")
        }

        return rtn
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {

        private val TAG = "TestService"
        private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
        }

        fun stop(context: Context) {
            val intent = Intent(context, TestService::class.java)
            intent.action = STOP_ACTION
            ContextCompat.startForegroundService(context, intent)
        }

    }

}

テスター

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)

start_test_service.setOnClickListener {
    TestService.start(this@MainActivity)
    TestService.stop(this@MainActivity)
}

結果

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.