Context.startForegroundService () nie wywołał następnie Service.startForeground ()


258

ja używam Service Class w systemie operacyjnym Android.

Planuję użyć Servicew tle.

Dokumentacja Androida stwierdza, że

Jeśli aplikacja jest kierowana na interfejs API na poziomie 26 lub wyższym, system nakłada ograniczenia na używanie lub tworzenie usług w tle, chyba że sama aplikacja jest na pierwszym planie. Jeśli aplikacja musi utworzyć usługę pierwszego planu, powinna zadzwonić startForegroundService().

Jeśli użyjesz startForegroundService(), Servicezgłasza następujący błąd.

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

Co jest z tym nie tak?


IOW, proszę podać minimalny odtwarzalny przykład . Obejmuje to cały ślad stosu Java i kod wywołujący awarię.
CommonsWare

3
Błąd nadal występuje w API 26 i 27 (27.0.3). Wersje Androida, których dotyczy problem, to 8.0 i 8.1. Można zmniejszyć liczbę awarii, dodając startForeground () zarówno do onCreate (), jak i onStartCommand (), ale awarie nadal występują u niektórych użytkowników. Jedynym sposobem, aby to naprawić atm, jest targetSdkVersion 25 w build.gradle.
Alex


1
Teraz używam tego obejścia. działa jak urok theandroiddeveloper.com/blog/…
Prasad Pawar

1
Mam ten sam problem. Naprawiam ten problem. Udostępniłem moją implementację w tym temacie stackoverflow.com/questions/55894636/...
Beyazid

Odpowiedzi:


99

Z dokumentów Google na temat zmian w zachowaniu Androida 8.0 :

System umożliwia aplikacjom wywoływanie Context.startForegroundService (), nawet gdy aplikacja jest w tle. Jednak aplikacja musi wywołać metodę startForeground () tej usługi w ciągu pięciu sekund po jej utworzeniu.

Rozwiązanie: Zaproszenie startForeground()w onCreate()dla Servicektórego używaszContext.startForegroundService()

Zobacz także: Limity wykonywania w tle dla Androida 8.0 (Oreo)


19
Zrobiłem to w onStartCommandmetodzie, ale wciąż pojawia się ten błąd. Zadzwoniłem startForegroundService(intent)do mnie MainActivity. Być może usługa jest uruchamiana zbyt wolno. Myślę, że limit pięciu sekund nie powinien istnieć, zanim będą mogli obiecać, że usługa zostanie natychmiast uruchomiona.
Kimi Chiu

29
5 sekund to zdecydowanie za mało, ten wyjątek zdarza się bardzo często w sesjach debugowania. Podejrzewam, że od czasu do czasu MOGĄ się zdarzyć w trybie zwolnienia. I będąc fatalnym WYJĄTKIEM, po prostu powoduje awarię aplikacji! Próbowałem złapać go za pomocą Thread.setDefaultUncaughtExceptionHandler (), ale nawet po uzyskaniu go i zignorowaniu Androida zawiesza aplikację. To dlatego, że ten wyjątek jest wyzwalany z metody handleMessage (), a główna pętla skutecznie kończy się ... szukając obejścia.
southerton

W odbiorniku emisji wywołałem metodę Context.startForegroundService (). więc jak sobie poradzić w tej sytuacji? ponieważ onCreate () nie jest dostępny w odbiorniku
Anand Savjani

6
@southerton Myślę, że powinieneś zadzwonić onStartCommand()zamiast tego onCreate, ponieważ jeśli zamkniesz usługę i uruchomisz ją ponownie, może się udać onStartCommand()bez dzwonienia onCreate...
deweloper Androida

1
Tutaj możemy sprawdzić odpowiedź od zespołu google Issuetracker.google.com/issues/76112072#comment56
Prags

82

dzwoniłem ContextCompat.startForegroundService(this, intent) aby uruchomić usługę

Czynny 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
Ja też. Ale wciąż pojawia się ten błąd od czasu do czasu. Może Android nie może zagwarantować, że zadzwoni za onCreate5 sekund. Powinni więc przeprojektować go, zanim zmuszą nas do przestrzegania zasad.
Kimi Chiu

5
Dzwoniłem do startForeground w onStartCommand () i od czasu do czasu otrzymywałem ten błąd. Przeniosłem go na onCreate i od tego czasu go nie widziałem (kciuki)
Tyler

