Gå til hovedindhold
Version2 it for professionelle
Forsiden

Hovedmenu

  • It-nyheder
  • Blogs
  • It-job
  • It-firmaer
  • Emner
  • Opret bruger
  • Log ind
Emner Udviklingsværktøjer, Java, Styresystemer

Android-skolen del 4: Gør androiden mindre tungnem med trådning af GUI'en

Trådning af den grafiske brugergrænseflade er på skemaet i fjerde omgang af Android-skolen. Det lyder tørt, men er knusende vigtigt for brugeroplevelsen af din applikation.

Af Mikkel Meister Fredag, 4. december 2009 - 15:59

Du kender det sikkert. Et tryk på en knap eller lidt scrolling i en menu får programmet til at fryse fast og efterlader det lammet og inaktivt, mens du febrilsk hamrer på musen eller begår overgreb på touchskærmen.

Velkommen til fjerde lektion af Android-skolen, hvor vi denne gang tager fat i et vigtigt emne, der sikrer responsvillighed i din Android-applikation: Trådning af den grafiske brugergrænseflade.

Den vigtigste læresætning denne gang er at undgå tunge opgaver i selve UI-tråden. De blokerer den grafiske brugergrænseflade, så længe de udføres, og det efterlader brugeren med, hvad der virker som en 'frosset' telefon.

Det er for eksempel håbløst at sætte gang i at downloade billedfiler i din onCreate()-metode, der kaldes, når din applikation starter op allerførste gang. Gør du det alligevel, vil du hurtigt se, at den grafiske brugerflade 'hænger', så længe den bagvedliggende operation er ved at blive udført.

»Undersøgelser viser at brugere, kan opfatte 'lags' (forsinket GUI-respons, red.) allerede ved 100-200 millisekunder. Med en platform som Android, hvor programmøren har så mange muligheder, er det vigtigt ikke at glemme, at det stadig er en mobiltelefon med begrænset hastighed og hukommelse, man arbejder på,« forklarer fjerde lektions gæsteunderviser, Android-udvikler Klaus Kartou.

Han er en af ophavsmændene til koncertapplikationen Gigbox, der for ganske nylig fik en fjerdeplads i Lifestyle-kategorien i Googles stort anlagte konkurrence Android Developer Challenge 2.

»Da alt som standard udføres i selve hovedtråden, kaldet UI-tråden, vil tunge opgaver blokere UI-tråden. Det betyder, at der ikke bliver sendt nogle events afsted, hvis brugeren for eksempel klikker på en knap eller scroller. Set fra brugerens synspunkt vil det se ud som om, at applikationen hænger,« siger Klaus Kartou.

Din ven i denne lektion er klassen AsyncTask, der hjælper dig med at tråde den grafiske brugerflade. Du kunne i princippet benytte Javas egen klasse til trådning, Thread, men AsyncTask gør livet nemmere for dig.

Denne gang er det ikke afgørende, om du afvikler applikationen på din Android-telefon eller i Android-emulatoren.

1) Problemet i al sin enkelhed

Android-applikationer kører i udgangspunktet på én hoved-tråd, kaldet UI-tråden. Den står for at uddelegere de forskellige events, der sker i brugergrænsefladen, til de dele af applikationen, der skal have besked om det.

Når du for eksempel trykker på en knap på skærmen, giver UI-tråden besked om hændelsen, hvorefter applikationen udfører en operation og gentegner sig selv.

Problemerne begynder, hvis den bagvedliggende operation tager tid at udføre. Hvis du for eksempel har bedt din applikation om at downloade et billede fra nettet, når der klikkes på knappen, vil brugergrænsefladen 'fryse', så længe operationen er i gang.

Det opleves rent visuelt ved, at knappen bliver holdt nede, så længe operationen er i gang, i stedet for at knappen slipper med det samme, og operationen udføres i baggrunden.

Det betyder i værste fald, at Android begynder at brokke sig over, at din applikation ikke svarer. Du får et såkaldt Application Not Responding-dialogboks (ANR) op på skærmen efter omtrent fem sekunders ventetid, der spørger, om du vil vente på applikationen, eller tvinge den til at lukke ned, som vist i eksemplet nedenfor.

Ikke særligt brugervenligt. Vi illustrerer nu problemet med en 'beregningstung' opgave, der blokerer UI-tråden.

2) Læg ud med at gøre noget dumt Start Eclipse.

Opret et nyt Android-projekt, ganske som du plejer. Navngiv applikationen som vist nedenfor, eller efter eget valg.

