すでに多くの回答が公開されていますが、真実は-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クラスも必要です。これは、実装する必要があり、デバイスから送信されるすべてのメッセージを処理する必要があります。
結論として、それはそれほど単純ではなく、内部とコーディングを掘り下げる必要がありますが、それは機能し、そして最も重要なことです-クラッシュしません。