4
Spowoduje to utworzenie lokalnego powiadomienia w Oreo z informacją: „APP_NAME jest uruchomiony. Stuknij, aby zamknąć lub wyświetlić informacje”. Jak zatrzymać wyświetlanie tego powiadomienia?
Artist404,

6
Nie, zespół Android twierdził, że jest to zamierzone zachowanie. Właśnie przeprojektowałem moją aplikację. To jest niedorzeczne. Zawsze musisz przeprojektować aplikacje ze względu na te „zamierzone zachowanie”. To trzeci raz.
Kimi Chiu

12
Główną przyczyną tego problemu jest zatrzymanie usługi przed awansem na pierwszy plan. Ale twierdzenie nie ustało po zniszczeniu usługi. Możesz spróbować to odtworzyć, dodając StopServicepo rozmowie telefonicznej startForegroundService.
Kimi Chiu

55

Przyczyną tego problemu jest to, że system Android nie może zagwarantować, że usługa zostanie uruchomiona w ciągu 5 sekund, ale z drugiej strony system ma ścisły limit powiadomień na pierwszym planie musi zostać uruchomiony w ciągu 5 sekund, bez sprawdzania, czy system próbował uruchomić usługę .

Jest to z pewnością kwestia ramowa, ale nie wszyscy programiści, którzy napotykają ten problem, starają się:

  1. startForegroundpowiadomienie musi być w obu przypadkach, onCreatea onStartCommandponieważ jeśli twoja usługa jest już utworzona i jakoś twoja aktywność próbuje ją uruchomić ponownie, onCreatenie zostanie wywołana.

  2. identyfikator powiadomienia nie może wynosić 0, w przeciwnym razie nastąpi taka sama awaria, nawet nie z tego samego powodu.

  3. stopSelfnie wolno wcześniej nazywać startForeground.

Przy wszystkich powyższych 3 problem ten można nieco zmniejszyć, ale nadal nie jest to poprawka, prawdziwa poprawka lub, powiedzmy, obejściem problemu jest obniżenie docelowej wersji SDK do 25.

I zauważ, że najprawdopodobniej Android P nadal będzie nosił ten problem, ponieważ Google nie chce nawet zrozumieć, co się dzieje i nie wierzy, że to ich wina, przeczytaj nr 36 i 56, aby uzyskać więcej informacji


9
Dlaczego zarówno onCreate, jak i onStartCommand? Czy możesz to po prostu ustawić w witrynie StartStommand?
rcell,

StopSelf nie może być wywoływany przed startForeground => lub bezpośrednio po nim, ponieważ wydaje się, że anuluje on „startForeground”.
Frank

1
Przeprowadziłem kilka testów i nawet jeśli onCreate nie zostanie wywołany, jeśli usługa jest już utworzona, nie musisz ponownie wywoływać startForeground. Dlatego możesz wywoływać go tylko w metodzie onCreate, a nie w onStartCommand. Prawdopodobnie uważają pierwszą instancję usługi za znajdującą się na pierwszym planie po pierwszym wywołaniu do końca.
Lxu

Używam 0 jako identyfikatora powiadomienia dla pierwszego połączenia startForeground. Zmiana na 1 rozwiązuje problem (mam nadzieję)
mr5

Więc jakie jest rozwiązanie?
IgorGanapolsky

35

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.


Dobra odpowiedź, ale istnieje poważny problem, JobIntentServicedziała natychmiast jako IntentServiceOreo poniżej, ale planuje Zadanie w Oreo i powyżej, więc JobIntentServicenie uruchamia się natychmiast. Więcej informacji
CopsOnRoad,

1
@CopsOnRoad Jest to bardzo przydatne i zaczyna się natychmiast w moim przypadku, jak napisałem, służy do interakcji w czasie rzeczywistym między telefonem a smartwatchem. Nie ma mowy, że użytkownik poczekałby 15 minut na przesłanie danych między tymi dwoma. Działa bardzo dobrze i nigdy nie ulega awarii.
Oleg Gryb

1
Brzmi fajnie, sprawiłoby, że twoja odpowiedź byłaby warta, gdybyś mógł udostępnić przykładowy kod, jestem nowym użytkownikiem Androida i okazało się, że Job...uruchomienie zajmuje dużo czasu i jest to zaawansowana wersja AlarmManager.
CopsOnRoad

2
Prawdopodobnie zrobię to, gdy pozwoli na to czas: będę musiał oddzielić go od niestandardowego kodu i skompilować kilka zastrzeżonych bibliotek Samsunga
Oleg Gryb

