28/10/2022
I den moderne app-udvikling er billeder en uundværlig del af brugeroplevelsen. Uanset om det er app-ikoner, baggrundsbilleder, brugerfotos eller dynamisk indhold hentet fra internettet, spiller billeder en central rolle i at gøre apps visuelt tiltalende og funktionelle. Men hvordan håndterer man egentlig billeder i en Android-app? Denne omfattende guide vil tage dig gennem processen med at gemme, hente og vise billeder på Android-enheder, og dække både traditionelle metoder og moderne tilgange i Jetpack Compose. Vi vil udforske forskellige lagringsmuligheder, håndtering af filtilladelser og de bedste praksisser for at sikre, at dine billeder vises korrekt og effektivt på tværs af et væld af Android-enheder.

At forstå, hvor og hvordan man lagrer billeder, er afgørende for både ydeevne og brugersikkerhed. Android tilbyder flere lagringsmetoder, hver med sine egne fordele og ulemper. Valget af lagringsmetode afhænger ofte af billedets art, dets tilgængelighedskrav og den ønskede brugeroplevelse. Ligeledes er det vigtigt at vide, hvordan man integrerer disse billeder i din apps brugergrænseflade, især med de nye paradigmer som Jetpack Compose, der ændrer måden, vi bygger UI på.
Hvor Gemmer Man Bitmap-billeder i Android?
Når din Android-app skal gemme et billede, er der primært to steder, det kan ske: internt eller eksternt. Valget afhænger af, om billedet skal være privat for din app, eller om det skal være tilgængeligt for andre apps og brugeren direkte.
Intern Lagring: Den Sikre Hjemmebane
Intern lagring refererer til den del af enhedens hukommelse, der er dedikeret til individuelle apps. Hver app har sin egen sandkasse i intern lagring, hvilket betyder, at filer gemt her er private for den app, der oprettede dem, og andre apps kan ikke få adgang til dem uden specifikke tilladelser. Dette er den mest sikre måde at gemme følsomme data på.
For at gemme et Bitmap-billede i intern lagring, kan du bruge metoder som context.openFileOutput(). Denne metode returnerer en FileOutputStream, som du kan skrive billeddata til. Når du kalder openFileOutput(), angiver du filnavnet og en tilstand, typisk Context.MODE_PRIVATE, hvilket sikrer, at filen kun kan tilgås af din app. Efter at have åbnet filstrømmen, kan du komprimere dit Bitmap-objekt til strømmen ved hjælp af Bitmap.compress(). Denne metode giver dig mulighed for at vælge komprimeringsformat (f.eks. PNG eller JPEG) og kvaliteten. Det er vigtigt at lukke filstrømmen efter skrivning for at frigive ressourcer.
Fordelene ved intern lagring er sikkerhed og enkelhed, da du ikke behøver at anmode om særlige runtime-tilladelser fra brugeren. Ulempen er, at filerne slettes, hvis brugeren afinstallerer din app, og de er ikke direkte tilgængelige for brugeren via en filhåndtering.
Hentning og Lagring af Billeder fra Internettet
Ofte skal billeder hentes fra internettet, før de kan gemmes lokalt. I Android har dette traditionelt været håndteret asynkront for at undgå at blokere hovedtråden (UI-tråden), hvilket ville føre til en frossen brugergrænseflade og potentielle 'Application Not Responding' (ANR) fejl. En almindelig måde at gøre dette på har været ved brug af AsyncTask.
En AsyncTask er en hjælpeklasse, der giver dig mulighed for at udføre baggrundsoperationer og offentliggøre resultater på UI-tråden uden at skulle manipulere tråde og håndtere. Typisk vil du i doInBackground() metoden oprette en URL fra billedets webadresse, åbne en forbindelse (f.eks. med HttpURLConnection) og hente billeddata som en InputStream. Derefter kan BitmapFactory.decodeStream() bruges til at konvertere inputstrømmen direkte til et Bitmap-objekt.

