Procesy a vlákna

Quickview

  • Každá aplikace běží ve vlastním procesu a všechny součásti aplikace běží ve výchozím nastavení v tomto procesu
  • Všechny pomalé, blokující operace v činnosti by se měly provádět v novém vlákně, aby nedocházelo ke zpomalování uživatelského rozhraní

V tomto dokumentu

  1. Procesy
    1. Životní cyklus procesu
  2. Vlákna
    1. Pracovní vlákna
    2. Vlákno-bezpečné metody
  3. Komunikace mezi procesy

Když se spustí komponenta aplikace a aplikace nemá spuštěné žádné jiné komponenty, systém Android spustí nový linuxový proces pro aplikaci s jedním vláknem provádění. Ve výchozím nastavení jsou všechny komponenty téže aplikace spuštěny ve stejném procesu a vlákně(nazývaném „hlavní“ vlákno). Pokud se spustí komponenta aplikace a pro tuto aplikaci již existuje proces (protože existuje jiná komponenta z aplikace), pak se komponenta spustí v rámci tohoto procesu a použije stejné vlákno provádění. Můžete však zařídit, aby různé komponenty v aplikaci běžely v samostatných procesech, a můžete vytvořit dalšívlákna pro libovolný proces.

Tento dokument pojednává o tom, jak fungují procesy a vlákna v aplikaci Android.

Procesy

Ve výchozím nastavení běží všechny komponenty stejné aplikace ve stejném procesu a většina aplikací by to neměla měnit. Pokud však zjistíte, že potřebujete řídit, do kterého procesu určitákomponenta patří, můžete tak učinit v souboru manifest.

Zápis manifestu pro každý typ prvku komponenty – <activity>, <service>, <receiver> a <provider> – podporuje atribut android:process, který může specifikovatproces, ve kterém má daná komponenta běžet. Tento atribut můžete nastavit tak, aby každá komponenta běžela ve vlastním procesu nebo aby některé komponenty sdílely proces, zatímco jiné nikoli. Můžete také nastavit atribut android:process tak, aby komponenty různých aplikací běžely ve stejnémprocesu – za předpokladu, že aplikace sdílejí stejné ID uživatele Linuxu a jsou podepsány stejnými certifikáty.

Prvek <application> také podporuje atribut android:process, kterým lze nastavit výchozí hodnotu platnou pro všechny komponenty.

Android se může rozhodnout proces v určitém okamžiku vypnout, když je paměti málo a vyžadují ji jiné procesy, které bezprostředněji slouží uživateli. Aplikačníkomponenty běžící v procesu, který je ukončen, jsou následně zničeny. Proces je pro tyto komponenty znovu spuštěn, když je pro ně opět práce.

Při rozhodování, které procesy zabít, systém Android zvažuje jejich relativní důležitost pro uživatele. Například snáze ukončí proces, v němž probíhají činnosti, které již nejsou na obrazovce viditelné, ve srovnání s procesem, v němž probíhají viditelné činnosti. Rozhodnutí, zda proces ukončit, tedy závisí na stavu komponent běžících v tomto procesu. Níže jsou popsány postupy, které se používají při rozhodování o ukončení procesů.

Životní cyklus procesu

Systém Android se snaží udržet proces aplikace co nejdéle, ale nakonec musí staré procesy odstranit, aby získal zpět paměť pro nové nebo důležitější procesy. Pro určení, které procesy zachovat a které ukončit, systém zařazuje každý proces do „hierarchie důležitosti“ na základěkomponent běžících v procesu a stavu těchto komponent. Procesy s nejnižší důležitostí jsou odstraněny jako první, pak ty s další nejnižší důležitostí a tak dále, jak je to nutné pro obnovení systémových zdrojů.

