I Android er menuer en vigtig del af brugergrænsefladen, og de giver brugeren mulighed for at interagere med applikationen på en enkel og effektiv måde. Menusystemet i Android er meget fleksibelt og giver udviklere mulighed for at definere menuer i XML-ressourcer og derefter inflatere dem i deres aktiviteter. I denne sektion vil vi gå igennem, hvordan man opretter og håndterer et Options-menu i Android, herunder oprettelsen af et menu-XML, inflation af menuen i aktivitetens livscyklus og hvordan man reagerer på brugerens valg.
Et menu i Android kan oprettes i XML, som mange andre Android-ressourcer. Når du opretter et menu, er det vigtigt at forstå, hvordan forskellige menuelementer fungerer og interagerer med hinanden. Menuerne gemmes typisk i mappen res/menu og kan defineres med <menu>-tagget. Hvert menuobjekt defineres med et <item>-tag, der indeholder de nødvendige attributter som id, title, icon, og showAsAction.
Oprettelse af et Options-menu
For at oprette et Options-menu i Android, skal du følge nogle grundlæggende trin. Først skal du definere menuen i XML og derefter inflatere den i din aktivitet.
-
Definer menuen i XML: Opret en XML-fil i
res/menumappen, som beskriver dine menuobjekter. Et grundlæggende eksempel på en menu i XML kunne være:
I dette eksempel har vi defineret et enkelt menupunkt med id'et menu_settings og teksten "Settings". Attributten showAsAction="never" betyder, at dette menupunkt aldrig vil blive vist i Action Baren, men i stedet vil det blive vist i overflow-menuen.
-
Inflatere menuen i aktivitetens livscyklus: Når du har oprettet XML-filen, skal du sørge for at inflatere menuen i din aktivitet. Dette gøres i metoden
onCreateOptionsMenu(), som er ansvarlig for at oprette og vise menuen i din aktivitet. Du kan inflatere menuen som følger:
I denne metode anvender vi getMenuInflater()-metoden til at inflatere vores menu-XML og tilføje menuobjekterne til menuen.
-
Håndtering af brugerens valg: Når brugeren vælger et menupunkt, skal applikationen kunne reagere på dette valg. Dette gøres i metoden
onOptionsItemSelected(). Hvis brugeren vælger "Settings"-menupunktet, kan vi for eksempel vise en toast-meddelelse som feedback:
Dette er en simpel implementering, hvor vi viser en toast, når brugeren vælger indstillingsmenuen. Du kan naturligvis erstatte toast med en handling som at starte en ny aktivitet.
Brug af showAsAction-attributten
En af de vigtigste aspekter ved oprettelsen af et menu i Android er korrekt brug af showAsAction-attributten. Denne attribut styrer, hvordan menuen vises i Action Baren. De mulige værdier for showAsAction er:
-
ifRoom: Menuen vises i Action Baren, hvis der er nok plads. -
withText: Både ikonet og teksten vises. -
never: Menuen vises aldrig i Action Baren, kun i overflow-menuen. -
always: Menuen vises altid i Action Baren, men vær opmærksom på, at pladsen er begrænset.
Du kan kombinere flere af disse muligheder med en lodret streg (|), som for eksempel showAsAction="ifRoom|withText", for at tilpasse visningen af menuen i Action Baren.
Håndtering af undermenuer og grupper af menupunkter
Android giver også mulighed for at oprette undermenuer. Undermenuer defineres ved at inkludere et <menu>-element indenfor et andet <menu>-element. For eksempel:
Gruppering af menuobjekter kan også være nyttigt. Android understøtter grupper, hvor flere menuobjekter kan grupperes sammen og styres kollektivt. Du kan for eksempel vise eller skjule alle elementer i en gruppe med setGroupVisible(), aktivere eller deaktivere dem med setGroupEnabled() eller gøre dem checkable med setGroupCheckable().
Dette gør det nemt at håndtere flere relaterede menupunkter samtidigt.
Vigtige overvejelser
Når du arbejder med menuer i Android, er det vigtigt at overveje både brugervenlighed og præstation. At vise for mange elementer i Action Baren kan gøre brugergrænsefladen rodet og svær at navigere. Derfor bør man bruge showAsAction="ifRoom" og kun vise de vigtigste menupunkter direkte i Action Baren.
Desuden bør menuer kun indeholde de handlinger, der er relevante for den aktuelle kontekst af aktiviteten. En god praksis er at holde menuen enkel og intuitiv, så brugeren hurtigt kan finde de funktioner, de har brug for.
Hvordan gemme og læse data på Android-enheder
For at kunne arbejde med brugerdata effektivt i en Android-applikation, er det vigtigt at forstå de forskellige metoder til at gemme og hente data. Dette kan være alt fra at gemme simple navne i SharedPreferences, til at arbejde med tekstfiler i intern og ekstern lager. I denne sektion vil vi gennemgå, hvordan man implementerer datalagring og læsning i Android, både via SharedPreferences og ved at arbejde med tekstfiler på intern opbevaring.
Når du arbejder med Android, skal du forstå forskellen mellem de forskellige opbevaringsmetoder. For simple indstillinger og små data, som for eksempel brugerens navn eller præferencer, er SharedPreferences en enkel og effektiv løsning. Hvis du derimod har brug for at gemme større datamængder eller komplekse objekter, kan du vælge at arbejde med interne tekstfiler.
Brug af SharedPreferences til at gemme og hente data
For at gemme et navn ved hjælp af SharedPreferences i en Android-applikation, er det nødvendigt at oprette globale variabler og referencer til de nødvendige UI-elementer. For eksempel kan vi definere en konstant, der bruges som nøgle for at gemme brugerens navn:
Herefter skal du få adgang til SharedPreferences og hente det gemte navn i onCreate()-metoden. Hvis navnet er blevet gemt tidligere, vises det på skærmen, ellers vises en standard hilsen:
Når navnet skal gemmes, kan du oprette en metode som denne:
Når applikationen kører, kan brugeren gemme sit navn, og programmet vil automatisk hente det igen, næste gang applikationen åbnes. Det er vigtigt at forstå, at når man gemmer data med SharedPreferences, sker ændringerne ikke, medmindre commit()-metoden bliver kaldt. Uden dette vil ændringerne ikke blive gemt permanent.
Arbejde med interne filer
Når du har brug for at gemme tekstdata, som er større end de enkle navn/værdi-par i SharedPreferences, kan du bruge interne filer. For at demonstrere, hvordan man læser og skriver tekstfiler, kan du følge disse trin:
-
Opret en ny projekt i Android Studio, og kald det
InternalStorageFile. -
I layoutfilen (
main_activity.xml), tilføj etEditTextog to knapper til at skrive og læse filer. -
Opret globale variabler i
ActivityMain.java:
-
I
onCreate()-metoden, referér tilEditText:
-
Opret en metode til at skrive til en fil:
-
Opret en metode til at læse fra filen:
Disse metoder gør det muligt at skrive tekst til en intern fil og læse fra den igen. Filen gemmes i applikationens private opbevaring, og kan ikke tilgås af andre applikationer. Hvis du vil finde filen, kan du bruge Android Device Monitor til at navigere til dens placering.
Ekstra oplysninger om cache og ekstern opbevaring
Hvis applikationen kun skal gemme midlertidige data, kan du også benytte cache-mappen. Denne mappes indhold kan blive ryddet af systemet, hvis lagerpladsen bliver lav, og den kan også blive tømt af brugeren i appens indstillinger. Du kan få adgang til cache-mappen ved at bruge metoden getCacheDir(). Det er en god praksis at slette forældede filer fra cache-mappen for at undgå, at appen bruger unødvendig plads.
På den anden side, hvis du har brug for at gemme data, som skal være tilgængelige uden for applikationen, kan du bruge ekstern opbevaring. At arbejde med ekstern opbevaring er meget lig arbejdet med intern opbevaring, bortset fra at du skal have tilladelser til at skrive til ekstern lager, og du skal bruge metoder som getExternalStorageDirectory().
Afsluttende bemærkninger
Det er vigtigt at vælge den rette opbevaringsmetode afhængig af applikationens behov. SharedPreferences er ideel til at gemme små, enkle indstillinger som brugerens præferencer, mens interne filer er mere passende til større mængder data. Hvis appen skal kunne dele data med andre apps eller give brugeren mulighed for at tilgå dataene direkte, bør ekstern opbevaring overvejes. Det er også vigtigt at forstå de potentielle konsekvenser af at arbejde med cache og ekstern opbevaring, især hvad angår systemets opbevaringsadministration.
Hvordan man anvender projektion og kameravisning i OpenGL ES for at tilpasse skærmorientering
Når man arbejder med OpenGL ES til at tegne grafiske objekter, såsom trekanter, på en enhedsskærm, kan man støde på problemer med skærmorienteringen. Som vi så i den tidligere opskrift, når vi tegner et objekt til skærmen, vil objektet blive skævt, afhængigt af enhedens orientering. Årsagen til dette er, at OpenGL standardmæssigt antager, at skærmen er perfekt firkantet. Dette er sjældent tilfældet med moderne enheder, der ofte har skærme med forskellige dimensioner og opløsninger.
I OpenGL er dette problem løst gennem en teknik kaldet projektion. Projektionen hjælper med at tilpasse de virtuelle koordinater, der anvendes i OpenGL, til de fysiske dimensioner af den enhed, hvorpå objektet vises. Denne proces kræver, at vi først beregner en projektionsmatrix, som omdanner skærmens koordinatsystem til et system, der passer til den specifikke enhedsformat og dimensioner.
I denne opskrift vil vi dykke ned i, hvordan man anvender projektion og kameravisning til at tilpasse OpenGL-objekter, så de bliver korrekt gengivet på en skærm, uanset om skærmen er i portræt- eller landskabsorientering.
For at kunne gennemføre dette, skal vi modificere den kode, vi tidligere har arbejdet med, hvor vi tegnede trekanter på en GLSurfaceView. Vi vil nu introducere projektionsmatrixen og kameravisningen, så de objekter, vi tegner, tilpasses korrekt til den fysiske skærm. Først og fremmest skal vi opdatere vores shaderkode, så vi kan anvende en matrix til at beregne positionen af hvert punkt på objektet.
I shaderkoden til vertexen skal vi tilføje en matrixvariabel. Denne matrix vil blive brugt til at beregne positionen af hvert punkt, så det passer til de korrekte skærmkoordinater. Dette kan gøres ved at ændre vertex-shaderkoden til at inkludere en uniform matrix, der anvender transformationsmatrixen, før positionen beregnes.
Når det er gjort, skal vi modificere draw()-metoden til at tage matrixen som parameter og anvende den før rendering. På denne måde vil vi sikre, at hvert punkt i objektet bliver korrekt transformeret, så det kan tegnes korrekt i forhold til den aktuelle skærmorientering.
Dernæst skal vi beregne en projektionsmatrix i onSurfaceChanged()-metoden. Denne metode bliver kaldt, når størrelsen på skærmen ændres, og her vil vi beregne forholdet mellem bredden og højden på skærmen. Dette forhold bruges til at opdatere projektionsmatrixen, så objekterne stadig passer til den fysiske skærm, selvom enhedens orientering ændrer sig.
For at afslutte processen skal vi beregne en kameravisning i onDrawFrame()-metoden. Dette gøres ved at bruge metoden Matrix.setLookAtM(), som definerer kameraets position og orientering i forhold til objektet. Når vi har beregnet kameravisningen, kan vi kombinere den med projektionsmatrixen ved hjælp af Matrix.multiplyMM() for at skabe den endelige MVP-matrix, der bruges til at tegne objektet korrekt på skærmen.
Ved at følge disse trin vil vi sikre, at objekterne tegnes korrekt uanset enhedens orientering. I den næste opskrift vil vi videreudvikle dette ved at introducere rotation, så objekterne kan bevæge sig rundt på skærmen.
Det er vigtigt at bemærke, at vi i OpenGL ES arbejder med et koordinatsystem, der standardmæssigt antager en firkantet skærm. Når vi tilpasser vores objekt til en enhed med et ikke-firkantet skærmformat, er det nødvendigt at forstå, hvordan matrixtransformeringer fungerer, og hvordan de påvirker den endelige rendering. Når vi justerer både projektionen og kameravisningen, skaber vi en mere fleksibel og korrekt gengivelse af objekterne i forskellige skærmorienteringer.
Når disse grundlæggende transformationer er på plads, kan vi begynde at arbejde med mere avancerede teknikker som rotation og interaktiv animation. Dette åbner op for endnu mere dynamiske og engagerende brugeroplevelser, som kan udnytte den fulde kraft af OpenGL til at skabe komplekse grafiske applikationer.
Endtext
Hvordan man undgår ANR-fejl ved brug af baggrundstråde i Android
At udføre langvarige operationer på hovedtråden kan få din applikation til at føles langsom eller, i værste fald, føre til, at den fryser. Hvis en applikation ikke reagerer indenfor cirka 5 sekunder, vil systemet sandsynligvis vise en "Application Not Responding" (ANR) dialog, som giver brugeren mulighed for at afslutte appen. Dette er noget, man absolut vil undgå, da det er en effektiv måde at få din app afinstalleret på.
Android-applikationer arbejder med en en-tråds model, der er styret af to enkle regler:
-
Bloker ikke hovedtråden
-
Udfør alle UI-operationer på hovedtråden
Når Android starter din applikation, opretter det automatisk hovedtråden (eller UI-tråden), som er den tråd, hvor alle UI-operationer skal udføres. Den første regel – “Bloker ikke hovedtråden” – betyder, at enhver langvarig eller potentielt blokerende opgave skal køres på en baggrundstråd. Det er derfor, alle netværksbaserede opgaver skal udføres udenfor hovedtråden.
Android tilbyder flere muligheder, når du arbejder med baggrundstråde:
-
Activity.runOnUiThread() -
View.post() -
View.postDelayed() -
Handler -
AsyncTask
Denne opskrift vil fokusere på klassen AsyncTask, som tidligere blev oprettet, så du ikke behøver at bruge Handler eller post-metoderne direkte.
I Android Studio skal du oprette et nyt projekt kaldet AsyncTask. Vælg standardindstillingen "Phone & Tablet" og vælg "Empty Activity" som aktivitetstype. Vi skal kun bruge én knap i dette eksempel, og så kan vi definere en simpel AsyncTask for at vise, hvordan man kører langvarige opgaver uden at blokere hovedtråden.
I activity_main.xml skal du erstatte den eksisterende TextView med følgende knap:
I MainActivity.java definerer du en global variabel:
Derpå opretter du AsyncTask-klassen som en intern klasse i MainActivity:
I onCreate() initialiserer du knappen:
Og tilføj en metode, der aktiverer AsyncTask, når knappen bliver trykket:
Dette er en meget simpel implementering af AsyncTask. Teknisk set er det kun doInBackground()-metoden, der er nødvendig, men normalt vil man også bruge onPostExecute() for at få notifikationer, når opgaven er afsluttet. AsyncTask arbejder ved at oprette en arbejdertråd for doInBackground()-metoden og svarer tilbage på UI-tråden i onPostExecute()-callbacken.
Vær opmærksom på, at vi ikke udfører nogen UI-operationer i baggrundstråden, fordi det vil føre til kompilationsfejl eller runtime undtagelser. Derfor venter vi til onPostExecute() bliver kaldt, før vi gør noget med UI, som at aktivere knappen igen.
En vigtig detalje er, at en AsyncTask kun kan udføres én gang. Hvis du forsøger at køre execute() igen på den samme AsyncTask, vil det kaste en undtagelse. Derfor skal du oprette et nyt AsyncTask-objekt hver gang knappen bliver trykket.
AsyncTask er meget fleksibel og kan bruges på en simpel måde som i eksemplet, men giver også mulighed for at arbejde med mere komplekse scenarier, f.eks. opdateringer af fremskridt eller håndtering af annullering af opgaver. For eksempel kan opdatering af brugergrænsefladen med fremskridt ske ved at bruge metoden onProgressUpdate() i AsyncTask, som reagerer på kaldet publishProgress() i baggrundstråden.
Når du arbejder med AsyncTask, er det dog vigtigt at forstå, hvad der sker, når aktiviteten, som AsyncTask kører på, bliver ødelagt og oprettet igen (f.eks. ved en skiftende skærmrotation). Hvis AsyncTask stadig kører, kan det føre til fejl, da det vil forsøge at interagere med en nu destrueret aktivitet. Derfor er det ofte en god idé at bruge AsyncTask med en Fragment, som ikke bliver ødelagt ved skærmrotation, eller overveje at bruge en Loader, som er mere robust i sådanne scenarier.
En yderligere overvejelse ved brug af AsyncTask er de tre generiske parametre, der anvendes ved deklarationen af AsyncTask-klassen:
-
Params: Den type parameter, der bruges til at kalde
doInBackground() -
Progress: Den type parameter, der bruges til at opdatere fremskridt
-
Result: Den type parameter, der bruges til at sende resultater tilbage
Derudover kan du annullere en opgave ved at kalde cancel(true) på AsyncTask-objektet. Dette vil få isCancelled() i doInBackground() til at returnere true, hvilket kan bruges til at afslutte en løkke, hvis det er nødvendigt.
Det er også vigtigt at forstå, at AsyncTask ikke er den eneste måde at køre baggrundsoperationer på Android. Der findes andre metoder som f.eks. Handler, Runnable, og ExecutorService, der kan være bedre til mere komplekse opgaver, især når du har brug for flere baggrundsoperationer eller mere kontrol over tråde.
Endtext

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский