Processer og tråde

Quickview

  • Alle programmer kører i deres egen proces, og alle komponenter i programmet kører som standard i denne proces
  • Alle langsomme, blokerende operationer i en aktivitet bør udføres i en ny tråd, for at undgå at gøre brugergrænsefladen langsommere

I dette dokument

  1. Processer
    1. Processers livscyklus
  2. Tråde
    1. Arbejdertråde
    2. Thread-sikre metoder
  3. Interproceskommunikation

Når en programkomponent starter, og programmet ikke har andre komponenter kørende, starter Android-systemet en ny Linux-proces for programmet med en enkelt udførelsestråd. Som standard kører alle komponenter i det samme program i den samme proces og tråd (kaldet “hovedtråden”). Hvis en programkomponent startes, og der allerede findes en proces for det pågældende program (fordi der findes en anden komponent fra programmet), startes komponenten i denne proces og bruger den samme eksekveringstråd. Du kan dog sørge for, at forskellige komponenter i dit program kører i separate processer, og du kan oprette ekstra tråde til enhver proces.

Dette dokument beskriver, hvordan processer og tråde fungerer i et Android-program.

Processer

Som standard kører alle komponenter i det samme program i den samme proces, og de fleste programmer bør ikke ændre dette. Hvis du imidlertid finder ud af, at du har brug for at styre, hvilken proces en bestemt komponent tilhører, kan du gøre det i manifestfilen.

Den manifestpost for hver type komponentelement – <activity>, <service>, <receiver> og <provider> – understøtter en android:process-attribut, der kan angive en proces, som den pågældende komponent skal køre i. Du kan angive denne attribut, så hver komponent kører i sin egen proces, eller så nogle komponenter deler en proces, mens andre ikke gør det. Du kan også indstille android:process, så komponenter i forskellige programmer kører i samme proces, forudsat at programmerne har samme Linux-bruger-id og er signeret med samme certifikater.

<application>-elementet understøtter også en android:process-attribut, så du kan indstille en standardværdi, der gælder for alle komponenter.

Android kan beslutte at lukke en proces på et tidspunkt, når hukommelsen er lav og kræves af andre processer, der mere umiddelbart betjener brugeren. Applikationskomponenter, der kører i den proces, der dræbes, bliver derfor ødelagt. Der startes en proces igen for disse komponenter, når der igen er arbejde til dem.

Når Android-systemet beslutter, hvilke processer der skal dræbes, afvejer det deres relative betydning for brugeren. Det er f.eks. lettere at lukke en proces med aktiviteter, der ikke længere er synlige på skærmen, end en proces med synlige aktiviteter. Beslutningen om, hvorvidt en proces skal afsluttes, afhænger derfor af tilstanden af de komponenter, der kører i den pågældende proces. De regler, der anvendes til at afgøre, hvilke processer der skal afsluttes, gennemgås nedenfor.

Livscyklus for processer

Android-systemet forsøger at opretholde en programproces så længe som muligt, men er til sidst nødt til at fjerne gamle processer for at genvinde hukommelse til nye eller vigtigere processer. For at afgøre, hvilke processer der skal bevares og hvilke der skal slettes, placerer systemet hver proces i et “betydningshierarki” baseret på de komponenter, der kører i processen, og disse komponenters tilstand. Processer med den laveste betydning elimineres først, derefter processer med den næstlaveste betydning og så videre, alt efter behov for at genvinde systemressourcer.