Efter at billedet er downloadet i doInBackground(), vil onPostExecute() metoden blive kaldt på UI-tråden, hvor du kan modtage det downloadede Bitmap. Herfra kan du kalde den tidligere nævnte saveImage-metode for at gemme billedet i intern lagring. Selvom AsyncTask stadig fungerer, er det værd at bemærke, at nyere Android-udvikling ofte foretrækker Kotlin Coroutines eller WorkManager for asynkrone opgaver, da de tilbyder mere fleksible og robuste løsninger.
Læsning af Billeder fra Intern Lagring
Når et billede er gemt i intern lagring, skal du naturligvis også kunne læse det ind igen. Dette gøres ved hjælp af context.openFileInput(imageName), som fungerer analogt med openFileOutput(), men åbner filen for læsning. Den returnerer en FileInputStream, som igen kan bruges med BitmapFactory.decodeStream() for at genskabe Bitmap-objektet. Husk at lukke inputstrømmen efter brug.
Udover at indlæse billedet direkte, kan du også få adgang til filens fulde sti ved hjælp af getApplicationContext().getFileStreamPath(imageName).getAbsolutePath(). Du kan også tjekke, om en fil eksisterer med file.exists() og slette den med file.delete(), hvilket er nyttigt for at administrere appens lagrede data.
Ekstern Lagring: Adgang til Brugerens Filer
Ekstern lagring refererer til den del af hukommelsen, der er tilgængelig for alle apps og brugeren, ofte et SD-kort eller en partition på den interne hukommelse, der emulerer et SD-kort. Dette er det rette sted at gemme billeder, som brugeren forventer at kunne se i galleriet, eller som skal deles med andre apps.
Lagring på ekstern lagring er mere kompleks på grund af Androids udvikling inden for filhåndtering og tilladelser. Før Android 10 (API Level 29) skulle du anmode om WRITE_EXTERNAL_STORAGE-tilladelsen i din AndroidManifest.xml og også runtime-tilladelsen fra brugeren. Hvis tilladelsen blev givet, kunne du gemme billeder i offentlige mapper som Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), hvilket gjorde dem synlige i galleriet.
Fra Android 10 (API Level 29) og fremefter introducerede Google Scoped Storage. Dette er en betydelig ændring, der begrænser apps' direkte adgang til ekstern lagring, og i stedet opfordrer til brug af MediaStore API'en for at få adgang til mediefiler som billeder, videoer og lyd. Dette øger brugernes privatliv og systemets sikkerhed. For at gemme billeder i et offentligt galleri under Scoped Storage skal du bruge ContentResolver og MediaStore.Images.Media.EXTERNAL_CONTENT_URI. Du opretter en ny post i MediaStore med filinformation via ContentValues, og derefter får du en Uri, som du kan åbne en outputstrøm til for at skrive billeddataene.
For at lette overgangen til Scoped Storage kan apps, der målretter API Level 29 eller højere, midlertidigt fravælge Scoped Storage ved at tilføje android:requestLegacyExternalStorage="true" i deres application-tag i AndroidManifest.xml. Dette er dog kun en midlertidig løsning og anbefales ikke på lang sigt.

Håndtering af tilladelser, især runtime-tilladelser, er også blevet mere strømlinet med ActivityResultLauncher. I stedet for at overskrive onRequestPermissionResult, kan du registrere en ActivityResultLauncher, der håndterer resultatet af tilladelsesanmodningen, hvilket gør din kode renere og mere robust.
| Funktion | Intern Lagring | Ekstern Lagring (Før API 29) | Ekstern Lagring (API 29+) |
|---|---|---|---|
| Adgangsniveau | Privat for app | Offentlig, tilgængelig for alle apps/bruger | Semi-privat (Scoped Storage), adgang via MediaStore |
| Tilladelser | Ingen nødvendige | WRITE_EXTERNAL_STORAGE (runtime) | Ingen direkte skrivetilladelse; MediaStore API |
| Filsti | /data/data/your.app.packagename/files/ | /sdcard/DCIM/ eller lignende | Håndteres af MediaStore Content URI'er |
| Sletning ved afinstallation | Ja | Nej | Nej (hvis gemt via MediaStore) |
| Bedst til | App-specifikke data | Brugergenererede medier, deling | Brugergenererede medier, deling (anbefalet) |
Hvordan Bruger Man Drawable Billeder i Android Studio?
Når du har gemt dine billeder lokalt eller har statiske billeder, der er en del af din app's ressourcer, skal du vide, hvordan du viser dem i din app. Androids ressourcessystem og Jetpack Compose tilbyder effektive måder at gøre dette på.
Introduktion til Drawables som Ressourcer
I Android gemmes de fleste visuelle ressourcer, såsom billeder, ikoner og former, i res/drawable-mappen i dit projekt. Disse kaldes 'drawables' og er en central del af Androids ressourcessystem, der hjælper med at adskille design fra kode og understøtte forskellige enhedskonfigurationer (f.eks. skærmstørrelser og densiteter).
Import af Billeder til Dit Projekt
Den nemmeste måde at importere billeder til dit Android Studio-projekt er via Resource Manager. Du kan finde Resource Manager under 'View > Tool Windows > Resource Manager'. Herfra kan du klikke på '+' og vælge 'Import Drawables'. Når du importerer, er det vigtigt at overveje billedets densitet.
Android-enheder har forskellige skærmdensiteter (målt i DPI – dots per inch). Hvis du ikke tager højde for dette, kan systemet skalere dine billeder, hvilket kan føre til sløring eller forkert størrelse. For baggrundsbilleder eller fotos, der skal dække hele skærmen uden skalering, er den anbefalede praksis at placere dem i mappen drawable-nodpi. Dette fortæller Android, at billedet ikke skal skaleres baseret på skærmdensiteten, hvilket er ideelt for store baggrundsbilleder, hvor du selv styrer skaleringen i din UI-kode.
Visning af Billeder med Jetpack Compose
Jetpack Compose er Googles moderne værktøjssæt til at bygge native Android UI. I Compose bruges en Image komponerbar funktion til at vise billeder. For at indlæse et billede fra dine drawable-ressourcer, bruger du funktionen painterResource(), der tager ressource-ID'et (f.eks. R.drawable.androidparty) som argument. Den returnerer et Painter-objekt, som Image komponerbar kan bruge.
Et vigtigt aspekt ved Image komponerbar er contentDescription-parameteren. Denne parameter bruges til at give en tekstbeskrivelse af billedet for tilgængelighedsformål, især for skærmlæsere som TalkBack. Hvis billedet udelukkende er dekorativt og ikke tilføjer meningsfuld information for brugeren, kan du sætte contentDescription til null, så skærmlæseren ignorerer det.