1
@batmaci - JobService jest wewnętrznie wykorzystywany przez SAP. Szczegółowe informacje można znaleźć w aktualizacji 2 w OP.
Oleg Gryb

32

Aplikacja zawiesi się, jeśli zadzwonisz, Context.startForegroundService(...)a następnie zadzwonisz Context.stopService(...)wcześniej Service.startForeground(...).

Mam jasne repro tutaj ForegroundServiceAPI26

Otworzyłem błąd na ten temat: Google tracker problemów

Kilka błędów w tym zakresie zostało otwartych i zamkniętych. Nie naprawi się.

Mam nadzieję, że moje z wyraźnymi krokami repro zrobi cięcie.

Informacje dostarczone przez zespół Google

Narzędzie do śledzenia problemów Google Komentarz 36

To nie jest błąd ramowy; to celowe. Jeśli aplikacja uruchamia wystąpienie usługi startForegroundService(), musi przenieść to wystąpienie usługi do stanu pierwszego planu i wyświetlić powiadomienie. Jeśli wystąpienie usługi zostanie zatrzymane przed startForeground()wywołaniem go, obietnica jest niespełniona: jest to błąd w aplikacji.

Do nr 31 publikowanie usługi, którą inne aplikacje mogą uruchomić bezpośrednio, jest zasadniczo niebezpieczne. Możesz to nieco złagodzić, traktując wszystkie działania początkowe tej usługi jako wymagające startForeground(), choć oczywiście nie o to ci chodziło.

Narzędzie do śledzenia problemów Google Komentarz 56

Istnieje kilka różnych scenariuszy, które prowadzą do tego samego rezultatu.

Bezpośredni problem semantyczny, polegający na tym, że po prostu błędem jest rozpoczęcie czegoś, startForegroundService()ale zaniedbanie przeniesienia go na pierwszy planstartForeground() , jest taki: problem semantyczny. Celowo jest to traktowane jako błąd aplikacji. Zatrzymanie usługi przed przeniesieniem jej na pierwszy plan jest błędem aplikacji. To był sedno PO i dlatego kwestia ta została oznaczona jako „działająca zgodnie z przeznaczeniem”.

Istnieją jednak pytania dotyczące fałszywego wykrywania tego problemu. Jest to traktowane jako prawdziwy problem, chociaż jest śledzone oddzielnie od tego konkretnego problemu ze śledzeniem błędów. Nie jesteśmy głusi na skargę.


2
Najlepsza aktualizacja, jaką widziałem, to Issuetracker.google.com/issues/76112072#comment56 . Przepisałem kod, aby użyć Context.bindService, co całkowicie eliminuje problematyczne wywołanie Context.startForegroundService. Mój przykładowy kod możesz zobaczyć na github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/…
swooby

tak, jak napisałem, bind powinien działać, ale już przełączyłem się na JobServive i nie zamierzam niczego zmieniać, chyba że ten też się zepsuje ”:)
Oleg Gryb

19

Badałem to przez kilka dni i znalazłem rozwiązanie. Teraz w Androidzie O możesz ustawić ograniczenie tła, jak poniżej

Usługa wywołująca klasę usługi

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);
}

i klasa usługi powinna być jak

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
Czy wypróbowałeś to w aplikacji, która ma co najmniej kilka tysięcy użytkowników? Próbowałem, niektórzy użytkownicy nadal mają problemy z awarią.
Alex

Nie, nie. Używam dokładnego kodu i nie widzę awarii klienta.
Ahmad Arslan

Ilu masz użytkowników? Codziennie (z błędem) widziałem ~ 10-30 + awarii w aplikacji, która miała 10 000 użytkowników dziennie. Oczywiście zdarza się to tylko w przypadku użytkowników z Androidem 8.0 i Androidem 8.1, w przypadku gdy celSdkVersion ma 27 lat. Wszystkie problemy znikają po 0 awarii zaraz po ustawieniu targetSdkVersion na 25.
Alex

tylko 2200 użytkowników: - / ale nie doszło do awarii
Ahmad Arslan

1
@Jeeva, niezupełnie. Atm, używam targetSdkVersion 25 z compileSdkVersion 27. Wygląda na to, że najlepiej po wielu eksperymentach .... Mam nadzieję, że skończą developer.android.com/topic/libraries/architecture/... przed sierpniem 2018, ponieważ android-developers.googleblog .com / 2017/12 /…
Alex