Endnu engang er det metoden onCreate(), du tager udgangspunkt i. Som bekendt er det den, der bliver kaldt, når din applikation startes op. Koden ser nu ud som følger:

1 **public class **AndroidSkolenDel4 **extends **Activity { 2     /** Called when the activity is first created. */ 3     @Override 4     **public ****void **onCreate(Bundle savedInstanceState) { 5         **super**.onCreate(savedInstanceState); 6         setContentView(R.layout.main); 7     } 8 }

For at demonstrere, hvordan man absolut ikke skal håndtere tidskrævende operationer i en Android-applikation, opretter vi nu en knap i brugergrænsefladen, som får en progressbar til at tælle op fra 0-100 på fem sekunder.

Vi begynder med at definere en knap og en progressbar i vores layout-fil main.xml, der ligger i projektundermappen res/layout. Filen ser således ud efter vores ændringer:

01 "1.0" encoding="utf-8"?> 02 "http://schemas.android.com/apk/res/android" 03     android:orientation="vertical" 04     android:layout_width="fill_parent" 05     android:layout_height="fill_parent" 06     > 07  08     android:id="@+id/my_button" 09         android:layout_width="100px" 10         android:layout_height="wrap_content" 11         android:text="foo" 12         /> 13 "@+id/progress_bar"  14         android:layout_width="fill_parent"  15         android:layout_height="wrap_content" 16         style="?android:attr/progressBarStyleHorizontal" /> 17 

Læg mærke til, at vi blot har slettet det TextView, der ligger i filen som standard, og i stedet defineret en knap med teksten 'foo' og en progressbar.

Tilbage i AndroidSkolenDel4.java beder vi nu onCreate() om at oprette knappen og progressbar'en og få den til at tælle op på fem sekunder, når vi trykker på knappen.