Layout og Positionering af Billeder
Compose tilbyder en række layout-komponeringer til at arrangere dine UI-elementer. For at stable elementer oven på hinanden, som et billede og tekst ovenpå, bruger du en Box komponerbar. Elementer placeret inden i en Box kan overlappe, og du kan styre deres individuelle positionering og justering.
Når du viser et billede, især et baggrundsbillede, vil du ofte have det til at fylde hele skærmen eller en specifik del af den. Dette kan opnås ved hjælp af Modifiers, som er kraftfulde værktøjer til at dekorere eller tilføje adfærd til dine UI-elementer. For eksempel vil Modifier.fillMaxSize() få billedet til at fylde hele tilgængelige plads. For at kontrollere, hvordan billedet skaleres inden for den tildelte plads, bruger du contentScale-parameteren i Image komponerbar. ContentScale.Crop er en almindelig valgmulighed, der skalerer billedet ensartet, så det fylder pladsen og beskærer eventuelle overskydende dele, hvilket er ideelt for baggrundsbilleder. Du kan også justere billedets gennemsigtighed med alpha-parameteren (f.eks. alpha = 0.5F for 50% gennemsigtighed), hvilket kan forbedre kontrasten til tekst, der ligger ovenpå.
Andre Modifiers, som Modifier.padding(), bruges til at tilføje mellemrum omkring elementer, mens Modifier.align() kan bruges til at justere et enkelt element inden for dets forælder-layout (f.eks. Alignment.CenterHorizontally for at centrere tekst vandret). Layout-komponeringer som Column og Row har også parametre som verticalArrangement og horizontalAlignment til at styre, hvordan deres underordnede elementer arrangeres, hvilket giver dig fuld kontrol over dit UI-design.
Bedste Praksis: Ressourcer og Lokalisering
En fundamental bedste praksis i Android-udvikling er at adskille dine ressourcer (som billeder og tekst) fra din kode. Dette gør din app nemmere at vedligeholde, genbruge og lokalisere. For tekststrenge betyder dette, at du skal definere dem i strings.xml-filen i mappen res/values. I stedet for at hardcode strenge direkte i din kode (f.eks. "Happy Birthday Sam!"), bør du referere til dem via deres ressource-ID (f.eks. R.string.happy_birthday_text). I Jetpack Compose kan du indlæse disse strenge ved hjælp af stringResource()-funktionen.
Fordelen ved at bruge strings.xml er, at det gør det utrolig nemt at oversætte din app til forskellige sprog. Du kan blot oprette separate strings.xml-filer for hvert sprog (f.eks. values-da/strings.xml for dansk), og Android vil automatisk indlæse den korrekte streng baseret på brugerens sprogindstillinger. Dette er afgørende for at skabe en app, der kan bruges globalt.
Ofte Stillede Spørgsmål (FAQ)
- Hvad er forskellen på intern og ekstern lagring til billeder?
- Intern lagring er privat for din app; filer slettes ved afinstallation og kan ikke tilgås af andre apps eller brugeren direkte. Ekstern lagring er offentlig og tilgængelig for alle apps og brugeren, ideel til medier som fotos, der skal vises i galleriet eller deles. Med Android 10 (API 29) og Scoped Storage er adgangen til ekstern lagring blevet mere restriktiv og foregår primært via MediaStore API'en for at forbedre privatlivets fred.
- Hvorfor skal jeg bruge
drawable-nodpitil baggrundsbilleder? - Mappen
drawable-nodpibruges til billeder, der ikke skal skaleres baseret på enhedens skærmdensitet. Dette er især nyttigt for store baggrundsbilleder, hvor du ønsker at bevare billedets originale størrelse og forhindre Android i automatisk at skalere det, hvilket kan føre til tab af kvalitet eller uønskede effekter på forskellige skærmstørrelser. Du styrer selv skaleringen viaContentScalei din UI-kode. - Hvad er Scoped Storage, og hvordan påvirker det billedlagring?
- Scoped Storage er en sikkerhedsforanstaltning introduceret i Android 10 (API 29), der begrænser apps' direkte adgang til ekstern lagring. Apps får kun adgang til deres egne app-specifikke mapper og delte mediefiler (billeder, videoer, lyd) via MediaStore API'en. Dette øger brugernes privatliv og sikkerhed. Det betyder, at du ikke længere kan gemme billeder frit i offentlige mapper på samme måde som før API 29; du skal bruge
ContentResolverogMediaStoretil at indsætte og manipulere mediefiler. - Hvordan håndterer jeg billeder for forskellige skærmstørrelser og densiteter?
- For ikoner og små billeder kan du levere forskellige versioner i
drawable-mdpi,drawable-hdpi,drawable-xhdpi, osv. Android vælger automatisk den korrekte version baseret på enhedens densitet. For store baggrundsbilleder anbefalesdrawable-nodpikombineret medContentScalei din UI-kode (f.eks.ContentScale.Cropfor at fylde skærmen) for at sikre korrekt visning på tværs af forskellige skærmstørrelser og -densiteter. - Er
AsyncTaskstadig den anbefalede måde at downloade billeder på? - Nej,
AsyncTasker forældet og anbefales ikke længere til nye projekter. Selvom det stadig fungerer, er det bedre at bruge moderne alternativer som Kotlin Coroutines (med suspend-funktioner ogViewModelScope) eller WorkManager til længerevarende, planlagte opgaver. Disse løsninger tilbyder bedre håndtering af livscyklus, fejl og annullering. - Hvad er formålet med
contentDescriptioniImagekomponerbar? contentDescriptioner en vigtig parameter for tilgængelighed. Den giver en tekstbeskrivelse af billedets indhold, som bruges af skærmlæsere (f.eks. TalkBack) til at beskrive billedet for brugere med synshandicap. Hvis billedet er rent dekorativt og ikke tilføjer meningsfuld information, kan du sætte det tilnullfor at instruere skærmlæseren om at ignorere det.
At mestre billedhåndtering i Android er en afgørende færdighed for enhver app-udvikler. Ved at forstå forskellen mellem intern og ekstern lagring, håndtere filtilladelser korrekt, især med introduktionen af Scoped Storage, og udnytte Androids ressourcessystem og moderne UI-værktøjer som Jetpack Compose, kan du sikre, at dine apps ikke kun ser fantastiske ud, men også fungerer effektivt og sikkert på tværs af et bredt spektrum af enheder. Husk altid at prioritere ydeevne og tilgængelighed, når du arbejder med billeder, da dette vil bidrage til en overlegen brugeroplevelse.
Hvis du vil læse andre artikler, der ligner Guide: Billedhåndtering på Android-enheder, kan du besøge kategorien Mobil.