Hierarchie důležitosti má pět úrovní. Následující seznam uvádí různétypy procesů v pořadí podle důležitosti (první proces je nejdůležitější a je vyřazen jako poslední):

  1. Proces v popředí

    Proces, který je nutný pro to, co uživatel právě dělá. Proces je považován za proces v popředí, pokud platí některá z následujících podmínek:

    • Hostuje Activity, se kterým uživatel interaguje (byla vyvolána metoda Activity onResume()).
    • Hostí Service, který je vázán na aktivitu, s níž uživatelinteraguje.
    • Hostí Service, který běží „v popředí“ – služba zavolala startForeground().
    • Hostí Service, který vykonává jednu ze zpětných funkcí životního cyklu (onCreate(), onStart() nebo onDestroy()).
    • Hostí BroadcastReceiver, který vykonává svou metodu onReceive().

    Obvykle v daném okamžiku existuje pouze několik procesů v popředí. Jsou zabíjeny pouze jako poslední možnost – pokud je paměti tak málo, že nemohou všechny pokračovat v běhu. Obecně platí, že v tomto okamžiku se zařízení dostalo do stavu stránkování paměti, takže zabití některých procesů popředí jevyžadováno, aby uživatelské rozhraní zůstalo citlivé.

  2. Viditelný proces

    Proces, který nemá žádné komponenty popředí, ale přesto může ovlivnit to, co uživatel vidí na obrazovce. Proces je považován za viditelný, pokud platí některá z následujících podmínek:

    • Hostí Activity, který není v popředí, ale přesto je pro uživatele viditelný (byla zavolána jeho metoda onPause()). To může nastat například v případě, že aktivita v popředí spustila dialogové okno, které umožňuje, aby za ním byla viditelná předchozí aktivita.
    • Hostí Service, který je vázán na viditelnou (neboli aktivitu v popředí)

    Viditelný proces je považován za mimořádně důležitý a nebude zabit, pokud to není nutné pro zachování běhu všech procesů v popředí.

  3. Servisní proces

    Proces, který spouští službu, jež byla spuštěna metodou startService() a nespadá do žádné z obou vyšších kategorií. Přestože servisní procesy nejsou přímo spojeny s ničím, co uživatel vidí, obvykle dělají věci, na kterých uživateli záleží (například přehrávání hudby na pozadí nebo stahování dat na síti), takže je systém nechává běžet, pokud není dostatek paměti, aby je udržel spolu se všemi procesy v popředí a viditelnými procesy.

  4. Proces na pozadí

    Proces, v němž probíhá činnost, která není pro uživatele momentálně viditelná (byla zavolána metoda činnostionStop()). Tyto procesy nemají žádný přímývliv na práci uživatele a systém je může kdykoli zabít, aby získal zpět paměť pro proces na pozadí,viditelný nebo servisní proces. Obvykle je spuštěno mnoho procesů na pozadí, takže jsou vedeny v seznamu LRU (least recently used), aby bylo zajištěno, že proces s aktivitou, kterou uživatel viděl naposledy, bude zabit jako poslední. Pokud aktivita implementuje své metody životního cyklu správně a uloží svůj aktuální stav, nebude mít zabití jejího procesu viditelný vliv na uživatelské prostředí, protože když uživatel přejde zpět na aktivitu, aktivita obnoví veškerý svůj viditelný stav. Informace o ukládání a obnovování stavu naleznete v dokumentu Aktivity.

  5. Prázdný proces

    Proces, který neobsahuje žádné aktivní komponenty aplikace. Jediný důvod, proč udržovat tento druh procesu při životě, je pro účely ukládání do mezipaměti, aby se zlepšila doba spuštění, až v něm bude příště potřeba spustit nějakou komponentu. Systém tyto procesy často zabíjí, aby vyrovnal celkové systémovézdroje mezi mezipamětí procesů a mezipamětí základního jádra.

Android řadí proces na nejvyšší možnou úroveň na základě důležitosti komponent, které jsou v procesu aktuálně aktivní. Například pokud proces hostuje službu a viditelnouaktivitu, je proces zařazen jako viditelný proces, nikoliv jako proces služby.

Kromě toho může být pořadí procesu zvýšeno, protože jsou na něm závislé jiné procesy – proces, který obsluhuje jiný proces, nemůže být nikdy zařazen níže než proces, kterému slouží. Například pokud poskytovatel obsahu v procesu A obsluhuje klienta v procesu B nebo pokud je služba v procesu A vázána na komponentu v procesu B, je proces A vždy považován za přinejmenším stejně důležitý jako proces B.

Protože proces, v němž běží služba, je hodnocen výše než proces s aktivitami na pozadí, může být pro aktivitu, která iniciuje dlouhotrvající operaci, dobré spustit pro tuto operaci službu, spíše než jednoduše vytvořit pracovní vlákno – zejména pokud operace pravděpodobně přežije aktivitu.Například aktivita, která nahrává obrázek na webovou stránku, by měla spustit službu, která bude nahrávání provádět, aby nahrávání mohlo pokračovat na pozadí, i když uživatel aktivitu opustí.Použití služby zaručuje, že operace bude mít alespoň prioritu „servisního procesu“ bez ohledu na to, co se stane s aktivitou. To je stejný důvod, proč by přijímače vysílání mělypoužívat služby, a ne jednoduše vkládat časově náročné operace do vlákna.

Vlákna

