Wiem, że opublikowano już zbyt wiele odpowiedzi, jednak prawda jest taka - startForegroundService nie może zostać naprawiony na poziomie aplikacji i powinieneś przestać go używać. To zalecenie Google dotyczące korzystania z funkcji API # startForeground () w ciągu 5 sekund po wywołaniu kontekstu # startForegroundService () nie jest czymś, co aplikacja może zawsze zrobić.
Android uruchamia wiele procesów jednocześnie i nie ma żadnej gwarancji, że Looper wywoła usługę docelową, która ma wywołać startForeground () w ciągu 5 sekund. Jeśli usługa docelowa nie otrzyma połączenia w ciągu 5 sekund, nie będziesz miał szczęścia, a Twoi użytkownicy doświadczą sytuacji ANR. W śladzie stosu zobaczysz coś takiego:
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)
Jak rozumiem, Looper przeanalizował tutaj kolejkę, znalazł „napastnika” i po prostu go zabił. System jest teraz szczęśliwy i zdrowy, a programiści i użytkownicy nie, ale skoro Google ogranicza swoje obowiązki do systemu, dlaczego mieliby się przejmować tymi dwoma ostatnimi? Najwyraźniej nie. Czy mogą to poprawić? Oczywiście, np. Mogliby wyświetlić okno dialogowe „Aplikacja jest zajęta”, prosząc użytkownika o podjęcie decyzji o czekaniu lub zabiciu aplikacji, ale dlaczego to przeszkadza, to nie jest ich odpowiedzialność. Najważniejsze, że system jest teraz zdrowy.
Z moich obserwacji wynika, że zdarza się to stosunkowo rzadko, w moim przypadku około 1 awarii w miesiącu dla 1 000 użytkowników. Reprodukcja jest niemożliwa, a nawet jeśli jest reprodukowana, nic nie możesz zrobić, aby to naprawić na stałe.
W tym wątku była dobra sugestia, aby użyć „wiązania” zamiast „startu”, a następnie, gdy usługa jest gotowa, należy przetworzyć onServiceConnected, ale znowu oznacza to, że w ogóle nie używa wywołań startForegroundService.
Myślę, że właściwym i uczciwym działaniem ze strony Google byłoby poinformowanie wszystkich, że startForegourndServcie ma wadę i nie należy go stosować.
Pozostaje pytanie: czego zamiast tego użyć? Na szczęście dla nas są teraz JobScheduler i JobService, które są lepszą alternatywą dla usług pierwszego planu. Jest to lepsza opcja, ponieważ:
Podczas wykonywania zadania system wstrzymuje funkcję wakelock w imieniu Twojej aplikacji. Z tego powodu nie trzeba podejmować żadnych działań, aby zagwarantować, że urządzenie pozostanie w trybie czuwania przez czas trwania zadania.
Oznacza to, że nie musisz już martwić się obsługą wakelocków i dlatego nie różni się niczym od usług pierwszego planu. Z punktu widzenia implementacji JobScheduler nie jest twoją usługą, jest usługą systemową, prawdopodobnie dobrze poradzi sobie z kolejką, a Google nigdy nie zakończy własnego dziecka :)
Samsung przeszedł z startForegroundService na JobScheduler i JobService w swoim Samsung Accessory Protocol (SAP). Jest to bardzo pomocne, gdy urządzenia takie jak smartwatche muszą rozmawiać z hostami, takimi jak telefony, gdzie praca musi wchodzić w interakcję z użytkownikiem za pośrednictwem głównego wątku aplikacji. Ponieważ zadania są wysyłane przez program planujący do głównego wątku, staje się to możliwe. Powinieneś jednak pamiętać, że zadanie działa w głównym wątku i odciążyć wszystkie ciężkie rzeczy do innych wątków i zadań asynchronicznych.
Ta usługa wykonuje każde przychodzące zadanie w module obsługi działającym w głównym wątku aplikacji. Oznacza to, że musisz odciążyć logikę wykonania do innego wybranego wątku / procedury obsługi / AsyncTask
Jedyną pułapką przejścia na JobScheduler / JobService jest to, że będziesz musiał przebudować stary kod i nie jest to zabawne. Ostatnie dwa dni spędziłem, robiąc to, aby skorzystać z nowej implementacji SAP nowego Samsunga. Będę oglądać moje raporty o awariach i dam ci znać, jeśli ponownie zobaczę awarie. Teoretycznie nie powinno się to zdarzyć, ale zawsze są szczegóły, o których możemy nie wiedzieć.
AKTUALIZACJA
Żadnych awarii nie zgłoszono w Sklepie Play. Oznacza to, że JobScheduler / JobService nie mają takiego problemu, a przejście do tego modelu jest właściwym podejściem do pozbycia się problemu startForegroundService raz na zawsze. Mam nadzieję, że Google / Android to czyta i ostatecznie skomentuje / doradzi / zapewni oficjalne wskazówki dla wszystkich.
AKTUALIZACJA 2
Dla tych, którzy korzystają z SAP i pytają, w jaki sposób SAP V2 wykorzystuje JobService, wyjaśnienie znajduje się poniżej.
W niestandardowym kodzie musisz zainicjować SAP (to Kotlin):
SAAgentV2.requestAgent(App.app?.applicationContext,
MessageJobs::class.java!!.getName(), mAgentCallback)
Teraz musisz zdekompilować kod Samsunga, aby zobaczyć, co się dzieje w środku. W SAAgentV2 spójrz na implementację requestAgent i następujący wiersz:
SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);
where d defined as below
private SAAdapter d;
Przejdź teraz do klasy SAAdapter i znajdź funkcję onServiceConnectionRequested, która planuje zadanie za pomocą następującego wywołania:
SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12);
SAJobService to tylko implementacja Job'dervice dla systemu Android, a ta wykonuje harmonogram zadań:
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());
}
Jak widzisz, ostatni wiersz tutaj używa Job'dchedulera na Androida, aby uzyskać tę usługę systemową i zaplanować zadanie.
W wywołaniu requestAgent przekazaliśmy mAgentCallback, która jest funkcją oddzwaniania, która przejmie kontrolę, gdy wydarzy się ważne zdarzenie. Oto jak funkcja zwrotna jest zdefiniowana w mojej aplikacji:
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 tutaj to klasa, którą zaimplementowałem, aby przetwarzać wszystkie żądania pochodzące od smartwatcha Samsung. To nie jest pełny kod, tylko szkielet:
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) {
}
}
Jak widać, MessageJobs wymaga również klasy MessageSocket, którą należy zaimplementować i która przetwarza wszystkie wiadomości przychodzące z urządzenia.
Podsumowując, nie jest to takie proste i wymaga trochę kopania do elementów wewnętrznych i kodowania, ale działa, a co najważniejsze - nie ulega awarii.