01 **public class **AndroidSkolenDel4 **extends **Activity { 02  03   **private **Button button; 04   **private **ProgressBar progressBar; 05   **private ****int **progressStatus = 0; 06  07   **public ****void **onCreate(Bundle savedInstanceState) { 08     **super**.onCreate(savedInstanceState); 09     setContentView(R.layout.main); 10  11     button = (Button) findViewById(R.id.my_button); 12     progressBar = (ProgressBar) findViewById(R.id.progress_bar); 13  14     button.setOnClickListener(**new **View.OnClickListener() { 15       **public ****void **onClick(View v) { 16         **try **{ 17           **while **(progressStatus < 100) { 18             Thread.sleep(50); 19             progressStatus++; 20             progressBar.setProgress(progressStatus); 21           } 22  23         } **catch **(InterruptedException e) { 24           // TODO Auto-generated catch block 25           e.printStackTrace(); 26         } 27       } 28     }); 29   }

Lad os se på koden linje for linje:

Vi begynder med at oprette en knap og en progressbar i linje 11-12.

I linje 14 opretter vi en onClickListener, der lytter på et tryk på vores knap. Vores intention er at få progressbar'en til at tælle én tak op, hver gang der er gået 50 millisekunder, Thread.sleep(50), i linje 17-21. Tanken er naturligvis, at det skal være synligt for brugeren, at progressbar'en tælles op.

Vi tæller variablen progressStatus op fra 0-100, så der sammenlagt kommer til at gå fem sekunder, før progressbar'en er talt helt op.

Oversæt og kør applikationen med ctrl+b efterfulgt af ctrl+F11

Vi ser nu, at knappen foo bliver ved med at være markeret med en farve, selvom vi har sluppet den igen. Samtidig bliver progressbar'en slet ikke opdateret i den periode, vi har fat i UI-tråden. Det betyder, at progressbar'en står på 0 i fem sekunder, hvorefter den pludselig springer til 100.

Det skyldes, at vi har placeret den 'beregningstunge' del af koden i vores onClickListener(). Dermed blokerer koden UI-tråden og forhindrer knappen og progressbar'en i at blive opdateret løbende.

Det skal der laves om på.

3) Lær af dine fejl

Du skal nu lægge den tunge del af koden ud i sin egen tråd, så UI-tråden ikke blokeres, mens koden udføres.

Tanken er at erstatte koden i vores onClickListener med et enkelt kald, der opretter og sætter en tråd i sving 'ved siden af' UI-tråden.

Som nævnt tidligere har Java sin egen Thread-klasse, men vi vælger at bruge Androids egen klasse, AsyncTask, der er en mere simpel indpakning af Javas egen trådhåndtering.

»AsyncTask er en simpel og effektiv måde for programmøren at udføre baggrundsopgaver og kommunikere resultater tilbage til UI-tråden, uden at skulle sætte sig ind i Java-threads og handlers. Det gør det nemt for nye brugere at håndtere baggrundsopgaver på Android-platformen,« forklarer Klaus Kartou.

Pointen er, at man ikke kan opdatere UI-komponenter fra en anden tråd end UI-tråden. AsyncTask er smart, fordi dens metoder udføres på UI-tråden automatisk.

Vi opretter nu en inner class, MyTask, lige under vores onCreate()-metode. MyTask sættes til at nedarve fra AsyncTask-klassen.

Samtidig rydder vi indholdet i vores onClickListener i onCreate()-metoden. Koden ser nu ud som følger:

01 **public class **AndroidSkolenDel4 **extends **Activity { 02  03   **private **Button button; 04   **private **ProgressBar progressBar; 05  06   **public ****void **onCreate(Bundle savedInstanceState) { 07     **super**.onCreate(savedInstanceState); 08     setContentView(R.layout.main); 09  10     button = (Button) findViewById(R.id.my_button); 11     progressBar = (ProgressBar) findViewById(R.id.progress_bar); 12  13     button.setOnClickListener(**new **View.OnClickListener() { 14       **public ****void **onClick(View v) { 15  16       } 17     }); 18   } 19    20   **private class **MyTask **extends **AsyncTask { 21  22     @Override 23     **protected **Boolean doInBackground(String... params) { 24       // TODO Auto-generated method stub 25       **return null**; 26     }     27   }

Den autogenererede metode doInBackground er dér, du gerne vil lægge din beregningstunge del af koden.

Vi kan dog ikke nøjes med den til vores applikation, da vi også skal sørge for at opdatere vores progressbar løbende. Derfor tilføjer vi metoden onProgressUpdate(), der står for netop den del.

V lægger nu vores beregningstunge kode ind i doInBackground. Læg mærke til, at koden er den samme, som vi brugte i vores 'dårlige' eksempel ovenfor.

01   **private class **MyTask **extends **AsyncTask { 02     **private **ProgressBar myProgressBar; 03     **private ****int **myProgressStatus; 04  05     **private **MyTask(ProgressBar p) { 06       myProgressBar = p; 07       myProgressStatus = 0; 08     } 09  10     **protected **Boolean doInBackground(String... params) { 11       **try **{ 12         **while **(myProgressStatus < 100) { 13           Thread.sleep(50); 14           myProgressStatus++; 15           publishProgress(myProgressStatus); 16         } 17  18       } **catch **(InterruptedException e) { 19         // TODO Auto-generated catch block 20         e.printStackTrace(); 21       } 22       **return true**; 23     } 24  25     **protected ****void **onProgressUpdate(Integer... progress) { 26       myProgressBar.setProgress(progress[0]); 27     } 28     ...

Vi tager et grundigt kig på koden:

I linje 5-8 definerer vi en contructor til MyTask-klassen, som tager en progressbar som argument. Det sikrer os, at vi kan give vores egen progressbar i onCreate()-metoden med som argument, og at vi samtidig vil kunne oprette flere progressbar-tråde, hvis vi ønskede det.

Som sagt indeholder metoden doInBackground() udelukkende den del af koden, vi tidligere havde liggende under onClickListener'en i vores onCreate()-metode.

Læg dog mærke til linje 15, hvor vi kalder AsyncTask-metoden publishProgress(). Den står for at opdatere vores progressbar, og den udløser samtidig et kald til metoden onProgressUpdate() i linje 25-27.

Bemærk samtidig, at vi har sat doInBackground til at returnere 'true' i stedet for 'null' i linje 22.

Nu har du oprettet en klasse, MyTask, som gør dig i stand til at opdatere en progressbar og få den vist løbende i UI-tråden.

Nu skal du blot indsætte linjen

new MyTask(progressBar).execute();

i onClickListener'en i onCreate()-metoden, så den kommer til at tage sig således ud:

1     ... 2     button.setOnClickListener(**new **View.OnClickListener() { 3       **public ****void **onClick(View v) { 4         **new **MyTask(progressBar).execute(); 5       } 6     }); 7     ...

Oversæt og kør projektet med ctrl+b efterfulgt af ctrl+F11

Nu 'fryser' GUI'en ikke længere, når vi trykker på knappen 'foo', og progressbar'en løber stille og roligt op på hundrede procent i løbet af de fem sekunder, vi har fastsat i koden.

Nu er vi sådan set færdige, men AsyncTask tilbyder en smart metode, onPostExecute(), der giver dig mulighed for at vise en meddelelse på skærmen, når din tråds arbejde er vel overstået.

Vi indsætter onPostExecute umiddelbart efter onProgressUpdate() i vores MyTask-klasse.

01 ... 02     **protected ****void **onPostExecute(Boolean result) { 03       Context context = getApplicationContext(); 04       **int **duration = Toast.LENGTH_LONG; 05       **if **(result) { 06         CharSequence text = "Hurraaaa!"; 07         Toast toast = Toast.makeText(context, text, duration); 08         toast.show(); 09       } **else **{ 10         // vis en fejlbesked 11       } 12     } 13         ...

Argumentet result til onPostExecute() er det resultat, doInBackground() returnerer. Hvis alt er gået vel med at udføre vores operation i baggrunden, returnerer doInBackground() boolean-værdien 'true'.

Den laver vi et tjek på i onPostExecute(), og hvis resultatet er 'true', viser vi en lille besked på skærmen ? et såkaldt Toast.

Oversæt og kør koden med ctrl+b efterfulgt af ctrl+F11.

Når progressbar'en er talt helt op, kommer der en lille besked op på skærmen, som forsvinder igen efter et kort tidsrum.

Lad os slutte af med at kigge på den samlede kode:

01 **public class **AndroidSkolenDel4 **extends **Activity { 02  03   **private **Button button; 04   **private **ProgressBar progressBar; 05  06   **public ****void **onCreate(Bundle savedInstanceState) { 07     **super**.onCreate(savedInstanceState); 08     setContentView(R.layout.main); 09  10     button = (Button) findViewById(R.id.my_button); 11     progressBar = (ProgressBar) findViewById(R.id.progress_bar); 12      13     button.setOnClickListener(**new **View.OnClickListener() { 14       **public ****void **onClick(View v) { 15         **new **MyTask(progressBar).execute(); 16       } 17     }); 18      19   } 20    21   **private class **MyTask **extends **AsyncTask { 22     **private **ProgressBar myProgressBar; 23     **private ****int **myProgressStatus; 24  25     **private **MyTask(ProgressBar p) { 26       myProgressBar = p; 27       myProgressStatus = 0; 28     } 29  30     **protected **Boolean doInBackground(String... params) { 31       **try **{ 32         **while **(myProgressStatus < 100) { 33           Thread.sleep(50); 34           myProgressStatus++; 35           publishProgress(myProgressStatus); 36         } 37  38       } **catch **(InterruptedException e) { 39         // TODO Auto-generated catch block 40         e.printStackTrace(); 41       } 42       **return true**; 43     } 44  45     **protected ****void **onProgressUpdate(Integer... progress) { 46       myProgressBar.setProgress(progress[0]); 47     } 48  49     **protected ****void **onPostExecute(Boolean result) { 50       Context context = getApplicationContext(); 51       **int **duration = Toast.LENGTH_LONG; 52       **if **(result) { 53         CharSequence text = "Hurraaaa!"; 54         Toast toast = Toast.makeText(context, text, duration); 55         toast.show(); 56       } **else **{ 57         // vis en fejlbesked 58       } 59     } 60   } 61 }

Læg mærke til, at onCreate() udelukkede står for at oprette knap, progressbar og onClickListener() til knappen, samt at oprette tråden, vi udfører det tunge arbejde i.

Selve det tunge arbejde ligger i metoden doInBackground() i vores MyTask-klasse, som samtidig indeholder metoderne onProgressUpdate() og onPostExecution() til henholdsvis at opdatere progressbar'en og vise brugeren en meddelelse, når tråden er færdig med sit arbejde.

Tænk trådsikkerhed ind Når man har med flere tråde at gøre, skal der tænkes på trådsikkerheden. Du skal med andre ord sikre, at du ikke modificerer for eksempel en collection fra flere forskellige tråde uden at holde styr på slagets gang.

»Java tilbyder nogle tråd-sikre collection-klasser, som tager højde for netop det. CopyOnWriteArrayList-klassen opretter f.eks. en kopi af de underlæggende data, når en tråd ændrer på den. I Gigbox-applikationen bruger vi CopyOnWriteArrayList-objekter de steder, hvor vi ved, at flere tråde tilgår samme collection. Herved undgår vi de problemer, der kan opstå, hvis flere tråde ændrer på samme data samtidigt,« siger Klaus Kartou.

Det var alt for denne udgave af Android-skolen. Næste gang ser vi nærmere på avanceret GUI-programmering.

Litteratur: [1] http://developer.android.com

Skab Danmarks bedste Android-applikation og vind en eksklusiv tur med alt betalt til verdens største mobilmesse i Barcelona. Deadline er 11. januar 2010. Læs mere på http://www.version2.dk/android

Send Tweet
Udskriv

IT-job & karriere

  • Se alle it-job
  • Importer din kompetenceprofil fra LinkedIn
Java udviklere – Web-frontend
Udgivet 16. jun 2011 14.21
Skarp C#-udvikler søges til fast stilling i spændende virksomhed i Østjylland
Udgivet 8. feb 9.17
Java-backender med flair for frontend søges
Udgivet 11. jan 11.02
Java J2EE udvikler ( J2EE )
Udgivet 23. nov 2011 13.43

Tilføj kommentar

Opret en konto eller log ind for at følge indhold på Version2 - og bliv opdateret via e-mail eller rss

Følg kommentarer
Log ind herunder eller opret en bruger for at skrive kommentarer
Du kan logge ind med din e-mail-adresse
Der er forskel på store og små bogstaver i adgangskoden.
Glemt adgangskode?

Seneste nyt

Teknologirådet reddet: Fortsætter i ændret konstruktion

Udgivet 10. feb 11.32Opdateret 10. feb 11.32

Version2 tester: Her kan du fare vild i Windows 8

Udgivet 10. feb 10.44Opdateret 10. feb 11.04

Rygte: Google snart klar med Dropbox-konkurrent

Udgivet 10. feb 10.19Opdateret 10. feb 10.19

Ny blog stiller skarpt på juraen i it-kontrakter

Udgivet 10. feb 10.00Opdateret 10. feb 10.15

Windows 8 Consumer Preview klar til download 29. februar

Udgivet 10. feb 9.49Opdateret 10. feb 10.24
Flere it-nyheder »
Få it-nyheder og blogs hver dag med Version2's nyhedsbrev.

Seneste debat

  1. 4 gode sikkerhedsråd: Sådan gør du firma-pc'en vinterferieklar

    7 comments.
    Last update 2 minutter 9 sekunder
    Skrevet af Thomas Bundgaard
  2. Er it-skandalerne kontrakternes skyld?

    4 comments.
    Last update 3 minutter 50 sekunder
    Skrevet af Nicolai Dragsted
  3. Derfor bliver dårlige it-projekter ikke stoppet i tide

    4 comments.
    Last update 5 minutter 20 sekunder
    Skrevet af Daniel Madsen
  4. XBMC på fit-PC3

    20 comments.
    Last update 16 minutter 16 sekunder
    Skrevet af Peter Toft
  5. Microsoft skrotter Startknappen i Windows 8

    14 comments.
    Last update 18 minutter 18 sekunder
    Skrevet af Alex Larsen
  6. Konklusion af Polsag-review fra 2009: Elendig kode hånd i hånd med elendig kontrakt

    14 comments.
    Last update 18 minutter 27 sekunder
    Skrevet af Casper Skydt
  7. Opdateret liste over danske iværksættere

    3 comments.
    Last update 19 minutter 57 sekunder
    Skrevet af Johannes Ulfkjær Jensen
  8. Enhedslisten: Nødvendigt med ny it-strategi, hvis skandaler skal undgås

    11 comments.
    Last update 38 minutter 37 sekunder
    Skrevet af Martin Ipsen Pedersen
Mere debat »

Information

  • Kontakt redaktionen
  • Job- og annoncesalg
  • Teknisk support
  • Om Version2
  • Brugerbetingelser
  • Privatlivspolitik

Aktuelle emner

  • Agil udvikling
  • Android
  • Bruttolønsordning
  • Business Intelligence
  • Cloud computing
  • Digitaliseringsstyrelsen
  • HTML5
  • Harddisk-priser
  • IE9
  • Intranet
  • It-sikkerhed
  • Kindle Fire
  • Multimedieskat
  • NemID
  • OS X Lion
  • Open source CMS
  • Projektledelse
  • Scrum
  • Sharepoint intranet
  • Storage
  • Ubuntu 11.10
  • Virtualisering
  • Windows 8
  • Windows Phone 7
  • iOS 5
  • iPhone 4S

Tjenester

  • Android-app
  • iPhone-app
  • RSS-feeds
Følg @version2dk
Få it-nyheder og blogs hver dag med Version2's nyhedsbrev.

Version2 udgives af

  • Mediehuset Ingeniøren A/S work Skelbækgade 4 1717 København V
  • Tlf. work 33265300