Při spuštění aplikace systém vytvoří vlákno provádění aplikace,které se nazývá „main“. Toto vlákno je velmi důležité, protože má na starosti rozesílání událostí příslušným widgetům uživatelského rozhraní, včetně událostí kreslení. Je to také vlákno, ve kterém vaše aplikace komunikuje s komponentami ze sady nástrojů Android UI (komponenty z balíčků android.widget a android.view). Proto se hlavnímu vláknu někdy také říká vlákno uživatelského rozhraní.

Systém nevytváří samostatné vlákno pro každou instanci komponenty. Všechnykomponenty, které běží ve stejném procesu, jsou instancovány ve vlákně uživatelského rozhraní a systémová volání jednotlivých komponent jsou odesílána z tohoto vlákna. V důsledku toho metody, které reagují na systémovázpětná volání (například onKeyDown() hlášení uživatelských akcí nebo metoda zpětného volání životního cyklu), vždy běží ve vlákně UI procesu.

Například když se uživatel dotkne tlačítka na obrazovce, vlákno UI vaší aplikace odešle událostdotknutí widgetu, který následně nastaví svůj stisknutý stav a odešle požadavek na zneplatnění do fronty událostí. Vlákno uživatelského rozhraní požadavek zruší a oznámí widgetu, že se má překreslit.

Pokud vaše aplikace provádí intenzivní práci v reakci na interakci uživatele, může tento model jednoho vlákna přinést nízký výkon, pokud aplikaci neimplementujete správně. Konkrétně, pokud se vše odehrává ve vlákně uživatelského rozhraní, provádění dlouhých operací, jako je přístup k síti nebo dotazy na databázi, zablokuje celé uživatelské rozhraní. Pokud je vlákno zablokováno, nelze odesílat žádné události, včetně událostí vykreslování. Z pohledu uživatele se zdá, že aplikace visí. Ještě horší je, že pokud je vlákno uživatelského rozhraní zablokováno na dobu delší než několik sekund (v současnosti asi 5 sekund), zobrazí se uživateli nechvalně známé dialogové okno „aplikace neodpovídá“ (ANR). Uživatel se pak může rozhodnout ukončit vaši aplikaci a odinstalovat ji, pokud s ní není spokojen.

Sada nástrojů Andoid UI navíc není thread-safe. Nesmíte tedy manipulovat se svým uživatelským rozhraním z pracovního vlákna – veškerou manipulaci s uživatelským rozhraním musíte provádět z vlákna UI. Pro jednovláknový model systému Android tedy jednoduše platí dvě pravidla:

  1. Neblokujte vlákno uživatelského rozhraní
  2. Nepřistupujte k sadě nástrojů uživatelského rozhraní systému Android mimo vlákno uživatelského rozhraní

Dělnická vlákna

Vzhledem k výše popsanému jednovláknovému modelu je pro odezvu uživatelského rozhraní vašíaplikace zásadní, abyste vlákno uživatelského rozhraní neblokovali. Pokud máte provádět operace, které nejsou okamžité, měli byste se ujistit, že je provádíte v oddělených vláknech („background“ nebo „worker“ vlákna).

Níže je například uveden kód pro posluchač kliknutí, který stáhne obrázek z odděleného vlákna a zobrazí jej v 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 první pohled se zdá, že to funguje dobře, protože se vytvoří nové vlákno pro zpracování síťové operace. Porušuje však druhé pravidlo jednovláknového modelu: nepřistupujte k sadě nástrojůAndroid UI mimo vlákno UI – tato ukázka upravuje ImageView z pracovního vlákna místo z vlákna UI. To může vést k nedefinovanému a neočekávanému chování, jehož vysledování může být obtížné a časově náročné.

Pro odstranění tohoto problému nabízí Android několik způsobů, jak přistupovat k vláknu UI z jinýchvláken. Zde je seznam metod, které mohou pomoci:

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

Například výše uvedený kód můžete opravit pomocí 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();}

Tato implementace je nyní vláknově bezpečná: síťová operace se provádí ze samostatného vlákna, zatímco ImageView se manipuluje z vlákna uživatelského rozhraní.

S rostoucí složitostí operace však může být tento druh kódu komplikovaný a obtížně udržovatelný. Chcete-li zvládnout složitější interakce s pracovním vláknem, můžete zvážit použití Handler v pracovním vlákně, které bude zpracovávat zprávy doručené z vlákna uživatelského rozhraní. Možná nejlepším řešením je však rozšíření třídy AsyncTask,která zjednodušuje provádění úloh pracovního vlákna, které potřebují interakci s uživatelským rozhraním.

Použití třídy AsyncTask

AsyncTask umožňuje provádět asynchronní práci s uživatelským rozhraním. Provádí blokující operace v pracovním vlákně a poté publikuje výsledky ve vlákně uživatelského rozhraní, aniž byste museli sami zpracovávat vlákna a/nebo obslužné programy.