16

Mam widżet który dokonuje stosunkowo częstych aktualizacji, gdy urządzenie nie działa i widziałem tysiące awarii w ciągu zaledwie kilku dni.

Wyzwalacz problemu

Nawet zauważyłem ten problem, nawet na moim Pixel 3 XL, kiedy nie sądziłem, że urządzenie w ogóle ma duże obciążenie. I wszystkie ścieżki kodu zostały pokryte startForeground(). Ale potem zdałem sobie sprawę, że w wielu przypadkach moja służba wykonuje pracę naprawdę szybko. Uważam, że przyczyną mojej aplikacji było zakończenie usługi, zanim system zaczął wyświetlać powiadomienia.

Obejście / rozwiązanie

Byłem w stanie pozbyć się wszystkich awarii. To, co zrobiłem, to usunięcie połączenia z stopSelf(). (Myślałem o opóźnieniu zatrzymania, dopóki nie byłem prawie pewien, że powiadomienie zostało wyświetlone, ale nie chcę, aby użytkownik zobaczył powiadomienie, jeśli nie jest konieczne.) Gdy usługa była bezczynna przez minutę lub system niszczy go normalnie, nie zgłaszając żadnych wyjątków.

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

11

Tylko jedna głowa, ponieważ zmarnowałem na to zbyt wiele godzin. Cały czas otrzymywałem ten wyjątek, mimo że dzwoniłem startForeground(..)jako pierwszy onCreate(..). W końcu odkryłem, że problem był spowodowany użyciem NOTIFICATION_ID = 0. Wydaje się, że użycie jakiejkolwiek innej wartości to naprawić.


W moim przypadku korzystałem z ID 1, dzięki, problem został rozwiązany
Amin Pinjari

4
Korzystam z identyfikatora 101, wciąż czasami otrzymuję ten błąd.
CopsOnRoad

9

Mam rozwiązanie tego problemu. Sprawdziłem tę poprawkę we własnej aplikacji (300 KB + DAU), która może zmniejszyć co najmniej 95% tego rodzaju awarii, ale nadal nie mogę w 100% uniknąć tego problemu.

Ten problem występuje nawet wtedy, gdy wywołujesz funkcję startForeground () zaraz po uruchomieniu usługi zgodnie z dokumentacją Google. Może się tak zdarzyć, ponieważ proces tworzenia i inicjowania usługi kosztuje już ponad 5 sekund w wielu scenariuszach, bez względu na to, kiedy i gdzie wywołasz metodę startForeground (), ta awaria jest nieunikniona.

Moim rozwiązaniem jest upewnienie się, że startForeground () zostanie wykonany w ciągu 5 sekund po metodzie startForegroundService (), bez względu na to, jak długo twoja usługa musi zostać utworzona i zainicjowana. Oto szczegółowe rozwiązanie.

  1. Nie używaj startForegroundService w pierwszej kolejności, użyj bindService () z flagą auto_create. Będzie czekać na inicjalizację usługi. Oto kod, moja przykładowa usługa to 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. Oto implementacja 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. Najważniejsza część, implementacja MusicService, forceForeground () zapewni, że metoda startForeground () zostanie wywołana zaraz po 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. Jeśli chcesz uruchomić fragment kodu z kroku 1 w zamierzonym celu, na przykład jeśli chcesz uruchomić usługę pierwszego planu w widżecie (kliknij przycisk widgetu) bez otwierania aplikacji, możesz owinąć fragment kodu w odbiorniku emisji i uruchom zdarzenie rozgłoszeniowe zamiast polecenia uruchomienia usługi.

To wszystko. Mam nadzieję, że to pomoże. Powodzenia.



7

Ponieważ wszyscy tutaj odwiedzający cierpią z tego samego powodu, chcę podzielić się moim rozwiązaniem, którego nikt wcześniej nie próbował (w każdym razie w tym pytaniu). Zapewniam cię, że działa, nawet po zatrzymaniu punktu przerwania który potwierdza tę metodę.

