Usługa pierwszego planu zostaje zabita przez Androida

85

Aktualizacja : nie znalazłem prawdziwego rozwiązania problemu. Wymyśliłem metodę automatycznego ponownego łączenia się z poprzednim urządzeniem Bluetooth za każdym razem, gdy połączenie zostanie utracone. Nie jest idealny, ale wydaje się, że działa dość dobrze. Chciałbym jednak usłyszeć więcej sugestii dotyczących tego.

Mam taki sam problem jak w tym pytaniu: usługa jest zabijana podczas trzymania blokady uśpienia i po wywołaniu startForeground z urządzeniem (Asus Transformer), czas do zatrzymania usługi (30-45 min), użycie wake lock, użycie startForeground () oraz fakt, że problem nie występuje, jeśli aplikacja jest otwarta, gdy ekran zgaśnie.

Moja aplikacja utrzymuje połączenie Bluetooth z innym urządzeniem i przesyła dane między nimi, więc aby nasłuchiwać danych, musi być przez cały czas aktywna. Użytkownik może dowolnie uruchamiać i zatrzymywać usługę, i tak naprawdę jest to jedyny sposób, jaki wdrożyłem, aby uruchomić lub zatrzymać usługę. Po ponownym uruchomieniu usługi połączenie Bluetooth z innym urządzeniem zostanie utracone.

Zgodnie z odpowiedzią w powiązanym pytaniu metoda startForeground () „zmniejsza prawdopodobieństwo zabicia usługi, ale nie zapobiega temu”. Rozumiem, że tak jest, ale widziałem wiele przykładów innych aplikacji, które nie mają tego problemu (na przykład Tasker).

Użyteczność mojej aplikacji zostanie znacznie zmniejszona bez możliwości działania usługi do momentu zatrzymania przez użytkownika. Czy jest jakiś sposób, aby tego uniknąć ???

Widzę to w moim logcatie za każdym razem, gdy usługa jest zatrzymywana:

ActivityManager: No longer want com.howettl.textab (pid 32321): hidden #16
WindowManager: WIN DEATH: Window{40e2d968 com.howettl.textab/com.howettl.textab.TexTab paused=false
ActivityManager: Scheduling restart of crashed service com.howettl.textab/.TexTabService in 5000ms

EDYCJA: Powinienem również zauważyć, że nie wydaje się to występować na innym urządzeniu, do którego jestem podłączony: HTC Legend z systemem Cyanogen

EDYCJA: Oto wynik adb shell dumpsys activity services:

* ServiceRecord{40f632e8 com.howettl.textab/.TexTabService}

intent={cmp=com.howettl.textab/.TexTabService}

packageName=com.howettl.textab

processName=com.howettl.textab

baseDir=/data/app/com.howettl.textab-1.apk

resDir=/data/app/com.howettl.textab-1.apk

dataDir=/data/data/com.howettl.textab

app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

isForeground=true foregroundId=2 foregroundNoti=Notification(contentView=com.howettl.textab/0x1090087 vibrate=null,sound=null,defaults=0x0,flags=0x6a)

createTime=-25m42s123ms lastActivity=-25m42s27ms

 executingStart=-25m42s27ms restartTime=-25m42s124ms

startRequested=true stopIfKilled=false callStart=true lastStartId=1

Bindings:

* IntentBindRecord{40a02618}:

  intent={cmp=com.howettl.textab/.TexTabService}

  binder=android.os.BinderProxy@40a9ff70

  requested=true received=true hasBound=true doRebind=false

  * Client AppBindRecord{40a3b780 ProcessRecord{40bb0098 2995:com.howettl.textab/10104}}

    Per-process Connections:

      ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

All Connections:

  ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

A wynik adb shell dumpsys activity:

* TaskRecord{40f5c050 #23 A com.howettl.textab}

numActivities=1 rootWasReset=false

affinity=com.howettl.textab

intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab}

realActivity=com.howettl.textab/.TexTab

lastActiveTime=4877757 (inactive for 702s)

* Hist #1: ActivityRecord{40a776c8 com.howettl.textab/.TexTab}

    packageName=com.howettl.textab processName=com.howettl.textab

    launchedFromUid=2000 app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab }

    frontOfTask=true task=TaskRecord{40f5c050 #23 A com.howettl.textab}

    taskAffinity=com.howettl.textab

    realActivity=com.howettl.textab/.TexTab

    base=/data/app/com.howettl.textab-1.apk/data/app/com.howettl.textab-1.apk data=/data/data/com.howettl.textab

    labelRes=0x7f060000 icon=0x7f020000 theme=0x0

    stateNotNeeded=false componentSpecified=true isHomeActivity=false

    configuration={ scale=1.0 imsi=0/0 loc=en_CA touch=3 keys=2/1/1 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=6}

    launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=1644]

    state=STOPPED stopped=true delayedResume=false finishing=false

    keysPaused=false inHistory=true visible=false sleeping=true idle=true

    fullscreen=true noDisplay=false immersive=false launchMode=2

    frozenBeforeDestroy=false thumbnailNeeded=false

    connections=[ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}]