Chcete-li ji použít, musíte mít podtřídu AsyncTask a implementovat metodu zpětného volání doInBackground(), která běží ve fondu vláken na pozadí. Chcete-li aktualizovat uživatelské rozhraní, měli byste implementovat onPostExecute(), která doručí výsledek z doInBackground() a běží ve vlákně uživatelského rozhraní, takže můžete bezpečněaktualizovat uživatelské rozhraní. Úlohu pak můžete spustit voláním execute()z vlákna uživatelského rozhraní.

Příklad předchozí příklad můžete implementovat pomocí AsyncTask takto:

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); }}

Nyní je uživatelské rozhraní bezpečné a kód je jednodušší, protože rozděluje práci načást, která má být provedena ve vlákně worker, a část, která má být provedena ve vlákně uživatelského rozhraní.

Pro úplné pochopení toho, jak tuto třídu používat, byste si měli přečíst referenci AsyncTask, ale zde je stručný přehled toho, jak funguje:

  • Můžete určit typ parametrů, hodnoty průběhu a konečnou hodnotu úlohy pomocí generik
  • Metoda doInBackground() se provádí automatickyna pracovním vlákně
  • onPreExecute(), onPostExecute(), a onProgressUpdate() jsou všechny vyvolány na vlákně uživatelského rozhraní
  • Hodnota vrácená doInBackground() je odeslána na adresuonPostExecute()
  • Kdykoli můžete zavolat publishProgress() v doInBackground(), aby se provedla onProgressUpdate() na vlákně uživatelského rozhraní
  • Úlohu můžete kdykoli zrušit z kteréhokoli vlákna

Pozor: Dalším problémem, se kterým se můžete setkat při použití pracovního vlákna, je neočekávané restartování aktivity v důsledku změny konfigurace za běhu(například když uživatel změní orientaci obrazovky), což může zničit pracovní vlákno. Podívejte se na zdrojový kód ukázkové aplikace Shelves.

Metody bezpečné pro vlákna

V některých situacích mohou být implementované metody volány z více než jednoho vlákna, a proto musí být napsány tak, aby byly bezpečné pro vlákna.

Toto platí především pro metody, které lze volat vzdáleně – například metody ve vázané službě. Pokud volání metody implementované v IBinder pochází ze stejného procesu, ve kterém běží IBinder, je metoda provedena ve vlákně volajícího, pokud však volání pochází z jiného procesu, je metoda provedena ve vlákně vybraném z fondu vláken, který systém udržuje ve stejném procesu jako IBinder (není provedena ve vlákně uživatelského rozhraní procesu). Zatímco například metoda onBind() služby by byla volána z vlákna uživatelského rozhraní procesu služby, metody implementované v objektu, který onBind() vrací (například podtřída implementující metody RPC), by byly volány z vlákenv poolu. Protože služba může mít více než jednoho klienta, může stejnou metodu IBinder současně zapojit více vláken poolu. Metody IBinder proto musí být implementovány tak, aby byly bezpečné pro vlákna.

Podobně může poskytovatel obsahu přijímat požadavky na data, které pocházejí z jiných procesů.Ačkoli třídy ContentResolver a ContentProvider skrývají podrobnosti o tom, jak je řízena komunikace mezi procesy, metody ContentProvider, které na tyto požadavky odpovídají – metody query(), insert(), delete(), update() a getType() – jsou volány z fondu vláken v procesu poskytovatele obsahu, nikoli z UIvlákna daného procesu. Protože tyto metody mohou být volány z libovolného počtu vláken současně, musí být také implementovány tak, aby byly bezpečné pro vlákna.

Komunikace mezi procesy

Android nabízí mechanismus pro komunikaci mezi procesy (IPC) pomocí volání vzdálených procedur(RPC), kdy je metoda volána aktivitou nebo jinou komponentou aplikace, ale je prováděna vzdáleně (v jiném procesu) a případný výsledek je vrácen zpět volajícímu. To znamená rozložit volání metody a její data na úroveň, které operační systém rozumí, přenést je z místního procesu a adresního prostoru do vzdáleného procesu a adresního prostoru a tam volání znovu sestavit a provést. Návratové hodnoty jsou pak přenášeny opačným směrem. Systém Android poskytuje veškerý kód pro provádění těchto IPCtransakcí, takže se můžete soustředit na definování a implementaci programového rozhraní RPC.

Chcete-li provádět IPC, musí se vaše aplikace vázat na službu pomocí bindService(). Další informace naleznete v příručce pro vývojáře služeb.

Napsat komentář