Problemem jest zadzwonić Service.startForeground(id, notification)z samej usługi, prawda? Android Framework niestety nie gwarantuje zadzwonienia Service.startForeground(id, notification)w ciągu Service.onCreate()5 sekund, ale i tak zgłasza wyjątek, więc wymyśliłem ten sposób.

  1. Powiąż usługę z kontekstem za pomocą segregatora z usługi przed wywołaniem Context.startForegroundService()
  2. Jeśli powiązanie zakończy się powodzeniem, zadzwoń Context.startForegroundService() z połączenia serwisowego i natychmiast zadzwoń Service.startForeground() w ramach połączenia serwisowego.
  3. WAŻNA UWAGA: Wywołaj Context.bindService()metodę wewnątrz try-catch, ponieważ w niektórych przypadkach wywołanie może zgłosić wyjątek, w którym to przypadku musisz polegać na Context.startForegroundService()bezpośrednim wywołaniu i mieć nadzieję, że nie zawiedzie. Przykładem może być kontekst odbiornika rozgłoszeniowego, jednak uzyskanie kontekstu aplikacji nie rzuca wyjątku w tym przypadku, ale bezpośrednie użycie kontekstu.

Działa to nawet, gdy czekam na punkcie przerwania po powiązaniu usługi i przed wywołaniem wywołania „startForeground”. Oczekiwanie między 3-4 sekundami nie wyzwala wyjątku, a po 5 sekundach powoduje wyjątek. (Jeśli urządzenie nie może wykonać dwóch linii kodu w ciągu 5 sekund, nadszedł czas, aby wyrzucić to do kosza.)

Zacznij więc od utworzenia połączenia serwisowego.

// 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..");
    }
};

Wewnątrz usługi utwórz segregator, który zwróci wystąpienie usługi.

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;
    }
}

Następnie spróbuj powiązać usługę z tego kontekstu. Jeśli się powiedzie, wywoła ServiceConnection.onServiceConnected()metodę z połączenia usługi, którego używasz. Następnie obsłuż logikę w powyższym kodzie. Przykładowy kod wyglądałby tak:

// 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));
}

Wygląda na to, że działa na opracowywanych przeze mnie aplikacjach, więc powinien działać również przy próbie.


5

Problem z Androidem O API 26

Jeśli zatrzymasz usługę od razu (więc Twoja usługa tak naprawdę nie działa (sformułowanie / rozumienie) i jesteś znacznie poniżej przedziału ANR, nadal musisz wywołać startForeground przed stopSelf

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

Próbowałem tego podejścia, ale nadal powoduje błąd: -

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

Używam tego, dopóki błąd nie zostanie rozwiązany

mContext.startService(playIntent);

3
Powinno być Util.SDK_INT> = 26, a nie tylko większe
Andris

4

Mam ten sam problem i po spędzeniu czasu na znalezieniu rozwiązania możesz wypróbować poniższy kod. Jeśli używasz, Serviceumieść ten kod w polu onCreate, jeśli używasz, Intent Serviceumieść ten kod w polu 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

Badałem ten problem i właśnie to odkryłem. Ta awaria może się zdarzyć, jeśli mamy kod podobny do tego:

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);

Wyjątek jest zgłaszany w następującym bloku kodu:

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);
         }
    }
    ...
}

Metoda ta jest wykonywana przed onCreate()wśród MyForegroundServiceponieważ harmonogramy Android tworzenia służby na głównej obsługi wątku, ale bringDownServiceLockednazywa się na zasadzie BinderThread, wich jest sytuacja wyścigu. Oznacza to, że MyForegroundServicenie miałem okazji zadzwonićstartForeground co spowoduje awarię.

W tym celu musimy się upewnić, że ustalenie bringDownServiceLockednie jest wywoływana przed onCreate()o 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));
    }
}

Za pomocą transmisji trwałych mamy pewność, że program nie zgubić i stopReceiverodbiera przystanek zamiaru tak szybko, jak to zostało zarejestrowane w onCreate()od MyForegroundService. Do tego czasu już dzwoniliśmy startForeground(...). Musimy również usunąć tę lepką transmisję, aby następnym razem nie powiadomić stopReceiver.

Należy pamiętać, że metoda sendStickyBroadcastjest przestarzała i używam jej tylko jako tymczasowego obejścia tego problemu.


Powinieneś wywołać go zamiast context.stopService (serviceIntent), jeśli chcesz zatrzymać usługę.
makovkastar

4

Tak wiele odpowiedzi, ale żadna nie działała w moim przypadku.

Rozpocząłem taką usługę.

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

I w mojej usłudze 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);
    }

I nie zapomnij ustawić NOTIFICATION_ID na zero

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

Więc wszystko było idealne, ale wciąż zawiesza się na 8.1, więc przyczyną było jak poniżej.

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