...

Proc #15: adj=prcp /F 40e75070 959:android.process.acore/10006 (provider)

          com.android.providers.contacts/.ContactsProvider2<=Proc{40bb0098 2995:com.howettl.textab/10104}

Proc #16: adj=bak+2/F 40bb0098 2995:com.howettl.textab/10104 (foreground-service)

Wydaje się, że usługa działa na pierwszym planie.

howettl
źródło
Spójrz na tę odpowiedź - może Ci się przydać stackoverflow.com/a/21157035/624109
Muzikant

Odpowiedzi:

218

OK, OK. Przeszedłem przez piekło i wróciłem do tego problemu. Oto jak postępować. Są błędy. W tym poście opisano, jak analizować błędy w implementacji i obejść problemy.

Podsumowując, oto jak to powinno działać. Działające usługi będą rutynowo oczyszczane i kończone co około 30 minut. Usługi, które chcą pozostać przy życiu dłużej niż to, muszą zadzwonić do Service.startForeground, który umieszcza powiadomienie na pasku powiadomień, aby użytkownicy wiedzieli, że usługa jest stale uruchomiona i potencjalnie wyczerpuje baterię. Tylko 3 procesy usługowe mogą wyznaczyć się jako usługi pierwszego planu w danym momencie. Jeśli istnieje więcej niż trzy usługi pierwszego planu, Android wyznaczy najstarszą usługę jako kandydata do oczyszczania i zakończenia.

Niestety w systemie Android są błędy związane z ustalaniem priorytetów usług pierwszego planu, które są wyzwalane przez różne kombinacje flag powiązań usług. Nawet jeśli poprawnie wyznaczyłeś swoją usługę jako usługę pierwszoplanową, Android może i tak zakończyć twoją usługę, jeśli jakiekolwiek połączenia z usługami w twoim procesie zostały kiedykolwiek wykonane z pewnymi kombinacjami wiążących flag. Szczegóły podano poniżej.

Należy zauważyć, że bardzo niewiele usług musi być usługami pierwszego planu. Ogólnie rzecz biorąc, musisz być usługą pierwszego planu tylko wtedy, gdy masz stale aktywne lub długotrwałe połączenie internetowe, które może być włączane i wyłączane lub anulowane przez użytkowników. Przykłady usług, które wymagają statusu pierwszego planu: serwery UPNP, długotrwałe pobieranie bardzo dużych plików, synchronizacja systemów plików przez Wi-Fi i odtwarzanie muzyki.

Jeśli tylko okazjonalnie odpytujesz lub czekasz na odbiorniki transmisji systemowych lub zdarzenia systemowe, lepiej byłoby obudzić usługę za pomocą timera lub w odpowiedzi na odbiorniki transmisji, a następnie pozwolić, aby usługa umarła po zakończeniu. To jest zgodne z projektem zachowanie usług. Jeśli po prostu musisz pozostać przy życiu, czytaj dalej.

Po zaznaczeniu pól dobrze znanych wymagań (np. Wywołanie Service.startForeground), następnym miejscem, w którym należy spojrzeć, są flagi używane w wywołaniach Context.bindService. Flagi używane do wiązania wpływają na priorytet procesu usługi docelowej na wiele nieoczekiwanych sposobów. W szczególności użycie niektórych flag powiązań może spowodować, że system Android nieprawidłowo obniży wersję usługi pierwszego planu do usługi zwykłej. Kod używany do przypisywania priorytetów procesom został dość mocno odrzucony. Warto zauważyć, że w API 14+ istnieją poprawki, które mogą powodować błędy podczas używania starszych flag powiązań; aw 4.2.1 są określone błędy.

