Gdy uruchamia się komponent aplikacji, a aplikacja nie ma żadnych innych uruchomionych komponentów, system Android uruchamia nowy proces Linuksa dla aplikacji z jednym wątkiem wykonania. Domyślnie wszystkie komponenty tej samej aplikacji działają w tym samym procesie i wątku, zwanym wątkiem głównym.
Jeśli uruchamia się komponent aplikacji, a proces tej aplikacji już istnieje, ponieważ uruchomiony jest inny komponent tej aplikacji, komponent uruchamia się w tym procesie i używa tego samego wątku wykonania. Możesz jednak skonfigurować różne komponenty aplikacji tak, aby działały w osobnych procesach, i możesz tworzyć dodatkowe wątki dla dowolnego procesu.
W tym dokumencie opisujemy, jak działają procesy i wątki w aplikacji na Androida.
Procesy
Domyślnie wszystkie komponenty aplikacji działają w tym samym procesie i większość aplikacji tego nie zmienia. Jeśli jednak stwierdzisz, że musisz kontrolować, do którego procesu należy dany komponent, możesz to zrobić w pliku manifestu.
Wpis manifestu dla każdego typu elementu komponentu – <activity>, <service>, <receiver> i <provider> – obsługuje atrybut android:process, który może określać proces, w którym działa komponent. Możesz ustawić ten atrybut tak, aby każdy komponent działał w swoim procesie lub aby niektóre komponenty współdzieliły proces, a inne nie.
Możesz też ustawić android:process tak, aby komponenty różnych aplikacji działały w tym samym procesie, pod warunkiem że aplikacje mają ten sam identyfikator użytkownika Linuksa i są podpisane tymi samymi certyfikatami.
Element <application>
obsługuje też atrybut android:process, którego możesz użyć do ustawienia wartości
domyślnej stosowanej do wszystkich komponentów.
Android może w pewnym momencie zdecydować się na zamknięcie procesu, gdy zasoby są potrzebne innym procesom, które bezpośrednio obsługują użytkownika. W konsekwencji komponenty aplikacji działające w zamykanym procesie są niszczone. Gdy te komponenty mają coś do zrobienia, proces jest uruchamiany ponownie.
Decydując, które procesy zamknąć, system Android bierze pod uwagę ich względne znaczenie dla użytkownika. Na przykład łatwiej zamyka proces, w którym działają aktywności, które nie są już widoczne na ekranie, niż proces, w którym działają widoczne aktywności. Decyzja o zakończeniu procesu zależy więc od stanu komponentów działających w tym procesie.
Szczegóły cyklu życia procesu i jego związku ze stanami aplikacji znajdziesz w artykule Procesy i cykl życia aplikacji.
Wątki
Gdy aplikacja jest uruchamiana, system tworzy dla niej wątek wykonania, zwany wątkiem głównym. Ten wątek jest bardzo ważny, ponieważ odpowiada za wysyłanie zdarzeń do odpowiednich widżetów interfejsu użytkownika, w tym zdarzeń rysowania. Jest to też prawie zawsze wątek, w którym aplikacja wchodzi w interakcję z komponentami z pakietów android.widget i android.view zestawu narzędzi interfejsu Androida.
Z tego powodu wątek główny jest czasami nazywany wątkiem UI. W szczególnych okolicznościach wątek główny aplikacji może nie być jej wątkiem UI. Więcej informacji znajdziesz w artykule Adnotacje
wątków.
System nie tworzy osobnego wątku dla każdej instancji komponentu. Wszystkie komponenty działające w tym samym procesie są tworzone w wątku UI, a wywołania systemowe do każdego komponentu są wysyłane z tego wątku. W konsekwencji metody, które odpowiadają na wywołania zwrotne systemu, takie jak onKeyDown() do zgłaszania działań użytkownika lub metoda wywołania zwrotnego cyklu życia, zawsze działają w wątku UI procesu.
Jeśli na przykład użytkownik dotknie przycisku na ekranie, wątek interfejsu użytkownika aplikacji wysyła zdarzenie dotknięcia do widżetu, który z kolei ustawia stan wciśnięcia i wysyła żądanie unieważnienia do kolejki zdarzeń. Wątek UI usuwa żądanie z kolejki i powiadamia widżet, aby się przerysował.
Jeśli nie zaimplementujesz aplikacji prawidłowo, ten model jednowątkowy może powodować niską wydajność, gdy aplikacja wykonuje intensywne zadania w odpowiedzi na interakcję użytkownika. Wykonywanie długotrwałych operacji w wątku UI, takich jak dostęp do sieci lub zapytania do bazy danych, blokuje cały interfejs. Gdy wątek jest zablokowany, nie można wysyłać żadnych zdarzeń, w tym zdarzeń rysowania.
Z perspektywy użytkownika aplikacja przestaje odpowiadać. Co gorsza, jeśli wątek UI jest zablokowany przez kilka sekund, użytkownik zobaczy okno „Aplikacja nie odpowiada” (ANR). Użytkownik może wtedy zdecydować się na zamknięcie aplikacji lub nawet jej odinstalowanie.
Pamiętaj, że zestaw narzędzi interfejsu Androida nie jest bezpieczny dla wątków. Nie manipuluj więc interfejsem użytkownika z wątku instancji roboczej. Wszystkie manipulacje interfejsem użytkownika wykonuj w wątku interfejsu użytkownika. Model jednowątkowy Androida ma 2 reguły:
- Nie blokuj wątku UI.
- Nie uzyskuj dostępu do zestawu narzędzi interfejsu Androida spoza wątku UI.
Wątki instancji roboczej
Ze względu na ten model jednowątkowy ważne jest, aby nie blokować wątku UI, ponieważ ma to wpływ na szybkość reakcji interfejsu aplikacji. Jeśli masz do wykonania operacje, które nie są natychmiastowe, wykonaj je w osobnych wątkach działających w tle lub instancji roboczej. Pamiętaj, że nie możesz aktualizować interfejsu użytkownika z żadnego innego wątku niż wątek interfejsu użytkownika lub wątek główny.
Aby pomóc Ci w przestrzeganiu tych reguł, Android oferuje kilka sposobów uzyskiwania dostępu do wątku UI z innych wątków. Oto lista metod, które mogą Ci pomóc:
Poniższe przykłady pokazują, jak przenieść zadanie do wątku działającego w tle i zaktualizować wątek interfejsu użytkownika po zakończeniu zadania:
Kotlin
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
Ta implementacja jest bezpieczna dla wątków, ponieważ operacja w tle jest wykonywana w osobnym wątku, a ImageView jest zawsze manipulowany z wątku UI.
Jednak w miarę wzrostu złożoności operacji ten rodzaj kodu może się skomplikować i utrudnić jego utrzymanie. Aby obsługiwać bardziej złożone interakcje z wątkiem instancji roboczej, możesz użyć w nim Handler do przetwarzania wiadomości dostarczanych z wątku UI. Pełne wyjaśnienie, jak planować pracę w wątkach działających w tle i komunikować się z wątkiem interfejsu użytkownika, znajdziesz w artykule
Omówienie pracy w tle.
Metody bezpieczne dla wątków
W niektórych sytuacjach implementowane metody są wywoływane z więcej niż 1 wątku, dlatego muszą być napisane tak, aby były bezpieczne dla wątków.
Dotyczy to przede wszystkim metod, które można wywoływać zdalnie, np. metod w powiązanej usłudze. Gdy wywołanie metody zaimplementowanej w IBinder pochodzi z tego samego procesu, w którym działa
IBinder, metoda jest wykonywana w wątku wywołującego.
Jeśli jednak wywołanie pochodzi z innego procesu, metoda jest wykonywana w wątku wybranym z puli wątków, którą system utrzymuje w tym samym procesie co IBinder.
Nie jest wykonywana w wątku UI procesu.
Na przykład metoda
onBind() usługi jest wywoływana z wątku UI procesu
usługi, ale metody zaimplementowane w obiekcie, który zwraca onBind(), takie jak podklasa
implementująca metody zdalnego wywoływania procedur (RPC), są wywoływane z wątków
w puli. Ponieważ usługa może mieć więcej niż 1 klienta, więcej niż 1 wątek puli może jednocześnie wywoływać tę samą metodę IBinder, dlatego metody IBinder muszą być zaimplementowane tak, aby były bezpieczne dla wątków.
Podobnie dostawca treści może otrzymywać żądania danych pochodzące z innych procesów.
Klasy ContentResolver i ContentProvider
ukrywają szczegóły zarządzania komunikacją międzyprocesową (IPC),
ale metody ContentProvider, które odpowiadają na te żądania – metody
query(),
insert(),
delete(),
update(),
i getType() – są
wywoływane z puli wątków w procesie dostawcy treści, a nie z wątku interfejsu użytkownika
procesu. Ponieważ te metody mogą być wywoływane jednocześnie z dowolnej liczby wątków, muszą być zaimplementowane tak, aby były bezpieczne dla wątków.
Komunikacja między procesami (IPC)
Android oferuje mechanizm IPC za pomocą RPC, w którym metoda jest wywoływana przez aktywność lub inny komponent aplikacji, ale wykonywana zdalnie w innym procesie, a każdy wynik jest zwracany do wywołującego. Wymaga to rozłożenia wywołania metody i jej danych na poziom zrozumiały dla systemu operacyjnego, przesłania ich z procesu lokalnego i przestrzeni adresowej do procesu zdalnego i przestrzeni adresowej, a następnie ponownego złożenia i wykonania wywołania.
Wartości zwracane są następnie przesyłane w przeciwnym kierunku. Android udostępnia cały kod do wykonywania tych transakcji IPC, dzięki czemu możesz skupić się na definiowaniu i implementowaniu interfejsu programowania RPC.
Aby wykonać IPC, aplikacja musi powiązać się z usługą za pomocą bindService(). Więcej informacji znajdziesz w artykule Omówienie usług.