Processen en threads

Quickview

  • Elke toepassing draait in zijn eigen proces en alle componenten van de toepassing draaien standaard in dat proces
  • Alle langzame, blokkerende bewerkingen in een activiteit moeten in een nieuwe thread worden uitgevoerd, om vertraging van de gebruikersinterface te voorkomen

In dit document

  1. Processen
    1. Proceslevenscyclus
  2. Threads
    1. Worker threads
    2. Thread-veilige methoden
  3. Interprocess Communication

Wanneer een applicatiecomponent start en de applicatie heeft geen andere componenten lopen, start het Android-systeem een nieuw Linux-proces voor de toepassing met een enkele thread van uitvoering. Standaard draaien alle componenten van dezelfde toepassing in hetzelfde proces en dezelfde thread (genaamd de “main” thread). Als een applicatiecomponent wordt gestart en er bestaat al een proces voor die applicatie (omdat er een andere component van de applicatie bestaat), dan wordt de component binnen dat proces gestart en gebruikt dezelfde thread van uitvoering. U kunt echter verschillende componenten in uw toepassing in afzonderlijke processen laten draaien en u kunt extra threads maken voor elk proces.

Dit document bespreekt hoe processen en threads werken in een Android-toepassing.

Processen

Zo draaien standaard alle componenten van dezelfde toepassing in hetzelfde proces en de meeste toepassingen zouden dit niet moeten veranderen. Als u echter wilt bepalen tot welk proces een bepaalde component behoort, kunt u dat in het manifest-bestand doen.

De manifest-entry voor elk type component-element-<activity>, <service>, <receiver>, en <provider>-ondersteunt een android:process-attribuut dat een proces kan specificeren waarin die component moet draaien. U kunt dit attribuut zo instellen dat elke component in zijn eigen proces draait of dat sommige componenten een proces delen en andere niet. U kunt android:process ook zo instellen dat componenten van verschillende applicaties in hetzelfde proces draaien, mits de applicaties dezelfde Linux-gebruikers-ID hebben en met dezelfde certificaten zijn ondertekend.

Het <application> element ondersteunt ook een android:process attribuut, om een standaardwaarde in te stellen die voor alle componenten geldt.

Android kan besluiten om een proces op een gegeven moment af te sluiten, als het geheugen bijna op is en andere processen die de gebruiker directer van dienst zijn, het nodig hebben. Applicatiecomponenten die draaien in het proces dat wordt gedood, worden daardoor vernietigd. Een proces wordt opnieuw gestart voor die componenten wanneer er weer werk voor hen te doen is.

Bij de beslissing welke processen te doden, weegt het Android systeem hun relatieve belang voor de gebruiker. Het sluit bijvoorbeeld gemakkelijker een proces af dat activiteiten host die niet langer zichtbaar zijn op het scherm, vergeleken met een proces dat zichtbare activiteiten host. De beslissing om een proces al dan niet af te sluiten, hangt dus af van de toestand van de componenten die in dat proces draaien. De regels die worden gebruikt om te beslissen welke processen moeten worden beëindigd, worden hieronder besproken.

Process lifecycle

Het Android systeem probeert een applicatieproces zo lang mogelijk in stand te houden, maar moet uiteindelijk oude processen verwijderen om geheugen vrij te maken voor nieuwe of belangrijkere processen. Om te bepalen welke processen te behouden en welke te doden, het systeem plaatst elk proces in een “belang hiërarchie” op basis van de componenten die in het proces en de toestand van die componenten. Processen met het laagste belang worden het eerst verwijderd, dan die met het volgende laagste belang, enzovoort, zo nodig om systeembronnen terug te winnen.