Twoim przyjacielem w tym wszystkim jest narzędzie sysdump, którego można użyć, aby dowiedzieć się, jaki priorytet menedżer aktywności przypisał procesowi serwisowemu, i wykryć przypadki, w których przypisał nieprawidłowy priorytet. Skonfiguruj i uruchom usługę, a następnie wydaj następujące polecenie w wierszu polecenia na komputerze-hoście:

powłoka adb zrzuca procesy aktywności> tmp.txt

Użyj notatnika (nie WordPad / write), aby sprawdzić zawartość.

Najpierw sprawdź, czy pomyślnie udało Ci się uruchomić usługę w stanie pierwszego planu. Pierwsza sekcja pliku dumpsys zawiera opis właściwości ActivityManager dla każdego procesu. Poszukaj linii podobnej do poniższej, która odpowiada twojej aplikacji w pierwszej sekcji pliku dumpsys:

APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}

Sprawdź, czy foregroundServices = true w poniższej sekcji. Nie martw się o ukryte i puste ustawienia; opisują stan Czynności w procesie i nie wydają się być szczególnie istotne dla procesów z usługami w nich zawartymi. Jeśli foregroundService nie jest true, musisz wywołać Service.startForeground, aby to prawda.

Następną rzeczą, na którą musisz spojrzeć, jest sekcja pod koniec pliku zatytułowana "Lista procesów LRU (posortowana według oom_adj):". Wpisy na tej liście pozwalają określić, czy system Android faktycznie sklasyfikował Twoją aplikację jako usługę pierwszego planu. Jeśli twój proces znajduje się na dole tej listy, jest to główny kandydat do doraźnej eksterminacji. Jeśli twój proces znajduje się na szczycie listy, jest praktycznie niezniszczalny.

Spójrzmy na linię w tej tabeli:

  Proc #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

To jest przykład usługi pierwszoplanowej, która zrobiła wszystko dobrze. Kluczowym polem jest tutaj pole „adj =”. Wskazuje to na priorytet, jaki proces został przypisany przez usługę ActivityManagerService po tym, jak wszystko zostało zrobione. Chcesz, aby była to „adj = prcp” (widoczna usługa pierwszego planu); lub „adj = vis” (widoczny proces z działaniem) lub „fore” (proces z działaniem na pierwszym planie). Jeśli jest to „adj = svc” (proces usługi), „adj = svcb” (starsza usługa?) Lub „adj = bak” (pusty proces w tle), oznacza to, że proces jest prawdopodobnie kandydatem do zakończenia i zostanie zakończony nie rzadziej niż co 30 minut, nawet jeśli nie ma presji na odzyskanie pamięci. Pozostałe flagi w linii to głównie informacje diagnostyczne przeznaczone dla inżynierów Google. Decyzje o wypowiedzeniu są podejmowane na podstawie pól adj. Krótko mówiąc, / FS wskazuje usługę pierwszoplanową; / FA wskazuje proces pierwszoplanowy z aktywnością. / B oznacza usługę w tle. Etykieta na końcu wskazuje ogólną zasadę, zgodnie z którą procesowi przypisano priorytet. Zwykle powinno pasować do pola adj =; ale w niektórych przypadkach wartość adj = można korygować w górę lub w dół ze względu na flagi wiązania aktywnych powiązań z innymi usługami lub działaniami.

Jeśli potknąłeś się o błąd z wiążącymi flagami, linia dumpsys będzie wyglądać następująco:

  Proc #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Zwróć uwagę, że wartość pola adj jest niepoprawnie ustawiona na „adj = bak” (pusty proces w tle), co z grubsza oznacza „proszę, zakończ mnie teraz, abym mógł zakończyć to bezsensowne istnienie” dla celów oczyszczania procesów. Zwróć także uwagę na flagę (fg-service) na końcu wiersza, która wskazuje, że „reguły usług forground zostały użyte do określenia ustawienia„ adj ”. Pomimo faktu, że użyto reguł usługi fg, temu procesowi przypisano ustawienie adj „bak” i nie będzie żył długo. Mówiąc najprościej, jest to błąd.

Tak więc celem jest zapewnienie, że Twój proces zawsze otrzyma „adj = prcp” (lub lepszy). Metodą osiągnięcia tego celu jest modyfikowanie flag wiązania, dopóki nie uda się uniknąć błędów w przypisywaniu priorytetów.

