Procesy i wątki

Quickview

  • Każda aplikacja działa w swoim własnym procesie i wszystkie komponenty aplikacji działają w tym procesie, domyślnie
  • Każda wolna, blokująca operacja w działaniu powinna być wykonywana w nowym wątku, aby uniknąć spowolnienia interfejsu użytkownika

W tym dokumencie

  1. Procesy
    1. Cykl życia procesu
  2. Wątki
    1. Wątki robocze
    2. Metody bezpieczne dla wątku
    3. Metody bezpieczne dla wątkubezpieczne metody
  3. Komunikacja międzyprocesowa

Kiedy komponent aplikacji uruchamia się i aplikacja nie ma żadnych innych uruchomionych komponentów, system Android uruchamia nowy proces linuksowy dla aplikacji z pojedynczym wątkiem wykonawczym. Domyślnie wszystkie komponenty tej samej aplikacji są uruchamiane w tym samym procesie i wątku (zwanym „głównym” wątkiem). Jeżeli komponent aplikacji zostanie uruchomiony, a istnieje już proces dla tej aplikacji (ponieważ istnieje inny komponent aplikacji), wówczas komponent zostanie uruchomiony w tym procesie i użyje tego samego wątku wykonawczego. Jednakże, można zorganizować dla różnych składników w aplikacji, aby uruchomić w oddzielnych procesach, i można utworzyć dodatkowe wątki dla każdego procesu.

Ten dokument omawia jak procesy i wątki działają w aplikacji Android.

Procesy

Domyślnie, wszystkie składniki tej samej aplikacji działają w tym samym procesie i większość aplikacji nie powinna tego 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 ten komponent powinien działać. Możesz ustawić ten atrybut tak, aby każdy komponent działał w swoim własnym procesie lub tak, aby niektóre komponenty współdzieliły proces, a inne nie. Można również ustawićandroid:process tak, że składniki różnych aplikacji uruchomić w sameproces-pod warunkiem, że aplikacje mają ten sam identyfikator użytkownika Linux i są podpisane z tych samych certyfikatów.

The <application> element obsługuje również atrybut android:process, aby ustawić wartość domyślną, która ma zastosowanie do wszystkich komponentów.

Android może zdecydować się na zamknięcie procesu w pewnym momencie, gdy pamięć jest mało i wymagane przez inne procesy, które są bardziej natychmiastowe obsługi użytkownika. Applicationcomponents działające w procesie, który jest zabity są konsekwentnie zniszczone. Proces jest uruchamiany ponownie dla tych komponentów, gdy jest znowu praca dla nich do zrobienia.

Podejmując decyzję, które procesy zabić, system Android waży ich względne znaczenie dla użytkownika. Na przykład, łatwiej wyłącza proces hostujący działania, które nie są już widoczne na ekranie, w porównaniu do procesu hostującego widoczne działania. Decyzja, czy zakończyć proces, zależy więc od stanu komponentów działających w tym procesie. Procedury używane do decydowania, które procesy należy zakończyć, są omówione poniżej.

Cykl życia procesów

System Android próbuje utrzymać proces aplikacji tak długo, jak to możliwe, ale w końcu musi usunąć stare procesy, aby zwolnić pamięć dla nowych lub ważniejszych procesów. Aby określić, które procesy zachować, a które zabić, system umieszcza każdy proces w „hierarchii ważności” opartej na komponentach działających w procesie i stanie tych komponentów. Procesy o najniższym znaczeniu są eliminowane w pierwszej kolejności, następnie te o kolejnym najniższym znaczeniu i tak dalej, w zależności od potrzeby odzyskania zasobów systemowych.

