Processer och trådar

Quickview

  • Varje program körs i en egen process och alla komponenter i programmet körs som standard i den processen
  • Alla långsamma, blockerande operationer i en aktivitet bör göras i en ny tråd, för att undvika att användargränssnittet blir långsammare

I detta dokument

  1. Processer
    1. Processens livscykel
  2. Trådar
    1. Arbetartrådar
    2. Tråd-säkra metoder
  3. Interprocesskommunikation

När en programkomponent startar och programmet inte har några andra komponenter igång, startar Android-systemet en ny Linuxprocess för programmet med en enda tråd för utförandet. Som standard körs alla komponenter i samma program i samma process och tråd (kallad huvudtråden). Om en programkomponent startar och det redan finns en process för programmet (eftersom det finns en annan komponent från programmet), startas komponenten i den processen och använder samma exekveringstråd. Du kan dock ordna så att olika komponenter i programmet körs i separata processer, och du kan skapa ytterligare trådar för alla processer.

Detta dokument diskuterar hur processer och trådar fungerar i en Android-applikation.

Processer

Som standard körs alla komponenter i samma applikation i samma process och de flesta applikationer bör inte ändra på detta. Men om du upptäcker att du behöver styra vilken process en viss komponent tillhör kan du göra det i manifestfilen.

Inteckningen i manifestet för varje typ av komponentelement – <activity>, <service>, <receiver> och <provider> – har stöd för ett android:process-attribut som kan ange en process i vilken komponenten ska köras. Du kan ställa in det här attributet så att varje komponent körs i sin egen process eller så att vissa komponenter delar en process medan andra inte gör det. Du kan också ställa in android:process så att komponenter i olika program körs i samma process, förutsatt att programmen har samma Linux-användar-id och är signerade med samma certifikat.

Elementet <application> har också stöd för ett android:process-attribut, för att ställa in ett standardvärde som gäller för alla komponenter.

Android kan bestämma sig för att stänga av en process vid en viss tidpunkt, när minnet är lågt och det behövs för andra processer som mer direkt betjänar användaren. Applikationskomponenter som körs i den process som dödas förstörs därför. En process startas på nytt för dessa komponenter när det återigen finns arbete för dem att utföra.

När Android-systemet bestämmer vilka processer som ska dödas väger systemet deras relativa betydelse för användaren. Det är till exempel lättare att stänga av en process med aktiviteter som inte längre är synliga på skärmen än en process med synliga aktiviteter. Beslutet om huruvida en process ska avslutas beror därför på tillståndet för de komponenter som körs i processen. De regler som används för att avgöra vilka processer som skall avslutas diskuteras nedan.

Processens livscykel

Androidsystemet försöker behålla en tillämpningsprocess så länge som möjligt, men måste till slut ta bort gamla processer för att återvinna minne för nya eller viktigare processer. För att avgöra vilka processer som ska behållas och vilka som ska dödas placerar systemet varje process i en ”betydelsehierarki” baserat på de komponenter som körs i processen och tillståndet för dessa komponenter. Processer med lägst betydelse elimineras först, sedan de med näst lägst betydelse och så vidare, om det behövs för att återvinna systemresurser.