Er zijn vijf niveaus in de hiërarchie van belangrijkheid. De volgende lijst toont de verschillende soorten processen in volgorde van belangrijkheid (het eerste proces is het belangrijkst en wordt als laatste gekilled):

  1. Proces op de voorgrond

    Een proces dat nodig is voor wat de gebruiker op dat moment aan het doen is. Een proces wordt geacht op de voorgrond te treden als een van de volgende voorwaarden waar is:

    • Het bevat een Activity waarmee de gebruiker interactief is (de onResume() methode van de Activity is aangeroepen).
    • Host een Service die gebonden is aan de activiteit waarmee de gebruiker interageert.
    • Host een Service die “op de voorgrond “draait -deze dienst heeft startForeground() aangeroepen.
    • Host een Service die een van zijn lifecyclecallbacks uitvoert (onCreate(), onStart(), of onDestroy()).
    • Host een BroadcastReceiver die zijn onReceive() methode uitvoert.

    Over het algemeen bestaan er maar een paar voorgrondprocessen op een gegeven moment. Ze worden alleen als laatste redmiddel gedood, als het geheugen zo laag is dat ze niet allemaal kunnen blijven draaien. Over het algemeen heeft het apparaat op dat moment een geheugen paging toestand bereikt, dus het doden van enkele voorgrond processen is nodig om de gebruikersinterface responsief te houden.

  2. Zichtbaar proces

    Een proces dat geen voorgrond componenten heeft, maar toch invloed kan hebben op wat de gebruiker op het scherm ziet. Een proces wordt als zichtbaar beschouwd als een van de volgende voorwaarden waar is:

    • Het bevat een Activity die niet op de voorgrond staat, maar toch zichtbaar is voor de gebruiker (zijn onPause() methode is aangeroepen). Dit kan bijvoorbeeld gebeuren als de voorgrondactiviteit een dialoogvenster heeft gestart, waardoor de vorige activiteit erachter kan worden gezien.
    • Host een Service die is gebonden aan een zichtbare (of voorgrond)activiteit.

    Een zichtbaar proces wordt als uiterst belangrijk beschouwd en zal niet worden gedood, tenzij dit nodig is om alle voorgrondprocessen draaiende te houden.

  3. Service proces

    Een proces dat een service draait die is gestart met de startService() methode en niet in een van de twee hogere categorieën valt. Hoewel serviceprocessen niet direct zijn gekoppeld aan iets dat de gebruiker ziet, doen ze over het algemeen dingen die de gebruiker belangrijk vindt (zoals het afspelen van muziek op de achtergrond of het downloaden van gegevens op het netwerk), dus het systeem laat ze draaien, tenzij er niet genoeg geheugen is om ze vast te houden samen met alle voorgrond- en zichtbare processen.

  4. Achtergrondproces

    Een proces dat een activiteit vasthoudt die momenteel niet zichtbaar is voor de gebruiker (deonStop() methode van de activiteit is aangeroepen). Deze processen hebben geen directe invloed op de gebruikerservaring, en het systeem kan ze op elk moment doden om geheugen vrij te maken voor een voorgrond-, zichtbaar-, of serviceproces. Gewoonlijk draaien er veel achtergrondprocessen, dus worden ze in een LRU (least recently used) lijst gehouden om er zeker van te zijn dat het proces met de activiteit die het laatst door de gebruiker is gezien, als laatste wordt gedood. Als een activiteit zijn levenscyclus op de juiste manier implementeert, en zijn huidige status bewaart, zal het doden van zijn proces geen zichtbaar effect hebben op de gebruikerservaring, omdat wanneer de gebruiker terug navigeert naar de activiteit, de activiteit al zijn zichtbare status herstelt. Zie het Activitiesdocument voor informatie over het opslaan en herstellen van status.

  5. Leeg proces

    Een proces dat geen actieve applicatiecomponenten bevat. De enige reden om dit soort processen in leven te houden is voor caching doeleinden, om de opstarttijd te verbeteren als er de volgende keer een component in moet draaien. Het systeem doodt deze processen vaak om de totale systeembronnen in evenwicht te brengen tussen procescaches en de onderliggende kernelcaches.

Android rangschikt een proces op het hoogste niveau dat het kan, gebaseerd op het belang van de componenten die momenteel actief zijn in het proces. Als een proces bijvoorbeeld een dienst en een zichtbare activiteit host, wordt het proces gerangschikt als een zichtbaar proces, niet als een dienstproces.

Daarnaast kan de rangorde van een proces worden verhoogd omdat andere processen ervan afhankelijk zijn-een proces dat een ander proces bedient, kan nooit lager worden gerangschikt dan het proces dat het bedient. Als bijvoorbeeld een inhoudsaanbieder in proces A een klant in proces B bedient, of als een dienst in proces A gebonden is aan een component in proces B, dan wordt proces A altijd minstens zo belangrijk gevonden als proces B.

Omdat een proces dat een dienst draait hoger wordt gerangschikt dan een proces met achtergrondactiviteiten, zou een activiteit die een langlopende operatie start er goed aan kunnen doen om een dienst voor die operatie te starten, in plaats van simpelweg een worker thread aan te maken-vooral als de operatie waarschijnlijk langer zal duren dan de activiteit.Bijvoorbeeld, een activiteit die een foto upload naar een website zou een service moeten starten om de upload uit te voeren, zodat de upload door kan gaan op de achtergrond zelfs als de gebruiker de activiteit verlaat. Het gebruik van een service garandeert dat de operatie op zijn minst “service process” prioriteit heeft, ongeacht wat er met de activiteit gebeurt. Dit is dezelfde reden waarom broadcast ontvangers services zouden moeten gebruiken in plaats van tijdrovende operaties in een thread te stoppen.

Threads