Istnieje pięć poziomów w hierarchii ważności. Poniższa lista przedstawia różne rodzaje procesów w kolejności ważności (pierwszy proces jest najważniejszy i jest zabijany jako ostatni):

  1. Proces pierwszoplanowy

    Proces, który jest wymagany do tego, co użytkownik aktualnie robi. Proces jest uważany za znajdujący się na pierwszym planie, jeśli którykolwiek z następujących warunków jest prawdziwy:

    • Jest gospodarzem procesu Activity, z którym użytkownik wchodzi w interakcję (wywołano metodę onResume() procesu Activity).
    • To hostuje Service, który jest związany z aktywnością, z którą użytkownik wchodzi w interakcję.
    • To hostuje Service, który działa „na pierwszym planie”- usługa wywołała startForeground().
    • Utrzymuje Service, który wykonuje jedno ze swoich lifeeclecallbacks (onCreate(), onStart() lub onDestroy()).
    • Utrzymuje BroadcastReceiver, który wykonuje swoją metodę onReceive().

    Zwykle w danym momencie istnieje tylko kilka procesów pierwszoplanowych. Są one zabijane tylko jako ostatnia deska ratunku – jeśli pamięć jest tak niska, że nie mogą one wszystkie kontynuować pracy. Na ogół w tym momencie urządzenie osiągnęło stan stronicowania pamięci, więc zabicie niektórych procesów pierwszoplanowych jest wymagane, aby interfejs użytkownika był responsywny.

  2. Proces widoczny

    Proces, który nie ma żadnych składników pierwszoplanowych, ale nadal może wpływać na to, co użytkownik widzi na ekranie. Proces jest uważany za widoczny, jeśli jeden z następujących warunków jest prawdziwy:

    • Jest gospodarzem procesu Activity, który nie jest na pierwszym planie, ale nadal jest widoczny dla użytkownika (jego metoda onPause() została wywołana). Może się tak zdarzyć, na przykład, jeśli aktywność na pierwszym planie uruchomiła okno dialogowe, które pozwala na zobaczenie poprzedniej aktywności za nim.
    • Otrzymuje Service, który jest związany z widoczną (lub pierwszoplanową) aktywnością.

    Widoczny proces jest uważany za bardzo ważny i nie zostanie zabity, chyba że jest to wymagane do utrzymania wszystkich procesów pierwszoplanowych.

  3. Proces usługowy

    Proces, który uruchamia usługę, która została uruchomiona metodą startService() i nie należy do żadnej z dwóch wyższych kategorii. Chociaż procesy usługowe nie są bezpośrednio związane z niczym, co widzi użytkownik, na ogół wykonują rzeczy, na których mu zależy (takie jak odtwarzanie muzyki w tle lub pobieranie danych w sieci), więc system utrzymuje je w działaniu, chyba że nie ma wystarczającej ilości pamięci, aby zachować je wraz ze wszystkimi procesami pierwszoplanowymi i widocznymi.

  4. Proces tła

    Proces przechowujący działanie, które nie jest obecnie widoczne dla użytkownika (metoda działaniaonStop() została wywołana). Procesy te nie mają bezpośredniego wpływu na doświadczenie użytkownika, a system może je zabić w dowolnym momencie, aby odzyskać pamięć dla wcześniejszego, widocznego lub usługowego procesu. Zazwyczaj działa wiele procesów tła, więc są one przechowywane na liście LRU (least recently used), aby zapewnić, że proces z aktywnością, która była ostatnio widziana przez użytkownika, zostanie zabity jako ostatni. Jeśli aktywność poprawnie zaimplementuje swoje metody cyklu życia i zachowa swój aktualny stan, zabicie procesu nie będzie miało widocznego wpływu na doświadczenie użytkownika, ponieważ gdy użytkownik przejdzie z powrotem do aktywności, aktywność przywróci cały swój widoczny stan. Zobacz dokument Activities, aby uzyskać informacje na temat zapisywania i przywracania stanu.

  5. Pusty proces

    Proces, który nie zawiera żadnych aktywnych komponentów aplikacji. Jedynym powodem utrzymywania tego rodzaju procesu przy życiu jest buforowanie, aby poprawić czas uruchamiania następnym razem, gdy komponent musi zostać w nim uruchomiony. System często zabija te procesy w celu zrównoważenia ogólnego systemuresources między cache procesów i podstawowych cache jądra.

