Ich weiß, es wurden bereits zu viele Antworten veröffentlicht, aber die Wahrheit ist - startForegroundService kann nicht auf App-Ebene repariert werden und Sie sollten es nicht mehr verwenden. Die Empfehlung von Google, die API Service # startForeground () innerhalb von 5 Sekunden nach dem Aufruf von Context # startForegroundService () zu verwenden, kann eine App nicht immer ausführen.
Android führt viele Prozesse gleichzeitig aus und es gibt keine Garantie dafür, dass Looper Ihren Zieldienst aufruft, der startForeground () innerhalb von 5 Sekunden aufrufen soll. Wenn Ihr Zieldienst den Anruf nicht innerhalb von 5 Sekunden erhalten hat, haben Sie kein Glück und Ihre Benutzer werden eine ANR-Situation erleben. In Ihrer Stapelverfolgung sehen Sie ungefähr Folgendes:
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)
Soweit ich weiß, hat Looper die Warteschlange hier analysiert, einen "Missbraucher" gefunden und sie einfach getötet. Das System ist jetzt glücklich und gesund, Entwickler und Nutzer jedoch nicht. Da Google jedoch seine Verantwortlichkeiten auf das System beschränkt, warum sollten sie sich für die beiden letzteren interessieren? Anscheinend nicht. Könnten sie es besser machen? Natürlich hätten sie beispielsweise den Dialog "Anwendung ist beschäftigt" bedienen und einen Benutzer bitten können, eine Entscheidung über das Warten oder Beenden der App zu treffen, aber warum sich die Mühe machen, liegt nicht in ihrer Verantwortung. Die Hauptsache ist, dass das System jetzt gesund ist.
Nach meinen Beobachtungen kommt dies relativ selten vor, in meinem Fall ungefähr 1 Absturz pro Monat für 1K-Benutzer. Es ist unmöglich, es zu reproduzieren, und selbst wenn es reproduziert wird, können Sie nichts tun, um es dauerhaft zu reparieren.
In diesem Thread gab es einen guten Vorschlag, "bind" anstelle von "start" zu verwenden und dann, wenn der Dienst bereit ist, onServiceConnected zu verarbeiten. Dies bedeutet jedoch wiederum, dass startForegroundService-Aufrufe überhaupt nicht verwendet werden.
Ich denke, die richtige und ehrliche Aktion von Google-Seite wäre, allen zu sagen, dass startForegourndServcie einen Mangel hat und nicht verwendet werden sollte.
Die Frage bleibt weiterhin: Was soll stattdessen verwendet werden? Zum Glück gibt es jetzt JobScheduler und JobService, die eine bessere Alternative für Vordergrunddienste darstellen. Aus diesem Grund ist es eine bessere Option:
Während ein Job ausgeführt wird, hält das System im Namen Ihrer App ein Wakelock. Aus diesem Grund müssen Sie keine Maßnahmen ergreifen, um sicherzustellen, dass das Gerät für die Dauer des Auftrags wach bleibt.
Es bedeutet, dass Sie sich nicht mehr um den Umgang mit Wakelocks kümmern müssen, und deshalb unterscheidet es sich nicht von Vordergrunddiensten. Aus Sicht der Implementierung ist JobScheduler nicht Ihr Dienst, sondern ein System, vermutlich wird es die Warteschlange richtig behandeln und Google wird niemals sein eigenes Kind beenden :)
Samsung hat in seinem Samsung Accessory Protocol (SAP) von startForegroundService auf JobScheduler und JobService umgestellt. Dies ist sehr hilfreich, wenn Geräte wie Smartwatches mit Hosts wie Telefonen kommunizieren müssen, bei denen der Job über den Hauptthread einer App mit einem Benutzer interagieren muss. Da die Jobs vom Scheduler an den Hauptthread gesendet werden, wird dies möglich. Sie sollten sich jedoch daran erinnern, dass der Job auf dem Hauptthread ausgeführt wird, und alle schweren Inhalte auf andere Threads und asynchrone Aufgaben auslagern.
Dieser Dienst führt jeden eingehenden Job auf einem Handler aus, der im Hauptthread Ihrer Anwendung ausgeführt wird. Dies bedeutet, dass Sie Ihre Ausführungslogik auf einen anderen Thread / Handler / AsyncTask Ihrer Wahl auslagern müssen
Die einzige Gefahr beim Wechsel zu JobScheduler / JobService besteht darin, dass Sie alten Code umgestalten müssen, und es macht keinen Spaß. Ich habe die letzten zwei Tage damit verbracht, die SAP-Implementierung des neuen Samsung zu nutzen. Ich werde meine Absturzberichte ansehen und Sie wissen lassen, ob die Abstürze erneut auftreten. Theoretisch sollte es nicht passieren, aber es gibt immer Details, die uns möglicherweise nicht bekannt sind.
UPDATE
Keine Abstürze mehr vom Play Store gemeldet. Dies bedeutet, dass JobScheduler / JobService kein solches Problem haben und der Wechsel zu diesem Modell der richtige Ansatz ist, um das Problem startForegroundService ein für alle Mal zu beseitigen. Ich hoffe, Google / Android liest es und wird irgendwann eine offizielle Anleitung für alle kommentieren / beraten / bereitstellen.
UPDATE 2
Für diejenigen, die SAP verwenden und fragen, wie SAP V2 JobService verwendet, finden Sie unten eine Erklärung.
In Ihrem benutzerdefinierten Code müssen Sie SAP initialisieren (es ist Kotlin):
SAAgentV2.requestAgent(App.app?.applicationContext,
MessageJobs::class.java!!.getName(), mAgentCallback)
Jetzt müssen Sie den Samsung-Code dekompilieren, um zu sehen, was im Inneren vor sich geht. Sehen Sie sich in SAAgentV2 die Implementierung von requestAgent und die folgende Zeile an:
SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);
where d defined as below
private SAAdapter d;
Wechseln Sie jetzt zur SAAdapter-Klasse und suchen Sie die Funktion onServiceConnectionRequested, mit der ein Job mithilfe des folgenden Aufrufs geplant wird:
SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12);
SAJobService ist nur eine Implementierung von Android'd JobService und dies ist diejenige, die eine Jobplanung durchführt:
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());
}
Wie Sie sehen, verwendet die letzte Zeile hier Android'd JobScheduler, um diesen Systemdienst abzurufen und einen Job zu planen.
Im requestAgent-Aufruf haben wir mAgentCallback übergeben, eine Rückruffunktion, die die Kontrolle erhält, wenn ein wichtiges Ereignis eintritt. So wird der Rückruf in meiner App definiert:
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 hier ist eine Klasse, die ich implementiert habe, um alle Anfragen von einer Samsung-Smartwatch zu verarbeiten. Es ist nicht der vollständige Code, nur ein Skelett:
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) {
}
}
Wie Sie sehen, erfordert MessageJobs auch die MessageSocket-Klasse, die Sie implementieren müssten, und die alle von Ihrem Gerät kommenden Nachrichten verarbeitet.
Unterm Strich ist es nicht so einfach und erfordert einige Eingriffe in Interna und Codierung, aber es funktioniert und vor allem - es stürzt nicht ab.