Det finns fem nivåer i betydelsehierarkin. I följande lista presenteras de olika typerna av processer i betydelseordning (den första processen är viktigast och elimineras sist):

  1. Process i förgrunden

    En process som krävs för det som användaren gör för tillfället. En process anses vara i förgrunden om något av följande villkor är uppfyllda:

    • Den är värd för en Activity som användaren interagerar med (Activitys onResume() metod har anropats).
    • Den är värd för en Service som är bunden till aktiviteten som användaren interagerar med.
    • Den är värd för en Service som körs ”i förgrunden” – tjänsten har anropat startForeground().
    • Den är värd för en Service som utför en av sina livscykelåterkopplingar (onCreate(), onStart() eller onDestroy()).
    • Den är värd för en BroadcastReceiver som utför sin onReceive()-metod.

    I allmänhet finns det bara ett fåtal förgrundsprocesser vid varje given tidpunkt. De dödas endast som en sista utväg – om minnet är så lågt att de inte alla kan fortsätta att köras. I allmänhet har enheten vid den tidpunkten nått ett tillstånd där minnet pageras, så det krävs att vissa förgrundsprocesser dödas för att användargränssnittet ska vara responsivt.

  2. Synlig process

    En process som inte har några förgrundskomponenter, men som ändå kan påverka vad användaren ser på skärmen. En process anses vara synlig om något av följande villkor är uppfyllda:

    • Den är värd för en Activity som inte finns i förgrunden, men som ändå är synlig för användaren (dess onPause() metod har anropats). Detta kan till exempel inträffa om förgrundsaktiviteten startade en dialog, vilket gör att den föregående aktiviteten kan ses bakom den.
    • Den är värd för en Service som är bunden till en synlig (eller förgrunds)aktivitet.

    En synlig process anses vara extremt viktig och kommer inte att dödas om det inte krävs för att alla förgrundsprocesser ska kunna fortsätta att köras.

  3. Tjänsteprocess

    En process som kör en tjänst som har startats med metoden startService() och som inte tillhör någon av de två högre kategorierna. Även om tjänsteprocesser inte är direkt kopplade till något som användaren ser gör de i allmänhet saker som användaren bryr sig om (t.ex. spelar musik i bakgrunden eller laddar ner data på nätverket), så systemet låter dem fortsätta att köras om det inte finns tillräckligt med minne för att behålla dem tillsammans med alla förgrunds- och synliga processer.

  4. Bakgrundsprocess

    En process som håller en aktivitet som för närvarande inte är synlig för användaren (aktivitetensonStop() metod har anropats). Dessa processer har ingen direkt inverkan på användarupplevelsen, och systemet kan när som helst avbryta dem för att återta minnet för en tidigare grund-, synlig- eller serviceprocess. Vanligtvis är det många bakgrundsprocesser som körs, så de hålls i en LRU-lista (least recently used) för att se till att den process med den aktivitet som användaren senast såg är den sista som dödas. Om en verksamhet tillämpar sina livscykelmetoder på ett korrekt sätt och sparar sitt nuvarande tillstånd kommer det inte att ha någon synlig effekt på användarupplevelsen om processen dödas, eftersom verksamheten återställer hela sitt synliga tillstånd när användaren navigerar tillbaka till verksamheten. Se dokumentet Aktiviteter för information om hur man sparar och återställer tillstånd.

  5. Tom process

    En process som inte innehåller några aktiva programkomponenter. Den enda anledningen till att hålla den här typen av process vid liv är för caching, för att förbättra starttiden nästa gång en komponent behöver köras i den. Systemet dödar ofta dessa processer för att balansera de totala systemresurserna mellan processcaches och de underliggande kärncaches.

Android rangordnar en process på den högsta nivå det kan, baserat på vikten av de komponenter som för närvarande är aktiva i processen. Om en process till exempel är värd för en tjänst och en synlig aktivitet rankas processen som en synlig process, inte som en tjänsteprocess.

Det kan dessutom hända att en process rankas högre eftersom andra processer är beroende av den – en process som betjänar en annan process kan aldrig rankas lägre än den process som den betjänar. Om till exempel en innehållsleverantör i process A betjänar en klient i process B, eller om en tjänst i process A är bunden till en komponent i process B, anses process A alltid vara minst lika viktig som process B.

Om en process som kör en tjänst rankas högre än en process med bakgrundsaktiviteter, kan en aktivitet som inleder en långvarig operation göra klokt i att starta en tjänst för den operationen i stället för att bara skapa en arbetstråd – särskilt om operationen sannolikt kommer att överleva aktiviteten.Till exempel bör en aktivitet som laddar upp en bild till en webbplats starta en tjänst för att utföra uppladdningen, så att uppladdningen kan fortsätta i bakgrunden även om användaren lämnar aktiviteten.Genom att använda en tjänst garanteras att operationen kommer att ha minst ”serviceprocess”-prioritet, oavsett vad som händer med aktiviteten. Detta är samma anledning till att mottagare av sändningar böranvända tjänster i stället för att helt enkelt lägga tidskrävande operationer i en tråd.

Threads