Wanneer een applicatie wordt gestart, maakt het systeem een thread aan voor de uitvoering van de applicatie, genaamd “main.” Deze thread is zeer belangrijk omdat hij verantwoordelijk is voor het verzenden van events naar de juiste gebruikersinterface-widgets, inclusief tekengebeurtenissen. Het is ook de thread waarin uw applicatie interageert met componenten van de Android UI toolkit (componenten uit de android.widget en android.view pakketten). Als zodanig, de belangrijkste thread wordt ook wel de UI thread.

Het systeem maakt geen aparte thread voor elke instantie van een component. Alle componenten die in hetzelfde proces draaien, worden in de UI thread geïnstalleerd, en systeemaanroepen voor elke component worden vanuit die thread afgehandeld. Methoden die reageren op systeemcallbacks (zoals onKeyDown() voor het rapporteren van gebruikersacties of een lifecycle callback methode) worden altijd in de UI thread van het proces uitgevoerd.

Wanneer de gebruiker bijvoorbeeld een knop op het scherm aanraakt, verzendt de UI thread van uw app de touch event naar de widget, die op zijn beurt de ingedrukte status instelt en een invalidate request naar de event queue stuurt. De UI thread dequeues het verzoek en informeert de widget dat het zichzelf opnieuw moet tekenen.

Wanneer uw app intensief werk verricht in reactie op interactie van de gebruiker, kan dit single thread model slechte prestaties opleveren, tenzij u uw applicatie goed implementeert. Als alles in de UI thread gebeurt, zal het uitvoeren van lange bewerkingen, zoals netwerktoegang of database-query’s, de hele UI blokkeren. Wanneer de thread geblokkeerd is, kunnen er geen gebeurtenissen worden verzonden, inclusief tekengebeurtenissen. Vanuit het oogpunt van de gebruiker lijkt de applicatie te hangen. Erger nog, als de UI thread langer dan een paar seconden geblokkeerd is (momenteel ongeveer 5 seconden) krijgt de gebruiker het beruchte “application notresponding” (ANR) dialoogvenster te zien. De gebruiker kan dan besluiten om de applicatie te verlaten en te verwijderen als hij niet tevreden is.

Daar komt bij dat de Andoid UI toolkit niet thread-safe is. Dus, je mag je UI niet manipuleren vanuit een worker thread-je moet alle manipulatie van je gebruikersinterface vanuit de UIthread doen. Er zijn dus gewoon twee regels voor Android’s single thread model:

  1. Blokkeer de UI thread
  2. Toegang tot de Android UI toolkit niet van buiten de UI thread

Worker threads

Omwille van het single thread model dat hierboven is beschreven, is het van vitaal belang voor de responsiviteit van de UI van uw applicatie dat u de UI thread niet blokkeert. Als je bewerkingen moet uitvoeren die niet ogenblikkelijk zijn, moet je ervoor zorgen dat je ze in aparte threads (“background” of “worker” threads) uitvoert.

Voorbeeld hieronder is wat code voor een click listener die een afbeelding download uit een aparteethread en weergeeft in een 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();}

Op het eerste gezicht lijkt dit prima te werken, omdat het een nieuwe thread aanmaakt om de netwerkoperatie uit te voeren. Het schendt echter de tweede regel van het single-threaded model: heb geen toegang tot de Android UI toolkit van buiten de UI thread – dit voorbeeld wijzigt de ImageView vanuit de worker thread in plaats van de UI thread. Dit kan resulteren in ongedefinieerd en onverwacht gedrag, wat moeilijk en tijdrovend kan zijn om op te sporen.

Om dit probleem op te lossen, biedt Android verschillende manieren om toegang te krijgen tot de UI thread vanuit otherthreads. Hier is een lijst van methoden die kunnen helpen:

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

U kunt bijvoorbeeld de bovenstaande code repareren door de View.post(Runnable)-methode te gebruiken:

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

Nu is deze implementatie thread-safe: de netwerkoperatie wordt gedaan vanuit een aparte thread terwijl de ImageView wordt gemanipuleerd vanuit de UI thread.

Maar naarmate de complexiteit van de operatie toeneemt, kan dit soort code ingewikkeld en moeilijk te onderhouden worden. Om complexere interacties met een worker thread af te handelen, zou je kunnen overwegen om een Handler in je worker thread te gebruiken, om berichten te verwerken die vanuit de UIthread worden afgeleverd. Maar misschien is de beste oplossing wel om de AsyncTask klasse uit te breiden, die de uitvoering van worker thread taken die interactie moeten hebben met de UI vereenvoudigt.

Het gebruik van AsyncTask

AsyncTask stelt u in staat om asynchroon werk uit te voeren op uw gebruikersinterface. Het voert de blokkeringsbewerkingen uit in een worker thread en publiceert vervolgens de resultaten op de UI thread, zonder dat u zelf threads en/of handlers hoeft af te handelen.