Zadzwoniłem do zatrzymania pierwszego planu z usunięciem powiadomienia, ale po usunięciu powiadomienia usługa staje się tłem, a usługa w tle nie może działać w Androidzie O z tła. rozpoczęty po otrzymaniu wypychania.

Tak magiczne słowo brzmi

   stopSelf();

Jak dotąd, z jakiegokolwiek powodu awarii usługi, wykonaj wszystkie powyższe kroki i ciesz się.


if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (true); } co to jest, jeśli jeszcze? w obu przypadkach robiłeś to samo, stopForeground (true);
user3135923

Wierzę, że chciałeś umieścić stopSelf (); wewnątrz bloku else zamiast stopForeground (true);
Justin Stanley

1
Tak, @JustinStanley używa stopSelf ();
piaszczysty

Nie startForegroundnależy się wzywać onCreate?
Aba

Używam NotificationManager i nie klasy Notification. Jak mogę to zaimplementować?
Prajwal W

3

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

Podobne do startService (Intent), ale z domyślną obietnicą, że usługa wywoła startForeground (int, android.app.Notification), gdy zacznie działać. Usługa ma na to czas porównywalny z interwałem ANR, w przeciwnym razie system automatycznie zatrzyma usługę i zadeklaruje ANR aplikacji.

W przeciwieństwie do zwykłego startService (Intent), tej metody można użyć w dowolnym momencie, niezależnie od tego, czy aplikacja hostująca usługę jest w stanie pierwszoplanowym.

upewnij się, że wywołujesz funkcję Service.startForeground(int, android.app.Notification)onCreate (), więc upewnisz się, że zostanie ona wywołana .. jeśli masz jakieś warunki, które mogą Ci to uniemożliwić, lepiej skorzystaj z normalnego Context.startService(Intent)i zadzwoń do Service.startForeground(int, android.app.Notification)siebie.

Wygląda na to, że Context.startForegroundService()dodaje strażnika, aby upewnić się, że zadzwoniłeś, Service.startForeground(int, android.app.Notification)zanim został zniszczony ...


3

Proszę nie wywoływać żadnych StartForgroundServices w metodzie onCreate () , musisz wywołać usługi StartForground w onStartCommand () po utworzeniu wątku roboczego, w przeciwnym razie zawsze otrzymasz ANR, więc nie pisz skomplikowanego logowania w głównym wątku 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);

    }
}

EDYCJA: Uwaga! Funkcja startForeground nie może przyjąć 0 jako pierwszego argumentu, spowoduje wyjątek! ten przykład zawiera nieprawidłowe wywołanie funkcji, zmień 0 na własną stałą, która nie może być równa 0 lub większa niż Max (Int32)


2

Nawet po wywołaniu startForegroundinService , on awarii na niektórych urządzeniach, jeżeli nazywamy stopServicetuż przed onCreatenazywa. Rozwiązałem ten problem, uruchamiając usługę z dodatkową flagą:

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

i dodał check onStartCommand, aby sprawdzić, czy zaczął się zatrzymywać:

@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 Gdyby usługa nie była uruchomiona, najpierw uruchomiłaby usługę, co jest narzutem.


2

Musisz dodać pozwolenie poniżej dla urządzenia z systemem Android 9, gdy używasz docelowego pakietu SDK 28 lub nowszego, w przeciwnym razie wyjątek zawsze się zdarza:

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

1

Sprawdzam tylko PendingIntentzero lub nie przed wywołaniem context.startForegroundService(service_intent)funkcji.

to działa dla mnie

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);
        }
}

W rzeczywistości powoduje to uruchomienie usługi startService zamiast startForegroundService, jeśli ma wartość NULL. Instrukcja if musiałaby być zagnieżdżona, w przeciwnym razie aplikacja zawiesiłaby się przy próbie użycia usług w późniejszej wersji.
a54studio

1

wystarczy wywołać metodę startForeground natychmiast po utworzeniu usługi lub usługi IntentService. lubię to:

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

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

FATAL EXCEPTION: main android.app.RemoteServiceException: Złe powiadomienie dla startForeground: java.lang.RuntimeException: niepoprawny kanał dla powiadomienia serwisowego: Powiadomienie (channel = null pri = 0 contentView = null vibrate = null sound = null defaults = 0x0 flags = 0x40 kolor = 0x00000000 vis = PRYWATNY) w android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) w android.os.Handler.dispatchMessage (Handler.java:106) w android.os.Looper.loop (Looper. java: 164) w android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry

1

Ok, zauważyłem coś, co może pomóc również kilku innym. Jest to ściśle z testowania, czy mogę wymyślić, jak naprawić zdarzenia, które widzę. Dla uproszczenia, powiedzmy, że mam metodę, która wywołuje to od prezentera.

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

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

To się zawiesi z tym samym błędem. Usługa NIE uruchomi się, dopóki metoda nie zostanie zakończona, dlatego nie onCreate()w usłudze.

Więc nawet jeśli zaktualizujesz interfejs użytkownika poza głównym wątkiem, JEŻELI masz coś, co może zatrzymać tę metodę po nim, nie uruchomi się on na czas i da ci przerażający błąd na pierwszym planie. W moim przypadku ładowaliśmy niektóre rzeczy do kolejki i każda z nich była wywoływana startForegroundService, ale w tle działała pewna logika. Jeśli więc logika zajęła zbyt dużo czasu, aby ukończyć tę metodę, ponieważ były one wywoływane jeden po drugim, czas awarii. Stary startServicepo prostu zignorował to i poszedł dalej, a ponieważ dzwoniliśmy za każdym razem, następna runda dobiegała końca.

Zastanawiałem się, czy jeśli zadzwonię do usługi z wątku w tle, czy nie można go całkowicie związać na starcie i uruchomić natychmiast, więc zacząłem eksperymentować. Mimo że NIE uruchamia się to natychmiast, nie ulega awarii.

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();
              }       
        }
});

Nie będę udawać, że wiem, dlaczego nie ulega awarii, chociaż podejrzewam, że zmusza to do czekania, aż główny wątek poradzi sobie z tym w odpowiednim czasie. Wiem, że nie jest idealne wiązanie go z głównym wątkiem, ale ponieważ moje użycie wywołuje go w tle, nie martwię się, czy czeka, aż będzie w stanie zakończyć, a nie zawiesić.


zaczynasz swoją usługę od tła lub w aktywności?
Mateen Chaudhry

Tło. Problem polega na tym, że Android nie może zagwarantować, że usługa uruchomi się w wyznaczonym czasie. Można by pomyśleć, że rzucą błąd, aby go zignorować, jeśli chcesz, ale niestety nie Android. To musi być śmiertelne. Pomogło to w naszej aplikacji, ale nie naprawiło tego całkowicie.
a54studio

1

Dodaję kod w @humazed odpowiedzi. Więc nie ma wstępnego powiadomienia. To może być obejście, ale działa dla mnie.

@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);
        }
}

Po powiadomieniu dodaję transparentColor w małej ikonie i kolorze. To będzie działać.


0

Rozwiązałem problem z uruchamianiem usługi startService(intent)zamiast zamiast Context.startForeground()dzwonieniem startForegound()natychmiast po niej super.OnCreate(). Ponadto, jeśli uruchamiasz usługę podczas rozruchu, możesz uruchomić działanie, które uruchamia usługę w rozgłaszaniu rozruchu. Chociaż nie jest to trwałe rozwiązanie, działa.


w niektórych urządzeniach Xiaomi nie można rozpocząć aktywności w tle
Mateen Chaudhry

0

Aktualizowanie danych w onStartCommand(...)

onBind (...)

onBind(...)jest lepsza impreza cyklu zainicjować startForegroundVs. onCreate(...)ponieważ onBind(...)przechodzi w Intentktóre mogą zawierać ważne dane w Bundlepotrzebne do zainicjowania Service. Nie jest to jednak konieczne, ponieważ onStartCommand(...)jest wywoływane przy pierwszym Serviceutworzeniu lub wywoływane później.

onStartCommand (...)

startForegroundw onStartCommand(...)ważne jest, aby zaktualizować Serviceraz już zostało stworzone.

Kiedy ContextCompat.startForegroundService(...) jest wywoływany po Serviceutworzeniu onBind(...)i onCreate(...)nie jest wywoływany. W związku z tym zaktualizowane dane mogą być przekazywane onStartCommand(...)za pośrednictwem Intent Bundledo aktualizacji danych w Service.

Próba

Używam tego wzorca, aby zaimplementować PlayerNotificationManagerw aplikacji wiadomości kryptowaluty Coinverse .

Activity / 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

Usługa

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)
        }

    }

}

Próbnik

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)
}

Wynik

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.