När ett program startas skapar systemet en exekveringstråd för programmet,kallad ”main”. Denna tråd är mycket viktig eftersom den ansvarar för att skicka händelser till lämpliga widgetar i användargränssnittet, inklusive ritningshändelser. Det är också den tråd i vilken ditt program interagerar med komponenter från Android UI toolkit (komponenter från paketen android.widget och android.view). Huvudtråden kallas också ibland för UI-tråden.

Systemet skapar inte en separat tråd för varje instans av en komponent. Alla komponenter som körs i samma process instansieras i UI-tråden, och systemanrop till varje komponent skickas från den tråden. Metoder som svarar på systemåterkopplingar (t.ex. onKeyDown() för att rapportera användaråtgärder eller en livscykelåterkopplingsmetod) körs därför alltid i processens UI-tråd.

När användaren exempelvis rör vid en knapp på skärmen skickar appens UI-tråd beröringshändelsen till widgeten, som i sin tur ställer in sitt tryckta tillstånd och skickar en begäran om ogiltigförklaring till händelsekön. UI-tråden tar bort begäran från kön och meddelar widgeten att den ska rita om sig själv.

När din app utför intensivt arbete som svar på användarens interaktion kan den här modellen med en enda tråd ge dålig prestanda om du inte implementerar din applikation på rätt sätt. Om allting sker i UI-tråden kommer långa operationer, t.ex. nätverksåtkomst eller databasförfrågningar, att blockera hela UI-tråden. När tråden är blockerad kan inga händelser skickas, inklusive ritningshändelser. Ur användarens perspektiv ser programmet ut att hänga. Ännu värre är att om gränssnittstråden är blockerad i mer än några sekunder (för närvarande cirka 5 sekunder) får användaren se den ökända dialogrutan ”Application notresponding” (ANR). Användaren kan då välja att avsluta programmet och avinstallera det om han/hon är missnöjd.

Andoid UI toolkit är dessutom inte trådsäker. Du får alltså inte manipulera ditt användargränssnitt från en arbetstråd – du måste göra all manipulation av ditt användargränssnitt från UI-tråden. Det finns alltså helt enkelt två regler för Androids enkeltrådsmodell:

  1. Blockera inte UI-tråden
  2. Access inte till Android UI-verktygslådan utanför UI-tråden

Arbetartrådar

På grund av den enkeltrådsmodell som beskrivs ovan är det viktigt för responsiviteten hos din applikations UI att du inte blockerar UI-tråden. Om du har operationer att utföra som inte är omedelbara bör du se till att göra dem i separata trådar (”bakgrundstrådar” eller ”arbetstrådar”).

Till exempel finns nedan lite kod för en klicklyssnare som laddar ner en bild från en separat tråd och visar den i en 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();}

Detta verkar först fungera bra, eftersom det skapas en ny tråd för att hantera nätverksoperationen. Det bryter dock mot den andra regeln i den enkeltrådade modellen: få inte åtkomst tillAndroid UI toolkit utanför UI-tråden – detta exempel ändrar ImageView från arbetstråden i stället för UI-tråden. Detta kan resultera i odefinierat och oväntat beteende, vilket kan vara svårt och tidskrävande att spåra.

För att åtgärda detta problem erbjuder Android flera sätt att komma åt UI-tråden från andra trådar. Här är en lista över metoder som kan hjälpa till:

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

Du kan till exempel rätta till koden ovan genom att använda metoden 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();}

Nu är denna implementering trådsäker: nätverksoperationen utförs från en separat tråd medan ImageView manipuleras från UI-tråden.

När komplexiteten i operationen ökar kan dock denna typ av kod bli komplicerad och svår att underhålla. För att hantera mer komplexa interaktioner med en arbetstråd kan du överväga att använda en Handler i din arbetstråd för att behandla meddelanden som levereras från UI-tråden. Den kanske bästa lösningen är dock att utöka klassen AsyncTask,som förenklar utförandet av arbetstrådsuppgifter som behöver interagera med användargränssnittet.

Användning av AsyncTask

AsyncTask gör det möjligt för dig att utföra asynkront arbete på ditt användargränssnitt. Den utför de blockerande operationerna i en arbetstråd och publicerar sedan resultaten på gränssnittstråden, utan att du behöver hantera trådar och/eller handlers själv.