Oto błędy, o których wiem. (1) Jeśli JAKIEKOLWIEK usługa lub działanie kiedykolwiek zostało powiązane z usługą za pomocą Context.BIND_ABOVE_CLIENT, istnieje ryzyko, że ustawienie adj = zostanie obniżone do „bak”, nawet jeśli to powiązanie nie jest już aktywne. Jest to szczególnie ważne, jeśli masz również powiązania między usługami. Wyraźny błąd w źródłach 4.2.1. (2) Zdecydowanie nigdy nie używaj BIND_ABOVE_CLIENT do powiązania między usługami. Nie używaj go również do połączeń typu aktywność-usługa. Flaga używana do zaimplementowania zachowania BIND_ABOVE_CLIENT wydaje się być ustawiana na podstawie procesu, a nie połączenia, więc wywołuje błędy związane z powiązaniami usługa-usługa, nawet jeśli nie ma aktywnego działania z usługą wiązanie z ustawioną flagą. Wydaje się również, że występują problemy z ustaleniem priorytetu, gdy w procesie jest wiele usług, z powiązaniami usługa-usługa. Użycie Context.BIND_WAIVE_PRIORITY (API 14) w powiązaniach usługa-usługa wydaje się pomocne. Context.BIND_IMPORTANT wydaje się być mniej lub bardziej dobrym pomysłem w przypadku wiązania z działania do usługi. Spowoduje to podniesienie priorytetu procesu o jeden stopień wyżej, gdy działanie jest na pierwszym planie, bez wyrządzania żadnej widocznej szkody, gdy działanie zostanie wstrzymane lub zakończone.

Ale ogólnie strategia polega na dostosowywaniu flag bindService do momentu, gdy sysdump wskaże, że twój proces otrzymał właściwy priorytet.

Do moich celów przy użyciu Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT dla powiązań działania z usługą i Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY dla powiązań usługa-usługa wydaje się działać właściwie. Twój przebieg może się różnić.

Moja aplikacja jest dość złożona: dwie usługi działające w tle, z których każda może niezależnie utrzymywać stany usług pierwszego planu, oraz trzecia, która może również przyjmować stan usługi pierwszego planu; dwie usługi są ze sobą powiązane warunkowo; trzecia zawsze wiąże się z pierwszą. Ponadto Activites działają w osobnym procesie (sprawia, że ​​animacja jest płynniejsza). Wydaje się, że uruchamianie działań i usług w tym samym procesie nie ma żadnego znaczenia.

Implementację zasad oczyszczania procesów (i kodu źródłowego używanego do generowania zawartości plików sysdump) można znaleźć w podstawowym pliku Androida

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.

Duża szansa.

PS: Oto interpretacja ciągów sysdump dla Androida 5.0. Nie pracowałem z nimi, więc zrób z nich, co chcesz. Uważam, że chcesz 4 to „A” lub „S”, a 5 to „IF” lub „IB”, a 1 to możliwie najniższy poziom (prawdopodobnie poniżej 3, ponieważ tylko 3 trzy procesy usługowe na pierwszym planie są aktywne w konfiguracji domyślnej).

Example:
   Proc # : prcp  F/S/IF trm: 0 31719: neirotech.cerebrum.attention:blePrcs/u0a77 (fg-service)

Format:
   Proc # {1}: {2}  {3}/{4}/{5} trm: {6} {7}: {8}/{9} ({10}

1: Order in list: lower is less likely to get trimmed.

2: Not sure.

3:
    B: Process.THREAD_GROUP_BG_NONINTERACTIVE
    F: Process.THREAD_GROUP_DEFAULT

4:
    A: Foreground Activity
    S: Foreground Service
    ' ': Other.

5:
    -1: procState = "N ";
        ActivityManager.PROCESS_STATE_PERSISTENT: procState = "P ";
    ActivityManager.PROCESS_STATE_PERSISTENT_UI:procState = "PU";
    ActivityManager.PROCESS_STATE_TOP: procState = "T ";
    ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF";
    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: procState = "IB";
    ActivityManager.PROCESS_STATE_BACKUP:procState = "BU";
    ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: procState = "HW";
    ActivityManager.PROCESS_STATE_SERVICE: procState = "S ";
    ActivityManager.PROCESS_STATE_RECEIVER: procState = "R ";
    ActivityManager.PROCESS_STATE_HOME: procState = "HO";
    ActivityManager.PROCESS_STATE_LAST_ACTIVITY: procState = "LA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: procState = "CA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "Ca";
    ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CE";

{6}: trimMemoryLevel