Om het te gebruiken, moet u AsyncTask subclassificeren en de doInBackground() callback method implementeren, die in een pool van background threads wordt uitgevoerd. Om uw UI te updaten, moet u onPostExecute() implementeren, die het resultaat van doInBackground() aflevert en in de UI thread draait, zodat u veilig uw UI kunt updaten. U kunt dan de taak uitvoeren door execute() aan te roepen vanuit de UI thread.

U kunt bijvoorbeeld het vorige voorbeeld implementeren met AsyncTask op deze manier:

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

Nu is de UI veilig en de code is eenvoudiger, omdat het werk wordt gescheiden in het deel dat moet worden gedaan op een worker thread en het deel dat moet worden gedaan op de UI thread.

Je zou de AsyncTask referentie moeten lezen voor een volledig begrip van hoe je deze klasse moet gebruiken, maar hier is een kort overzicht van hoe het werkt:

  • U kunt het type van de parameters, de voortgangswaarden, en de eindwaarde van de taak specificeren, met behulp van generics
  • De methode doInBackground() wordt automatisch uitgevoerd op een worker thread
  • onPreExecute(), onPostExecute() en onProgressUpdate() worden allemaal aangeroepen op de UI thread
  • De door doInBackground() geretourneerde waarde wordt gestuurd naaronPostExecute()
  • U kunt publishProgress() op elk moment in doInBackground() aanroepen om onProgressUpdate() uit te voeren op de UI thread
  • U kunt de taak op elk moment annuleren, vanuit elke thread

Voorzichtigheid: Een ander probleem dat u kunt tegenkomen bij het gebruik van een workerthread is onverwachte herstarts in uw activiteit als gevolg van een runtime-configuratiewijziging (zoals wanneer de gebruiker de schermoriëntatie wijzigt), waardoor uw workerthread kan worden vernietigd. Zie de broncode voor de Shelves voorbeeld applicatie.

Thread-safe methods

In sommige situaties kunnen de methodes die je implementeert vanuit meer dan een thread aangeroepen worden, en daarom moeten ze thread-safe geschreven zijn.

Dit geldt vooral voor methoden die op afstand kunnen worden aangeroepen, zoals methoden in een gebonden service. Wanneer een aanroep van een methode die is geïmplementeerd in een IBinder afkomstig is uit hetzelfde proces waarin de IBinder wordt uitgevoerd, wordt de methode uitgevoerd in de thread van de aanroeper. Wanneer de aanroep echter afkomstig is uit een ander proces, wordt de methode uitgevoerd in een thread die wordt gekozen uit een pool van threads die het systeem onderhoudt in hetzelfde proces als de IBinder (hij wordt niet uitgevoerd in de UI thread van het proces). Bijvoorbeeld, terwijl een serviceonBind() methode zou worden aangeroepen vanuit de UI thread van het proces van de service, methoden die zijn geïmplementeerd in het object dat onBind() retourneert (bijvoorbeeld een subklasse die RPC methoden implementeert) zouden worden aangeroepen vanuit threads in de pool. Omdat een service meer dan één client kan hebben, kunnen meer dan één pool-thread dezelfde IBinder-methode op hetzelfde moment aanroepen. IBinder methoden moeten daarom worden geïmplementeerd om thread-safe te zijn.

Evenzo kan een inhoudsaanbieder gegevensverzoeken ontvangen die afkomstig zijn van andere processen.Hoewel de ContentResolver en ContentProvider klassen de details verbergen van hoe de interprocess communicatie wordt beheerd, ContentProvider methoden die op deze verzoeken reageren – de methoden query(), insert(), delete(), update(), en getType() – worden aangeroepen vanuit een pool van threads in het proces van de inhoudsaanbieder, niet de UIthread voor het proces. Omdat deze methoden door een willekeurig aantal threads tegelijk kunnen worden aangeroepen, moeten ze ook thread-safe worden geïmplementeerd.

Interprocess Communication

Android biedt een mechanisme voor interprocess communication (IPC) met behulp van remote procedure calls (RPCs), waarbij een methode wordt aangeroepen door een activiteit of een ander applicatie-onderdeel, maar op afstand wordt uitgevoerd (in een ander proces), met een eventueel resultaat teruggestuurd naar de aanroeper. Dit houdt in dat een methodeaanroep en de gegevens ervan worden ontleed tot een niveau dat het besturingssysteem kan begrijpen, dat de gegevens van het lokale proces en de lokale adresruimte worden overgebracht naar het proces en de adresruimte op afstand, en dat de aanroep daar dan opnieuw wordt samengesteld en uitgevoerd. Terugkerende waarden worden dan in omgekeerde richting doorgegeven. Android biedt alle code om deze IPC-transacties uit te voeren, zodat u zich kunt concentreren op het definiëren en implementeren van de RPC programmeerinterface.

Om IPC uit te voeren, moet uw applicatie zich binden aan een service, met behulp van bindService(). Voor meer informatie, zie de Services ontwikkelaars gids.

Plaats een reactie