Der er fem niveauer i betydningshierarkiet. Følgende liste viser de forskellige typer processer i rækkefølge efter vigtighed (den første proces er vigtigst og slettes sidst):

  1. Forgrundsproces

    En proces, der er nødvendig for det, brugeren er i gang med. En proces anses for at være i forgrunden, hvis en af følgende betingelser er opfyldt:

    • Den er vært for en Activity, som brugeren interagerer med (Activity‘s onResume()-metode er blevet kaldt).
    • Den er vært for en Service, der er bundet til den aktivitet, som brugeren interagerer med.
    • Den er vært for en Service, der kører “i forgrunden”-tjenesten har kaldt startForeground().
    • Den er vært for en Service, der udfører en af sine lifecyclecallbacks (onCreate(), onStart() eller onDestroy()).
    • Den er vært for en BroadcastReceiver, der udfører sin onReceive()-metode.

    Generelt set findes der kun få forgrundsprocesser på et givet tidspunkt. De bliver kun dræbt som en sidste udvej – hvis hukommelsen er så lav, at de ikke alle kan fortsætte med at køre. Generelt har enheden på det tidspunkt nået en tilstand, hvor hukommelsen er paging, så det er nødvendigt at dræbe nogle forgrundsprocesser for at holde brugergrænsefladen lydhør.

  2. Synlig proces

    En proces, der ikke har nogen forgrundskomponenter, men som stadig kan påvirke det, brugeren ser på skærmen. En proces anses for at være synlig, hvis en af følgende betingelser er opfyldt:

    • Den er vært for en Activity, der ikke er i forgrunden, men som stadig er synlig for brugeren (dens onPause() metode er blevet kaldt). Dette kan f.eks. forekomme, hvis forgrundsaktiviteten startede en dialog, hvilket gør det muligt at se den forrige aktivitet bag den.
    • Den er vært for en Service, der er bundet til en synlig (eller forgrunds)aktivitet.

    En synlig proces anses for at være ekstremt vigtig og vil ikke blive dræbt, medmindre det er nødvendigt for at holde alle forgrundsprocesser kørende.

  3. Tjenesteproces

    En proces, der kører en tjeneste, som er startet med metoden startService(), og som ikke falder ind under nogen af de tohøjere kategorier. Selv om tjenesteprocesser ikke er direkte knyttet til noget, brugeren ser, udfører de generelt ting, som brugeren er interesseret i (f.eks. afspilning af musik i baggrunden eller downloading af data på netværket), så systemet holder dem kørende, medmindre der ikke er nok hukommelse til at beholde dem sammen med alle forgrunds- og synlige processer.

  4. Baggrundsproces

    En proces, der holder en aktivitet, som i øjeblikket ikke er synlig for brugeren (aktivitetensonStop() metode er blevet kaldt). Disse processer har ingen direkte indflydelse på brugeroplevelsen, og systemet kan til enhver tid dræbe dem for at genvinde hukommelse til en førgrunds-, synlig- eller serviceproces. Normalt kører der mange baggrundsprocesser, så de holdes på en LRU-liste (least recently used) for at sikre, at den proces med den aktivitet, der senest er blevet set af brugeren, er den sidste, der bliver dræbt. Hvis en aktivitet implementerer sine livscyklusmetoder korrekt og gemmer sin aktuelle tilstand, vil det ikke have nogen synlig virkning på brugeroplevelsen at dræbe processen, for når brugeren navigerer tilbage til aktiviteten, genetablerer aktiviteten hele sin synlige tilstand. Se dokumentet Aktivitetsdokumentet for at få oplysninger om lagring og genoprettelse af tilstand.

  5. Tom proces

    En proces, der ikke indeholder nogen aktive programkomponenter. Den eneste grund til at holde denne type proces i live er for at kunne cachelagre den for at forbedre opstartstiden, næste gang en komponent skal køre i den. Systemet dræber ofte disse processer for at afbalancere de samlede systemressourcer mellem procescaches og de underliggende kernecaches.

Android rangerer en proces på det højeste niveau, det kan, baseret på vigtigheden af de komponenter, der i øjeblikket er aktive i processen. Hvis en proces f.eks. er vært for en tjeneste og en synlig aktivitet, rangeres processen som en synlig proces og ikke som en tjenesteproces.

Dertil kommer, at en process’ rangering kan øges, fordi andre processer er afhængige af den – en proces, der betjener en anden proces, kan aldrig rangeres lavere end den proces, den betjener. Hvis f.eks. en indholdsudbyder i proces A betjener en klient i proces B, eller hvis en tjeneste i proces A er bundet til en komponent i proces B, betragtes proces A altid som mindst lige så vigtig som proces B.

Da en proces, der kører en tjeneste, er højere rangeret end en proces med baggrundsaktiviteter, kan en aktivitet, der iværksætter en langvarig operation, med fordel starte en tjeneste for denne operation i stedet for blot at oprette en arbejdstråd – især hvis operationen sandsynligvis vil vare længere end aktiviteten.En aktivitet, der f.eks. uploader et billede til et websted, bør starte en tjeneste til at udføre uploadet, så uploadet kan fortsætte i baggrunden, selv om brugeren forlader aktiviteten.Ved at bruge en tjeneste garanteres det, at operationen som minimum vil have prioritet som “tjenesteproces”, uanset hvad der sker med aktiviteten. Dette er den samme grund til, at broadcast-modtagere børanvende tjenester i stedet for blot at lægge tidskrævende operationer i en tråd.

Threads