Android rangi proces na najwyższym poziomie może, w oparciu o znaczeniecomponents obecnie aktywne w procesie. Na przykład, jeśli proces hostuje usługę i widoczną aktywność, proces jest klasyfikowany jako widoczny proces, a nie proces usług.

Ponadto, ranking procesu może być zwiększona, ponieważ inne procesy są zależne odit-proces, który służy inny proces nigdy nie może być niższa niż proces, który jest obsługiwany. Na przykład, jeśli dostawca treści w procesie A obsługuje klienta w procesie B, lub jeśli usługa w procesie A jest związana z komponentem w procesie B, proces A jest zawsze uważany za co najmniej tak samo ważny jak proces B.

Ponieważ proces uruchamiający usługę jest klasyfikowany wyżej niż proces z działaniami w tle, działanie, które inicjuje długo trwającą operację, może dobrze zrobić, aby rozpocząć usługę dla tej operacji, a nie po prostu utworzyć wątek robotniczy – szczególnie jeśli operacja prawdopodobnie przekroczy czas działania.Na przykład, aktywność, która przesyła zdjęcie na stronę internetową, powinna uruchomić usługę, aby wykonać przesyłanie, tak aby przesyłanie mogło być kontynuowane w tle, nawet jeśli użytkownik opuści aktywność. Użycie usługi gwarantuje, że operacja będzie miała przynajmniej priorytet „procesu usługi”, niezależnie od tego, co stanie się z aktywnością. Jest to ten sam powód, dla którego odbiorcy transmisji powinni stosować usługi, a nie po prostu umieszczać czasochłonne operacje w wątku.

Wątki

Gdy aplikacja jest uruchamiana, system tworzy dla niej wątek wykonania, zwany „main”. Ten wątek jest bardzo ważny, ponieważ jest odpowiedzialny za wysyłanie zdarzeń do odpowiednich widżetów interfejsu użytkownika, w tym zdarzeń rysowania. Jest to również wątek, w którym nasza aplikacja wchodzi w interakcję z komponentami z zestawu narzędzi Android UI (komponenty z pakietów android.widget i android.view). Jako taki, główny wątek jest również czasami nazywany wątkiem UI.

System nie tworzy oddzielnego wątku dla każdej instancji komponentu. Wszystkie komponenty, które działają w tym samym procesie, są inicjowane w wątku UI, a wywołania systemowe do każdego komponentu są wysyłane z tego wątku. W rezultacie metody, które odpowiadają na wywołania zwrotne systemu (takie jak onKeyDown() zgłaszanie działań użytkownika lub metody wywołania zwrotnego cyklu życia), zawsze są uruchamiane w wątku UI procesu.

Na przykład, gdy użytkownik dotknie przycisku na ekranie, wątek UI aplikacji wysyła zdarzenie dotykowe do widżetu, który z kolei ustawia swój stan naciśnięcia i wysyła żądanie unieważnienia do kolejki zdarzeń. Wątek UI dequeues żądanie i powiadamia widżet, że powinien przerysować siebie.

Gdy twoja aplikacja wykonuje intensywną pracę w odpowiedzi na interakcję z użytkownikiem, ten model pojedynczego wątku może dać słabą wydajność, chyba że zaimplementujesz swoją aplikację prawidłowo. W szczególności, jeśli wszystko dzieje się w wątku UI, wykonywanie długich operacji, takich jak dostęp do sieci lub zapytania do bazy danych, zablokuje cały UI. Gdy wątek jest zablokowany, nie mogą być wysyłane żadne zdarzenia, w tym zdarzenia rysowania. Z perspektywy użytkownika aplikacja wydaje się zawieszać. Co gorsza, jeśli wątek UI jest zablokowany na dłużej niż kilka sekund (obecnie około 5 sekund), użytkownik otrzymuje niesławne okno dialogowe „aplikacja nie odpowiada” (ANR). Użytkownik może wtedy zdecydować się na wyjście z twojej aplikacji i odinstalowanie jej, jeśli jest niezadowolony.