För att använda den måste du underklassa AsyncTask och implementera doInBackground() callback-metoden, som körs i en pool av bakgrundstrådar. För att uppdatera användargränssnittet bör du implementera onPostExecute(), som levererar resultatet från doInBackground() och körs i användargränssnittstråden, så att du säkert kan uppdatera ditt användargränssnitt. Du kan sedan köra uppgiften genom att anropa execute() från UI-tråden.

Du kan till exempel implementera det tidigare exemplet genom att använda AsyncTask på följande sätt:

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 är UI säkert och koden är enklare, eftersom den separerar arbetet i den del som ska utföras på en arbetstråd och den del som ska utföras på UI-tråden.

Du bör läsa AsyncTask-referensen för att få en fullständig förståelse för hur den här klassen ska användas, men här är en snabb översikt över hur den fungerar:

  • Du kan ange parametrarnas typ, förloppsvärdena och slutvärdet för uppgiften med hjälp av generics
  • Metoden doInBackground() exekveras automatiskt på en arbetstråd
  • onPreExecute(), onPostExecute(), och onProgressUpdate() anropas alla på UI-tråden
  • Värdet som returneras av doInBackground() skickas tillonPostExecute()
  • Du kan anropa publishProgress() när som helst i doInBackground() för att exekvera onProgressUpdate() på UI-tråden
  • Du kan avbryta uppgiften när som helst, från vilken tråd som helst

Varning: Ett annat problem som du kan stöta på när du använder en workerthread är oväntade omstarter av din aktivitet på grund av en ändring av körtidskonfigurationen (t.ex. när användaren ändrar skärmens orientering), vilket kan förstöra din workerthread. I källkoden för exempelprogrammet Shelves kan du se hur du kan behålla uppgiften under en av dessa omstarter och hur du korrekt avbryter uppgiften när aktiviteten förstörs.

Trådsäkra metoder

I vissa situationer kan metoderna som du implementerar bli anropade från mer än en tråd, och måste därför skrivas trådsäkra.

Detta gäller främst för metoder som kan anropas på distans – till exempel metoder i en bunden tjänst. När ett anrop på en metod som är implementerad i en IBinder kommer från samma process som IBinder körs i, exekveras metoden i anroparens tråd, men när anropet kommer från en annan process exekveras metoden i en tråd som väljs från en pool av trådar som systemet upprätthåller i samma process som IBinder (den exekveras inte i processens UI-tråd). Medan t.ex. en tjänstsonBind()-metod skulle anropas från UI-tråden i tjänstens process, skulle metoder som implementeras i det objekt som onBind() returnerar (t.ex. en underklass som implementerar RPC-metoder) anropas från trådar i poolen. Eftersom en tjänst kan ha mer än en klient kan mer än en pooltråd anropa samma IBinder-metod samtidigt. IBinder-metoder måste därför implementeras så att de är trådsäkra.

På samma sätt kan en innehållsleverantör ta emot dataförfrågningar som kommer från andra processer.Även om ContentResolver– och ContentProvider-klasserna döljer detaljerna om hur kommunikationen mellan processerna hanteras, anropas ContentProvider-metoder som svarar på dessa förfrågningar – metoderna query(), insert(), delete(), update() och getType() – från en pool av trådar i innehållsleverantörens process, inte från processens UItråd. Eftersom dessa metoder kan anropas från ett obegränsat antal trådar samtidigt måste även de implementeras för att vara trådsäkra.

Interprocesskommunikation

Android erbjuder en mekanism för interprocesskommunikation (IPC) med hjälp av fjärrproceduranrop (RPC), där en metod anropas av en aktivitet eller en annan programkomponent, men utförs på distans (i en annan process), och resultatet returneras till den som anropat metoden. Detta innebär att ett metodanrop och dess data måste delas upp till en nivå som operativsystemet kan förstå, att det överförs från den lokala processen och adressrymden till den avlägsna processen och adressrymden, och att anropet sedan sätts ihop och utförs på nytt där. Returvärden överförs då i motsatt riktning. Android tillhandahåller all kod för att utföra dessa IPC-transaktioner, så du kan fokusera på att definiera och implementera RPC-programmeringsgränssnittet.

För att utföra IPC måste ditt program binda till en tjänst med hjälp av bindService(). Mer information finns i handboken för utvecklare av tjänster.

Lämna en kommentar