{8} Process ID.
{9} process name
{10} appUid 
Robin Davies
źródło
4
@Robin Davies, mam małe pytanie. Czy naprawdę muszę dzwonić, bindService()jeśli potrzebuję stale działającego serwisu? Czy nie wystarczy po prostu wezwać startForeground()serwis? Do komunikacji z serwerem I za pomocą EventBus.
ar-g
Wywołujesz Context.bindService z działania, aby usługa działała w pierwszej kolejności. Metoda Service.startService jest wywoływana przez kod w usłudze w celu przeniesienia uruchomionej usługi do stanu „pierwszego planu”. Zakładam, że biblioteka EventBus wywołuje w Twoim imieniu Context.bindService w pewnym momencie, aby uruchomić usługę. Jeśli istnieje inny sposób uruchomienia usługi, nie znam go.
Robin Davies
3
Wspaniały post! Głosowano za. Jeden fragment, który chciałem dodać do tego komentarza, który moim zdaniem jest istotny. Jeśli chcesz mieć stale działającą usługę, jak wspomniał Robin, musisz ją jakoś uruchomić. Możliwe jest wywołanie metody startService (usługa Intent) bezpośrednio w ramach działania zamiast metody bindService (), a następnie po uruchomieniu usługi można wywołać metodę startForeground (). Wołam to w onStartCommand () klasy usługi. O ile mi wiadomo, powinno to sprawić, że Twoja usługa nie będzie związana, ale powinna działać w oczekiwaniu na problemy z zasobami. Mam nadzieję, że to komuś pomoże.
Dave
Świetna robota!! Chciałbym dodać aktualizację do tego. Najpierw format wyjściowy adb nieznacznie się zmienił (styczeń 2016). Przetestowałem ten proces na dwóch urządzeniach LG Volt 4.4.2 i Nexus 5x 6.0.1, oba urządzenia nadal są dotknięte błędem. Mogę odtworzyć problem tylko za pomocą Context.BIND_ABOVE_CLIENT: Proc # 4: cch F / S / SF trm: 0 12354: com.test / u0a78 (fg-service) Użycie flagi troubled powoduje natychmiastowe zabicie przez większość czasu na starszym urządzenie po zakończeniu aktywności. Wszystkie inne flagi wydają się działać poprawnie w obu wersjach Androida.
user3259330
1
@Dave hej Dave, używam dokładnie tej metody, a także zwracam START_STICKY, ale moja usługa zawsze umiera po około godzinie, gdy urządzenie jest bezczynne. Czy masz jakieś pomysły, co może się
dziać
7

Jeśli mówi „już nie chcę ...”, oznacza to, że ten proces nie ma aktywnej usługi, która jest obecnie w stanie startForeground (). Sprawdź, czy Twoje wywołanie faktycznie się powiodło - czy widzisz opublikowane powiadomienie, w dzienniku nie ma żadnych komunikatów narzekających na cokolwiek itp. Użyj również usługi „adb shell dumpsys activity services”, aby spojrzeć na stan usługi i upewnij się, że jest ona faktycznie oznaczona jako pierwsza. Ponadto, jeśli jest poprawnie na pierwszym planie, w wyniku działania „adb shell dumpsys activity” zobaczysz w sekcji pokazującej OOM adj procesów, które z powodu tej usługi są obecnie na pierwszym planie.

hackbod
źródło
Dzięki za pomoc! Zredagowałem moje pytanie z danymi wyjściowymi wymienionych poleceń. Wydają się wskazywać, że usługa działa na pierwszym planie.
howettl
Czy jest sekcja kodu, który mógłbym opublikować, który może pomóc w diagnozie?
howettl
1
Zdecydowanie nie powinno się go zabijać będąc na pierwszym planie, a wiem na pewno, że rzeczy takie jak Muzyka w standardowej platformie nie są. Rozważ zgłoszenie błędu wraz z kodem, aby odtworzyć problem. Jedną rzeczą, na którą należy zwrócić uwagę, jest to, czy poruszasz się na pierwszym planie i z niego w dowolnym momencie, który może pozwolić na zabicie go.
hackbod
1
Czy jest możliwe, że aktualizacja trwającego powiadomienia przez wywołanie notyfikacji () zamiast ponownego wywoływania metody startForeground () mogłaby usunąć je ze statusu pierwszego planu? Mam również włączoną opcję FLAG_ALERT_ONLY_ONCE w powiadomieniu, jeśli to ma znaczenie.
howettl
2
Zdecydowanie nie aktualizuj go za pośrednictwem menedżera powiadomień. Publikujesz to za pośrednictwem usługi i powinieneś kontynuować aktualizację za pośrednictwem usługi.
hackbod