Dodatkowo, zestaw narzędzi UI Andoida nie jest bezpieczny dla wątków. Tak więc, nie wolno ci manipulować twoim UI z wątku robotniczego – musisz wykonać wszystkie manipulacje do twojego interfejsu użytkownika z wątku UI. Tak więc, są po prostu dwie zasady modelu pojedynczego wątku Androida:

  1. Nie blokuj wątku UI
  2. Nie uzyskuj dostępu do zestawu narzędzi Android UI spoza wątku UI

Wątki robotnicze

Z powodu modelu pojedynczego wątku opisanego powyżej, jest to istotne dla responsywności UI twojej aplikacji, że nie blokujesz wątku UI. Jeśli masz do wykonania operacje, które nie są natychmiastowe, powinieneś upewnić się, że wykonujesz je w oddzielnych wątkach (wątki „tła” lub „robotnicze”).

Na przykład, poniżej znajduje się kod słuchacza kliknięć, który pobiera obraz z oddzielnego wątku i wyświetla go w ImageView:

public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start();}

Na początku wydaje się to działać dobrze, ponieważ tworzy nowy wątek do obsługi operacji sieciowej. Jednakże, narusza to drugą zasadę modelu jednowątkowego: nie uzyskuj dostępu do zestawu narzędzi Androida UI spoza wątku UI – ta próbka modyfikuje ImageView z wątku robotniczego zamiast z wątku UI. Może to spowodować niezdefiniowane i nieoczekiwane zachowanie, które może być trudne i czasochłonne do wytropienia.

Aby rozwiązać ten problem, Android oferuje kilka sposobów dostępu do wątku UI z innych wątków. Oto lista metod, które mogą pomóc:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

Na przykład, możesz naprawić powyższy kod używając metody View.post(Runnable):

public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start();}

Teraz ta implementacja jest thread-safe: operacja sieciowa jest wykonywana z oddzielnego wątku, podczas gdy ImageView jest manipulowana z wątku UI.

Jednakże, gdy złożoność operacji rośnie, ten rodzaj kodu może stać się skomplikowany i trudny do utrzymania. Aby obsłużyć bardziej złożone interakcje z wątkiem robotniczym, możesz rozważyć użycie Handler w swoim wątku robotniczym, aby przetworzyć wiadomości dostarczone z wątku UI. Być może najlepszym rozwiązaniem jest jednak rozszerzenie klasy AsyncTask, która upraszcza wykonywanie zadań wątku robotniczego, które muszą wchodzić w interakcję z UI.

Użycie AsyncTask

AsyncTask pozwala na wykonywanie asynchronicznej pracy na interfejsie użytkownika. Wykonuje on operacje blokujące w wątku robotniczym, a następnie publikuje wyniki w wątku UI, nie wymagając od użytkownika samodzielnej obsługi wątków i/lub handlerów.

Aby go użyć, należy podklasować AsyncTask i zaimplementować metodę doInBackground() wywołania zwrotnego, która działa w puli wątków tła. Aby zaktualizować interfejs użytkownika, należy zaimplementować metodę onPostExecute(), która dostarcza wynik z doInBackground() i działa w wątku interfejsu użytkownika, dzięki czemu można bezpiecznie zaktualizować interfejs użytkownika. Możesz następnie uruchomić zadanie wywołując execute() z wątku UI.

Na przykład, możesz zaimplementować poprzedni przykład używając AsyncTask w ten sposób:

public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png");}private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); }}

