Właściwe korzystanie z wątków na Androidzie może poprawić wyniki aplikacji skuteczność reklam. Na tej stronie omawiamy kilka aspektów pracy z wątkami: praca z interfejsem lub głównym wątkiem; zależności między cyklem życia aplikacji priorytet wątku; oraz o metodach zapewnianych przez platformę ułatwiających zarządzanie wątkami i jego złożoność. W każdym z tych obszarów znajdziesz na tej stronie potencjalne pułapki oraz strategii ich unikania.
Wątek główny
Gdy użytkownik uruchamia Twoją aplikację, Android tworzy nowy system Linux wraz z wątkiem wykonania. W tym wątku głównym zwanym wątkiem UI, odpowiada za wszystko, co się dzieje na ekranie. Wiedza o tym, jak to działa, może pomóc w zaprojektowaniu aplikacji pod kątem korzystania w wątku głównym, aby uzyskać jak najlepszą wydajność.
Wewnętrzne
Główny wątek ma bardzo prosty projekt: jedynym zadaniem jest bloki pracy z kolejki roboczej zapewniającej bezpieczeństwo w wątkach do momentu zamknięcia aplikacji. platformy generują niektóre z tych bloków pracy z różnych miejsc. Te Miejsca obejmują wywołania zwrotne związane z informacjami o cyklu życia, zdarzenia użytkownika takie jak jako dane wejściowe lub zdarzenia pochodzące z innych aplikacji i procesów. Ponadto aplikacja może jawnego umieszczania bloków w kolejce, bez korzystania z platformy.
Prawie dowolne blok kodu wykonywany przez aplikację jest powiązany z wywołaniem zwrotnym zdarzenia, np. z danymi wejściowymi, inflację układu, czyli rysowanie. Gdy coś wywoła zdarzenie, wątek, w którym to zdarzenie wypchnięto zdarzenie z samego siebie i do wiadomości w wątku głównym kolejkę. Wątek główny może obsługiwać wydarzenie.
W trakcie animacji lub aktualizacji ekranu system próbuje wykonać blok pracy (który odpowiada za rysowanie ekranu) co około 16 ms, aby płynnie renderować się przy 60 klatek na sekundę. Aby system mógł osiągnąć ten cel, hierarchia UI/widoku musi zostać zaktualizowana w wątku głównym. Gdy jednak kolejka wiadomości w wątku głównym zawiera zadania, które są zbyt liczne lub zbyt długie, aby zmieścić się w wątku głównym aktualizacja dostatecznie szybko, aplikacja powinna przenieść tę pracę do instancji roboczej w wątku. Jeśli wątek główny nie może zakończyć wykonywania bloków pracy w ciągu 16 ms, użytkownik może zauważyć przerwy, opóźnienia lub brak responsywności interfejsu użytkownika. Jeśli wątek główny blokuje się przez około 5 sekund, system wyświetla Aplikacja Okno ANR (Nie odpowiada), które umożliwia użytkownikowi bezpośrednie zamknięcie aplikacji.
przenoszenie wielu lub długich zadań z wątku głównego, tak aby nie zakłócały one pracy; płynne renderowanie i szybkie reagowanie na dane wejściowe użytkownika. jest powód do wdrożenia w aplikacji podziału na wątki.
Odwołania do wątków i obiektów interfejsu
Z założenia Android Obiekty widoku nie są bezpieczne w wątkach. Aplikacja ma tworzyć, używać niszczenia obiektów UI, a wszystko to w wątku głównym. Jeśli spróbujesz zmienić a nawet odwołanie do obiektu UI w wątku innym niż główny, wynik mogą obejmować wyjątki, dyskretne awarie, awarie i inne nieokreślone błędy.
Problemy z plikami referencyjnymi dzielą się na 2 różne kategorie: bezpośrednie odniesienia i niejawne odwołania.
Bezpośrednie odwołania
Wiele zadań w wątkach innych niż główne ma na celu aktualizowanie obiektów UI. Jeśli jednak jeden z tych wątków uzyska dostęp do obiektu w hierarchii widoków, może spowodować niestabilność aplikacji: jeśli wątek instancji roboczej zmieni właściwości w tym samym czasie, w którym odwołuje się do niego każdy inny wątek, wyniki są niezdefiniowane.
Rozważmy na przykład aplikację, która zawiera bezpośrednie odniesienie do obiektu UI w
wątku instancji roboczej. Obiekt w wątku instancji roboczej może zawierać odniesienie do
View
; ale przed zakończeniem pracy View
jest
z hierarchii widoków. Jeśli te 2 działania zachodzą jednocześnie,
odwołanie zachowuje obiekt View
w pamięci i ustawia w nim właściwości.
Jednak użytkownik nigdy nie zobaczy,
ten obiekt, a aplikacja usunie go, gdy odwołanie do niego zniknie.
W innym przykładzie obiekty View
zawierają odwołania do działania.
ich właścicielem. Jeśli
że aktywność zostanie zniszczona, ale pozostanie blok z wątkiem,
odwołuje się do niej – bezpośrednio lub pośrednio – moduł czyszczenia pamięci nie będzie
aż do zakończenia wykonywania tego bloku pracy.
Ten scenariusz może powodować problem w sytuacjach, w których praca z wątkami może być
okres wyświetlania, gdy wystąpi pewne zdarzenie cyklu życia działania, takie jak obrót ekranu.
System nie byłby w stanie przeprowadzić procesu czyszczenia pamięci, dopóki nie będzie
które udało się ukończyć. W wyniku tego w argumencie Activity
mogą być 2 obiekty
pamięci, dopóki nie rozpocznie się proces czyszczenia.
W takich sytuacjach aplikacja nie powinna zawierać treści dla dorosłych odwołania do obiektów UI w zadaniach roboczych w wątkach. Unikanie takich odwołań pozwala uniknąć tego rodzaju wyciek pamięci, a jednocześnie unikał rywalizacji o wątki.
We wszystkich przypadkach aplikacja powinna aktualizować tylko obiekty interfejsu w wątku głównym. Ten Oznacza to, że należy stworzyć zasady negocjacji, które dopuszczają przekazać pracę z wątkiem głównym, którym zajmuje się najbardziej z aktualizacją rzeczywistego obiektu UI.
Odwołania ogólne
Powszechną usterkę w projekcie kodu związaną z obiektami z wątkami można zauważyć we fragmencie kodu poniżej:
Kotlin
class MainActivity : Activity() { // ... inner class MyAsyncTask : AsyncTask<Unit, Unit, String>() { override fun doInBackground(vararg params: Unit): String {...} override fun onPostExecute(result: String) {...} } }
Java
public class MainActivity extends Activity { // ... public class MyAsyncTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... params) {...} @Override protected void onPostExecute(String result) {...} } }
Błąd w tym fragmencie polega na tym, że w kodzie deklaruje obiekt threading
MyAsyncTask
jako niestatyczna klasa wewnętrzna pewnej aktywności (lub klasa wewnętrzna)
w Kotlin). Ta deklaracja tworzy niejawne odwołanie do zamykającego elementu Activity
instancji. W rezultacie obiekt zawiera odniesienie do działania do momentu
praca z wątkami zostaje zakończona, co powoduje opóźnienie w zniszczeniu wskazanego działania.
To z kolei wywiera większy nacisk na pamięć.
Bezpośrednim rozwiązaniem tego problemu jest zdefiniowanie przeciążonej klasy instancji jako klas statycznych lub w ich własnych plikach, co powoduje usunięcie odnośnik niejawny.
Innym rozwiązaniem jest anulowanie i czyszczenie zadań w tle w odpowiednim
Wywołanie zwrotne cyklu życia Activity
, np. onDestroy
. Takie podejście może
żmudne i podatne na błędy. Zasadniczo nie należy używać złożonej logiki nieobsługującej interfejsu użytkownika
bezpośrednio w aktywnościach. Oprócz tego interfejs AsyncTask
został wycofany i jest
nie jest zalecane do użycia w nowym kodzie. Zobacz Threading na Androidzie.
.
Cykle życia wątków i aktywności w aplikacjach
Cykl życia aplikacji może mieć wpływ na działanie wątków w aplikacji. Możesz zdecydować, czy wątek ma pozostawać czy nie powinien pozostawać po Musisz również zdawać sobie sprawę z relacji między określanie priorytetów wątków oraz określanie, czy aktywność jest uruchomiona na pierwszym planie, w tle.
Trwałe wątki
Wątki pozostają w życiu przez wszystkie działania, które je wywołały. Wątki są nadal wykonywane, nieprzerwanie niezależnie od tego, czy zostały utworzone czy zniszczone, choć zostaną one zakończone wraz z procedurą zgłoszenia, gdy nie będzie bardziej aktywne komponenty aplikacji. W niektórych przypadkach jest to korzystne.
Rozważmy przypadek, w którym aktywność powoduje powstawanie zestawu bloków roboczych z wątkami. jest następnie zniszczony, zanim wątek roboczy będzie mógł uruchomić bloki. Do czego służą co robimy z unoszącymi się w powietrzu blokami?
Jeśli blokady miałyby aktualizować interfejs, który już nie istnieje, nie ma powodu. aby kontynuował pracę. Jeśli na przykład ma to na celu wczytanie informacji o użytkownikach z bazy danych, a następnie aktualizować widoki, wątek nie jest już potrzebny.
Z kolei pakiety służbowe mogą przynosić pewne korzyści niezwiązane z
Interfejs. W takim przypadku należy utrwalić wątek. Pakiety mogą być na przykład
oczekiwanie na pobranie obrazu, zapisanie go w pamięci podręcznej i zaktualizowanie powiązanych
View
obiekt. Chociaż obiekt już nie istnieje, podczas pobierania
zapisanie obrazu w pamięci podręcznej może być pomocne, na wypadek gdyby użytkownik wrócił do
a niszczycielstwo.
Ręczne zarządzanie odpowiedziami cyklu życia wszystkich obiektów z wątkami może stać się
bardzo złożonym procesem. Jeśli nie będziesz nimi zarządzać prawidłowo, w przypadku aplikacji mogą wystąpić
rywalizacja o pamięć
i z wydajnością. Łączę
ViewModel
wraz z LiveData
umożliwia:
wczytuj dane i otrzymuj powiadomienia, gdy się zmienią
bez obaw o cykl życia.
ViewModel
obiektów jest
rozwiązania tego problemu. Modele widoków są utrzymywane po zmianach konfiguracji, które
zapewnia łatwy sposób na zachowanie danych widoku. Więcej informacji o modelach widoków znajdziesz w dokumentacji
Przewodnik po ViewModel i więcej informacji na jego temat
LiveData zapoznaj się z przewodnikiem LiveData. Jeśli
Użytkownik chciałby też dowiedzieć się więcej o architekturze aplikacji,
Przewodnik po architekturze aplikacji.
Priorytet wątku
Jak opisano w sekcji Procesy i Cykl życia aplikacji, czyli priorytet, jaki otrzymują wątki aplikacji. zależy częściowo od tego, na jakim etapie cyklu życia aplikacji znajduje się aplikacja. Podczas tworzenia do zarządzania wątkami w aplikacji, ważne jest, aby określić ich priorytety, że odpowiednie wątki uzyskują odpowiednie priorytety we właściwym czasie. Jeśli ustawisz zbyt wysoką wartość, może on przerwać wątek UI i RenderThread, przez co aplikacja i usuwać klatki. Jeśli ustawienie będzie za niskie, możesz utworzyć zadania asynchroniczne (np. graficzne ładowanie) wolniej, niż jest to konieczne.
Za każdym razem, gdy tworzysz wątek, musisz wywołać
setThreadPriority()
Wątek systemowy
algorytm szeregowania preferuje wątki o wysokim priorytecie, równoważąc te
i traktujemy priorytetowo, aby ostatecznie zakończyć całą pracę. Ogólnie rzecz biorąc,
na pierwszym planie
grupy otrzymują około 95% łącznego czasu wykonywania na urządzeniu,
grupa tła ma około 5%.
System przypisuje każdemu wątkowi własną wartość priorytetu, korzystając z metody
Process
zajęcia.
Domyślnie system ustawia priorytet wątku na ten sam priorytet i tę samą grupę
jako pierwszy wątek. Aplikacja może jednak wyraźnie
dostosuj priorytet wątku za pomocą
setThreadPriority()
Process
pomaga uprościć przypisywanie wartości priorytetów, zapewniając
zestaw stałych, których aplikacja może używać do określania priorytetów wątków. Przykład:
THREAD_PRIORITY_DEFAULT
reprezentuje domyślną wartość wątku. Aplikacja powinna ustawić priorytet wątku na
THREAD_PRIORITY_BACKGROUND
dla wątków wykonujących mniej pilne zadania.
Twoja aplikacja może używać tych uprawnień: THREAD_PRIORITY_LESS_FAVORABLE
i THREAD_PRIORITY_MORE_FAVORABLE
stałe jako przyrosty, aby ustawić względne priorytety. Lista:
priorytety wątków, patrz
THREAD_PRIORITY
stałych w
klasy Process
.
Więcej informacji na temat:
zarządzania wątkami, zapoznaj się z dokumentacją na temat
Thread
i Process
zajęć.
Klasy pomocnicze do podziału na wątki
W przypadku deweloperów, których językiem głównym jest Kotlin, zalecamy korzystanie z współrzędnych. Kogutyny mają wiele zalet, w tym możliwość pisania kodu asynchronicznego bez wywołań zwrotnych, a także uporządkowanej równoczesności do określania zakresu, anulowania i obsługi błędów.
Platforma udostępnia również te same klasy i elementy podstawowe w Javie, co ułatwia
wątki, takie jak Thread
, Runnable
i Executors
zajęć,
oraz dodatkowe, takie jak HandlerThread
.
Więcej informacji znajdziesz w artykule o Threading na Androidzie.
Klasa HandlerThread
Wątek modułu obsługi to długotrwały wątek, który pobiera zadania z kolejki i działa. .
Pomyśl o typowym problemie z uzyskaniem klatek podglądu z tagów
Camera
obiekt.
Gdy zarejestrujesz się do korzystania z klatek podglądu aparatu, otrzymasz je w
onPreviewFrame()
wywołanie zwrotne w wątku zdarzenia, z którego zostało wywołane. Jeśli
które było wywołanie zwrotne w wątku UI. Zadanie polega na obsłudze ogromnego
kolidowałyby z renderowaniem i przetwarzaniem zdarzeń.
W tym przykładzie, gdy aplikacja przekaże polecenie Camera.open()
do
blok pracy nad wątkiem modułu obsługi, powiązany blok
onPreviewFrame()
oddzwanianie
trafia do wątku modułu obsługi, a nie wątku UI. Jeśli więc planujesz długofalowo
nad pikselami, może to być lepsze rozwiązanie.
Gdy Twoja aplikacja utworzy wątek przy użyciu funkcji HandlerThread
, nie
zapomnij o ustawieniu
na podstawie typu wykonywanej pracy. Pamiętaj, że procesory mogą
obsługuje równoległą liczbę wątków. Pomaga określenie priorytetu
system zna właściwe sposoby planowania tej pracy, gdy wszystkie inne wątki
walczą o uwagę.
Klasa ThreadPoolExecutor
Istnieją pewne rodzaje pracy, które można ograniczyć do poziomu wysoce równoległego,
rozproszonych zadań. Do takich zadań należy na przykład obliczanie filtra dla każdego
Blok o wymiarach 8 x 8 obrazu w rozdzielczości 8 megapikseli. Ze względu na dużą liczbę pakietów służbowych
klasa HandlerThread
nie jest odpowiednią klasą.
ThreadPoolExecutor
to klasa pomocnicza,
ten proces. Ta klasa zarządza tworzeniem grupy wątków, zbiorów
ich priorytety i zarządzanie sposobem podziału pracy między te wątki.
W miarę zwiększania lub zmniejszania zbioru zadań klasa uruchamia lub niszczy więcej wątków
aby dostosować się do zadania.
Ta klasa pomaga również aplikacji uzyskać optymalną liczbę wątków. Kiedy to
tworzy ThreadPoolExecutor
obiektu, aplikacja ustawia minimalne i maksymalne wartości
liczby wątków. Ponieważ zbiór zadań przypisany do
ThreadPoolExecutor
rośnie,
klasa weźmie zainicjowaną minimalną i maksymalną liczbę wątków
konta i zastanów się, ile zostało do zrobienia. Na podstawie tych
ThreadPoolExecutor
określa, ile
i wątki powinny być aktywne w każdej chwili.
Ile wątków trzeba utworzyć?
Mimo że na poziomie oprogramowania kod może tworzyć setki wątków, co może powodować problemy z wydajnością. Aplikacja ma ograniczony udział CPU z usługami w tle, mechanizmem renderowania, mechanizmem audio i nie tylko. Procesory mają tak naprawdę możliwość równoległej obsługi niewielkiej liczby wątków; wszystko powyżej w kwestii priorytetu i harmonogramu. Dlatego ważne jest, aby tworzyć tylko tyle wątków, ile potrzebuje Twój zbiór zadań.
Zasadniczo jest za to odpowiedzialny szereg zmiennych, wyboru wartości (np. 4, czyli wartości inicjującej) i testowania jej Systrace to solidną strategię jak każda inna. Za pomocą metody prób i błędów możesz odkryć czyli minimalną liczbę wątków, których można użyć bez problemów.
Innym problemem przy podejmowaniu decyzji o liczbie wątków jest to, nie są bezpłatne: zajmują pamięć. Każdy wątek kosztuje co najmniej 64 tys. pamięci. Dane te szybko trafiają do wielu aplikacji zainstalowanych na urządzeniu, szczególnie w sytuacjach, w których stos wywołań znacznie się rozwija.
Wiele procesów systemowych i bibliotek zewnętrznych często tworzy własne pule wątków. Jeśli aplikacja może ponownie użyć istniejącej puli wątków, to ponowne wykorzystanie może pomóc wydajności przez zmniejszanie rywalizacji o zasoby pamięci i przetwarzania.