Når et program startes, opretter systemet en udførelsestråd for programmet,kaldet “main”. Denne tråd er meget vigtig, fordi den er ansvarlig for at sende hændelser til de relevante widgets i brugergrænsefladen, herunder tegningshændelser. Det er også den tråd, hvor dit program interagerer med komponenter fra Android UI-værktøjssættet (komponenter fra pakkerne android.widget og android.view). Som sådan kaldes hovedtråden også nogle gange for UI-tråden.

Systemet opretter ikke en separat tråd for hver instans af en komponent. Alle komponenter, der kører i den samme proces, instantieres i UI-tråden, og systemkald til hver enkelt komponent sendes fra denne tråd. Derfor kører metoder, der reagerer på systemcallbacks (f.eks. onKeyDown() for at rapportere brugerhandlinger eller en livscyklus-callbackmetode) altid i processens UI-tråd.

For eksempel, når brugeren rører ved en knap på skærmen, sender din app’s UI-tråd den berøringshændelse til widgeten, som igen indstiller sin trykkede tilstand og sender en anmodning om invalidering til hændelseskøen. UI-tråden fjerner anmodningen fra køen og meddeler widget’en, at den skal tegne sig selv igen.

Når din app udfører intensivt arbejde som reaktion på brugerinteraktion, kan denne enkelttrådsmodel give dårlig ydeevne, medmindre du implementerer din applikation korrekt. Hvis alting sker i brugergrænsefladetråden, vil lange operationer, f.eks. netværksadgang eller databaseforespørgsler, blokere hele brugergrænsefladen. Når tråden er blokeret, kan der ikke sendes nogen hændelser, herunder tegningshændelser. Fra brugerens synspunkt ser det ud til, at programmet hænger. Endnu værre er det, at hvis brugergrænsefladetråden er blokeret i mere end et par sekunder (i øjeblikket ca. 5 sekunder), får brugeren den berygtede dialogboks “Application Notresponding” (ANR). Brugeren kan derefter beslutte at afslutte programmet og afinstallere det, hvis han/hun er utilfreds.

Dertil kommer, at Andoid UI-værktøjssættet ikke er trådsikkert. Så du må ikke manipulere din brugergrænseflade fra en arbejdstråd – du skal foretage al manipulation af din brugergrænseflade fra UI-tråden. Der er således blot to regler for Androids enkelttrådsmodel:

  1. Blokér ikke UI-tråden
  2. Acces til Android UI-værktøjssættet fra uden for UI-tråden

Worker-tråde

På grund af den ovenfor beskrevne enkelttrådsmodel er det afgørende for responsiviteten i din applikations UI-grænseflade, at du ikke blokerer UI-tråden. Hvis du skal udføre operationer, der ikke er øjeblikkelige, skal du sørge for at udføre dem i separate tråde (“baggrunds-” eller “worker”-tråde).

Fors eksempel er der nedenfor noget kode til en kliklytter, der downloader et billede fra en separat tråd og viser det 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();}

I første omgang ser det ud til at fungere fint, fordi der oprettes en ny tråd til at håndtere netværksoperationen. Det overtræder imidlertid den anden regel i den enkelttrådede model: Du må ikke få adgang tilAndroid UI-værktøjssættet uden for UI-tråden – dette eksempel ændrer ImageView fra arbejdstråden i stedet for UI-tråden. Dette kan resultere i udefineret og uventet adfærd, som kan være vanskelig og tidskrævende at opspore.

For at løse dette problem tilbyder Android flere måder at få adgang til UI-tråden fra andre tråde. Her er en liste over metoder, der kan hjælpe:

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

For eksempel kan du rette ovenstående kode ved at bruge View.post(Runnable)-metoden:

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 er denne implementering trådsikker: netværksoperationen udføres fra en separat tråd, mens ImageView manipuleres fra brugergrænsefladetråden.

Men efterhånden som kompleksiteten af operationen vokser, kan denne type kode blive kompliceret og vanskelig at vedligeholde. Hvis du vil håndtere mere komplekse interaktioner med en arbejdstråd, kan du overveje at bruge en Handler i din arbejdstråd til at behandle meddelelser, der leveres fra UI-tråden. Måske er den bedste løsning dog at udvide AsyncTask-klassen,som forenkler udførelsen af opgaver i en workertråd, der skal interagere med brugergrænsefladen.

Med AsyncTask

AsyncTask kan du udføre asynkront arbejde på din brugergrænseflade. Den udfører de blokerende operationer i en arbejdstråd og offentliggør derefter resultaterne på brugergrænsefladetråden, uden at du selv skal håndtere tråde og/eller handlere.