Teraz UI jest bezpieczne, a kod jest prostszy, ponieważ rozdziela pracę na część, która powinna być wykonana w wątku robotniczym i część, która powinna być wykonana w wątku UI.

Powinieneś przeczytać referencję AsyncTask, aby w pełni zrozumieć, jak używać tej klasy, ale tutaj jest szybki przegląd tego, jak to działa:

  • Możesz określić typ parametrów, wartości postępu i wartość końcową zadania, używając generics
  • Metoda doInBackground() wykonuje się automatycznie w wątku robotniczym
  • onPreExecute(), onPostExecute(), i onProgressUpdate() są wywoływane na wątku UI
  • Wartość zwracana przez doInBackground() jest wysyłana doonPostExecute()
  • Możesz wywołać publishProgress() w dowolnym momencie w doInBackground(), aby wykonać onProgressUpdate() na wątku UI
  • Możesz anulować zadanie w dowolnym momencie, z dowolnego wątku

Uwaga: Innym problemem, który możesz napotkać podczas korzystania z workerthread, są nieoczekiwane restarty w twojej aktywności spowodowane zmianą konfiguracji runtime(np. gdy użytkownik zmienia orientację ekranu), co może zniszczyć twój worker thread. Aby zobaczyć, jak możesz utrzymać zadanie podczas jednego z tych restartów i jak prawidłowo anulować zadanie, gdy aktywność jest niszczona, zobacz kod źródłowy przykładowej aplikacji Shelves.

Metody bezpieczne dla wątków

W niektórych sytuacjach, metody, które implementujesz mogą być wywoływane z więcej niż jednego wątku, a zatem muszą być napisane tak, aby były bezpieczne dla wątków.

Dotyczy to głównie metod, które mogą być wywoływane zdalnie – takich jak metody w usługach związanych. 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. Jednak gdy 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, podczas gdy metodaonBind() usługi byłaby wywoływana z wątku UI procesu usługi, metody zaimplementowane w obiekcie, który onBind() zwraca (na przykład podklasa implementująca metody RPC) byłyby wywoływane z wątków puli. Ponieważ usługa może mieć więcej niż jednego klienta, więcej niż jeden wątek puli może zaangażować tę samą metodę IBinder w tym samym czasie. Metody IBinder muszą być zatem zaimplementowane tak, aby były bezpieczne dla wątków.

Podobnie dostawca treści może otrzymywać żądania danych, które pochodzą z innych procesów.Chociaż klasy ContentResolver i ContentProvider ukrywają szczegóły tego, jak zarządzana jest komunikacja międzyprocesowa, 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 zawartości, a nie z wątku UI dla tego procesu. Ponieważ te metody mogą być wywoływane z dowolnej liczby wątków w tym samym czasie, one również muszą być zaimplementowane tak, aby były bezpieczne dla wątków.

Komunikacja międzyprocesowa

Android oferuje mechanizm komunikacji międzyprocesowej (IPC) używając zdalnych wywołań procedur (RPC), w których metoda jest wywoływana przez aktywność lub inny komponent aplikacji, ale wykonywana zdalnie (w innym procesie), z każdym wynikiem zwracanym z powrotem do wywołującego. Wiąże się to z dekompozycją wywołania metody i jej danych do poziomu zrozumiałego dla systemu operacyjnego, przekazaniem ich z lokalnego procesu i przestrzeni adresowej do zdalnego procesu i przestrzeni adresowej, a następnie ponownym złożeniem i odtworzeniem wywołania w tym miejscu. Wartości powrotne są przesyłane w przeciwnym kierunku. Android dostarcza cały kod do wykonywania tych transakcji IPC, więc można się skupić na definiowaniu i implementacji interfejsu programowania RPC.

Aby wykonać IPC, Twoja aplikacja musi związać się z usługą, używając bindService(). Więcej informacji można znaleźć w podręczniku dla programistów Services.

.

Dodaj komentarz