Omówienie procesów i wątków

Gdy komponent aplikacji się uruchamia, gdy nie są uruchomione żadne inne komponenty, system Android uruchamia nowy proces Linuxa dla aplikacji z pojedynczym wątkiem wykonania. Domyślnie wszystkie komponenty tej samej aplikacji działają w tym samym procesie i wątku, co jest nazywane wątkiem głównym.

Jeśli komponent aplikacji się uruchomi i już istnieje proces dla tej aplikacji, ponieważ inny komponent tej aplikacji jest już uruchomiony, komponent rozpocznie się w ramach tego procesu i używa tego samego wątku wykonania. Możesz jednak zadbać o to, aby poszczególne komponenty aplikacji działały w osobnych procesach. Możesz też tworzyć dodatkowe wątki dla dowolnego procesu.

W tym dokumencie opisujemy sposób działania procesów i wątków w aplikacji na Androida.

Procesy

Domyślnie wszystkie komponenty aplikacji działają w ramach tego samego procesu i większość aplikacji tego nie zmienia. Jeśli jednak potrzebujesz kontroli nad tym, do którego procesu należy dany komponent, możesz to zrobić w pliku manifestu.

Wpis w pliku 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 jest uruchamiany. Możesz ustawić ten atrybut tak, aby każdy komponent działał jako własny proces, lub tak, aby niektóre z nich współpracowały proces, a inne nie.

Możesz też skonfigurować 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 w systemie Linux i są podpisane tymi samymi certyfikatami.

Element <application> obsługuje też atrybut android:process, za pomocą którego można ustawić wartość domyślną dotyczącą wszystkich komponentów.

Android może w pewnym momencie zdecydować o zamknięciu procesu, gdy potrzebne będą zasoby przez inne procesy, które szybciej obsługują użytkownika. Komponenty aplikacji uruchomione w zamykanym procesie są w efekcie niszczone. Proces rozpoczyna się ponownie w przypadku tych komponentów, gdy jest dostępna praca w tym zakresie.

Wybierając procesy do wyłączenia, system Android bierze pod uwagę ich znaczenie dla użytkownika. Na przykład szybciej wyłącza proces hostujący działania, które nie są już widoczne na ekranie, niż proces hostujący te działania. Decyzja o zakończeniu procesu zależy więc od stanu uruchomionych w nim komponentów.

Szczegóły cyklu życia procesu i jego relacji ze stanami aplikacji znajdziesz w artykule Procesy i cykl życia aplikacji.

Wątki

Po uruchomieniu aplikacji system tworzy dla niej wątek nazywany 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 za ich rysowanie. Prawie zawsze jest to wątek, w którym aplikacja wchodzi w interakcję z komponentami z pakietów android.widget i android.view zestawu narzędzi interfejsu Android. Z tego powodu główny wątek jest czasami nazywany wątkiem UI. Jednak w pewnych okolicznościach główny wątek aplikacji może nie być wątkiem jej interfejsu. Więcej informacji znajdziesz w sekcji Adnotacje do wątków.

System nie tworzy osobnego wątku dla każdego wystąpienia komponentu. Wszystkie komponenty, które działają w ramach tego samego procesu, są tworzone w wątku UI, a wywołania systemowe każdego z nich są wysyłane z tego wątku. W efekcie metody, które reagują na wywołania zwrotne systemu – takie jak onKeyDown() w celu raportowania działań użytkownika lub metoda wywołania zwrotnego w cyklu życia – zawsze działają w wątku interfejsu procesu.

Na przykład gdy użytkownik dotknie przycisku na ekranie, wątek interfejsu aplikacji wysyła do widżetu zdarzenie kliknięcia, które z kolei ustawia stan naciśnięcia i wysyła unieważnione żądanie do kolejki zdarzeń. Wątek UI usuwa żądanie z kolejki i powiadamia widżet, aby sam się przetworzył.

Jeśli nie wdrożysz poprawnie aplikacji, ten model jednowątkowy może uzyskać niską wydajność, gdy aplikacja wykona intensywną pracę w odpowiedzi na interakcję użytkownika. Wykonywanie długich operacji w wątku interfejsu użytkownika, 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 wydaje się zawieszać. Co gorsza, jeśli wątek UI zostanie zablokowany na dłużej niż kilka sekund, użytkownik zobaczy okno „Aplikacja nie odpowiada” (ANR). Użytkownik może zamknąć aplikację lub ją odinstalować.

