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