For at bruge den skal du underklassere AsyncTask og implementere doInBackground() callback-metoden, som kører i en pulje af baggrundstråde. Hvis du vil opdatere din brugergrænseflade, skal du implementere onPostExecute(), som leverer resultatet fra doInBackground() og kører i brugergrænsefladetråden, så du kan opdatere din brugergrænseflade uden fare. Du kan derefter køre opgaven ved at kalde execute() fra UI-tråden.

Du kan f.eks. implementere det foregående eksempel ved hjælp af AsyncTask på denne måde:

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 er UI’en sikker, og koden er enklere, fordi den adskiller arbejdet i den del, der skal udføres på en workertråd, og den del, der skal udføres på UI-tråden.

Du bør læse AsyncTask-referencen for at få en fuldstændig forståelse af, hvordan denne klasse skal bruges, men her er et hurtigt overblik over, hvordan den fungerer:

  • Du kan angive typen af parametrene, fremskridtsværdierne og den endelige værdi af opgaven ved hjælp af generics
  • Metoden doInBackground() udføres automatisk på en arbejdstråd
  • onPreExecute(), onPostExecute(), og onProgressUpdate() påkaldes alle på brugergrænsefladetråden
  • Værdien, der returneres af doInBackground(), sendes tilonPostExecute()
  • Du kan til enhver tid kalde publishProgress() i doInBackground() for at udføre onProgressUpdate() på brugergrænsefladetråden
  • Du kan annullere opgaven når som helst, fra en hvilken som helst tråd

Varsel: Et andet problem, du kan støde på, når du bruger en workerthread, er uventede genstarter i din aktivitet på grund af en ændring af køretidskonfigurationen (f.eks. når brugeren ændrer skærmorientering), hvilket kan ødelægge din workerthread. Du kan se, hvordan du kan bevare din opgave under en af disse genstarter, og hvordan du korrekt annullerer opgaven, når aktiviteten ødelægges, i kildekoden til eksempelprogrammet Shelves.

Thread-safe metoder

I nogle situationer kan de metoder, du implementerer, blive kaldt fra mere end én tråd, og derfor skal de skrives trådsikkert.

Dette gælder primært for metoder, der kan kaldes eksternt – f.eks. metoder i en bunden tjeneste. Når et kald på en metode, der er implementeret i en IBinder, stammer fra den samme proces, som IBinder kører i, udføres metoden i opkalderens tråd, men når kaldet stammer fra en anden proces, udføres metoden i en tråd, der vælges fra en pulje af tråde, som systemet vedligeholder i den samme proces som IBinder (den udføres ikke i processens brugergrænsefladetråd). Mens f.eks. en tjenestesonBind()-metode ville blive kaldt fra UI-tråden i tjenestens proces, ville metoder, der er implementeret i det objekt, som onBind() returnerer (f.eks. en underklasse, der implementerer RPC-metoder), blive kaldt fra tråde i puljen. Da en tjeneste kan have mere end én klient, kan mere end én puljetråd aktivere den samme IBinder-metode på samme tid. IBinder-metoder skal derfor implementeres, så de er trådsikre.

På samme måde kan en indholdsudbyder modtage dataanmodninger, der stammer fra andre processer.Selv om ContentResolver– og ContentProvider-klasserne skjuler detaljerne om, hvordan kommunikationen mellem processerne håndteres, kaldes ContentProvider-metoderne, der besvarer disse anmodninger – metoderne query(), insert(), delete(), update() og getType() – fra en pulje af tråde i indholdsudbyderens proces, ikke UIthread for processen. Da disse metoder kan blive kaldt fra et vilkårligt antal tråde på samme tid, skal de også implementeres, så de er trådsikre.

Interprocesskommunikation

Android tilbyder en mekanisme til interprocesskommunikation (IPC) ved hjælp af fjernprocedureopkald (RPC’er), hvor en metode kaldes af en aktivitet eller en anden programkomponent, men udføres eksternt (i en anden proces), med et eventuelt resultat returneret tilbage til den, der kalder. Dette indebærer, at et metodeopkald og dets data skal dekomponeres til et niveau, som operativsystemet kan forstå, og at det skal overføres fra den lokale proces og det lokale adresseområde til den eksterne proces og det eksterne adresseområde, hvorefter opkaldet skal samles og genudføres der. Returværdier overføres derefter i den modsatte retning. Android leverer al koden til at udføre disse IPC-transaktioner, så du kan fokusere på at definere og implementere RPC-programmeringsgrænsefladen.

For at udføre IPC skal dit program binde sig til en tjeneste ved hjælp af bindService(). Du kan finde flere oplysninger i Udviklingsvejledning for tjenester.

Skriv en kommentar