Pamiętaj, że zestaw narzędzi interfejsu Androida nie jest bezpieczny w wątkach. Nie manipuluj interfejsem użytkownika z wątku instancji roboczej. wszystkie operacje związane z interfejsem użytkownika wprowadzaj z poziomu wątku UI. W modelu z jednym wątkiem Androida istnieją 2 reguły:

  1. Nie blokuj wątku interfejsu.
  2. Nie uzyskaj dostępu do narzędzi interfejsu Androida spoza wątku UI.

Wątki instancji roboczych

Ze względu na ten model z jednym wątkiem ważne jest, aby responsywność interfejsu użytkownika aplikacji nie była blokowana. Jeśli masz operacje, które nie są natychmiastowe, pamiętaj, by je wykonywać w osobnych wątkach w tle lub instancji roboczych. Pamiętaj, że nie można aktualizować interfejsu z poziomu żadnego wątku innego niż wątek UI (główny).

Aby pomóc Ci w przestrzeganiu tych reguł, Android udostępnia kilka sposobów na dostęp do wątku UI z innych wątków. Oto lista metod, które mogą w tym pomóc:

W tym przykładzie użyto właściwości View.post(Runnable):

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // A potentially time consuming task.
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // A potentially time consuming task.
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Ta implementacja jest bezpieczna w wątku, ponieważ operacja w tle jest wykonywana w osobnym wątku, a ImageView zawsze jest wykonywana z wątku interfejsu.

Jednak wraz ze wzrostem złożoności operacji taki kod staje się skomplikowany i trudny w utrzymaniu. Aby obsługiwać bardziej złożone interakcje z wątkiem instancji roboczej, możesz rozważyć użycie w wątku roboczym wiadomości Handler do przetwarzania wiadomości dostarczanych z wątku interfejsu użytkownika. Pełne wyjaśnienie planowania pracy nad wątkami w tle i komunikowania się z wątkami interfejsu użytkownika znajdziesz w opisie działania w tle.

Metody bezpieczne dla wątków

W niektórych sytuacjach zaimplementowane metody są wywoływane z więcej niż 1 wątku, więc muszą być napisane w taki sposób, aby były bezpieczne w wątku.

Dotyczy to głównie metod, które można wywoływać zdalnie, takich jak metody w powiązanej usłudze. Gdy wywołanie metody zaimplementowanej w elemencie IBinder rozpoczyna się w tym samym procesie co IBinder, jest ona wykonywana w wątku elementu 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óre system utrzymuje w tym samym procesie co IBinder. Nie jest wykonywane w wątku interfejsu procesu.

Na przykład metoda onBind() usługi jest wywoływana z wątku interfejsu procesu usługi, ale metody wdrożone w obiekcie zwracanym przez onBind(), takie jak podklasa implementująca metody zdalnego wywołania procedury (RPC), są wywoływane z wątków w puli. Usługa może mieć więcej niż 1 klienta, więc więcej niż 1 wątek puli może w tym samym czasie korzystać z tej samej metody IBinder, dlatego tak, aby były bezpieczne w wątkach, trzeba zaimplementować metody IBinder.

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 reagujące 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 dla tego procesu. Te metody mogą być wywoływane z dowolnej liczby wątków jednocześnie, więc również muszą być wdrożone w taki sposób, aby były bezpieczne w wątkach.

Komunikacja międzyprocesowa

Android oferuje mechanizm obsługi IPC przy użyciu RPC, w którym metoda jest wywoływana przez działanie lub inny komponent aplikacji, ale wykonywana zdalnie w innym procesie, a każdy wynik jest zwracany do wywołania. Obejmuje to rozkład wywołania metody i jego danych na poziom zrozumiały dla systemu operacyjnego, przesyłanie ich z procesu lokalnego i przestrzeni adresowej do procesu zdalnego i przestrzeni adresowej, a następnie ponowne łączenie i rekonstrukcja wywołania.

Zwracane wartości są następnie przesyłane w przeciwnym kierunku. Android dostarcza cały kod do wykonywania tych transakcji IPC, możesz więc skupić się na zdefiniowaniu i wdrożeniu interfejsu programowania RPC.

Aby wykonać kod IPC, Twoja aplikacja musi powiązać się z usługą przy użyciu bindService(). Więcej informacji znajdziesz w artykule Omówienie usług.