BluetoothLeScanner — vad är det, metoder och BLE-skanning i Android

Författare: IT Sectr Publicerad: 2026-07-16 Lästid: 10 min

BluetoothLeScanner — Android-klassen för att skanna Bluetooth Low Energy-enheter, tillgänglig från API 21 (Android 5.0). BluetoothLeScanner ersatte den föråldrade metoden startLeScan på BluetoothAdapter och ger ett flexibelt API med skanningskonfiguration (ScanSettings), filtrering (ScanFilter) och stöd för bakgrundsläge (PendingIntent). Instansen erhålls via BluetoothAdapter.getBluetoothLeScanner(). Enligt Android Developers, 2026 stöder BluetoothLeScanner tre energiförbrukningslägen och tillåter skanning av BLE-reklampaket med filtrering efter tjänste-UUID, enhetsnamn eller MAC-adress.

Huvudpunkter

  • BluetoothLeScanner — modernt Android API (API 21+) för BLE-skanning, ersättning för föråldrade startLeScan
  • ScanSettings — konfiguration av skanningsläge: LOW_POWER, BALANCED, LOW_LATENCY och callback-typ
  • ScanFilter — filtrering av resultat efter tjänste-UUID, enhetsnamn, MAC-adress, tillverkardata
  • ScanCallback — callback av resultat onScanResult, onBatchScanResults och onScanFailed med felkoder
  • PendingIntent — bakgrundsskanning via BroadcastReceiver, även när appen är i bakgrunden

Vad är BluetoothLeScanner: essens och erhållande av instans

BluetoothLeScanner — systemklass för hantering av BLE-skanning på Android. Till skillnad från BluetoothAdapter.startLeScan(), som accepterar en enkel LeScanCallback, tillhandahåller BluetoothLeScanner ett objektorienterat API med inställningar, filter och utökad felhantering. Klassen dök upp i API 21 (Android 5.0) tillsammans med stöd för BLE 4.2 och är fortfarande den huvudsakliga metoden för BLE-skanning på alla moderna Android-versioner.

Erhållande av BluetoothLeScanner-instans görs via BluetoothAdapter.getBluetoothLeScanner(). Metoden returnerar null om Bluetooth-adaptern inte är tillgänglig (Bluetooth avaktiverat eller enheten stöder inte BLE). Före erhållande, kontrollera BluetoothAdapter.isEnabled() och förekomsten av FEATURE_BLUETOOTH_LE via PackageManager. Efter att skannern har erhållits kan du starta skanning i vilken tråd som helst — Android schemalägger själv BLE-operationer på den interna tråden i Bluetooth-stacken.

kotlin
// Hämta BluetoothLeScanner
class BLEScannerManager(context: Context) {

    private val bluetoothManager: BluetoothManager =
        context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    private val adapter: BluetoothAdapter? = bluetoothManager.adapter
    private var scanner: BluetoothLeScanner? = null

    fun initScanner(): Boolean {
        // Kontrollera BLE-tillgänglighet
        if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            return false
        }

        // Kontrollera att Bluetooth är aktiverat
        if (adapter?.isEnabled != true) {
            return false
        }

        // Hämta skanner
        scanner = adapter?.bluetoothLeScanner
        return scanner != null
    }

    // Kontrollera skannertillgänglighet
    val isAvailable: Boolean
        get() = scanner != null

    // Starta grundskanning utan filter
    fun startBasicScan() {
        scanner?.startScan(object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                handleResult(result)
            }
        })
    }

    private fun handleResult(result: ScanResult) {
        val device = result.device
        print("Device: ${device.name ?: "Unnamed"}, RSSI: ${result.rssi}, address: ${device.address}")
    }
}

Klassen BLEScannerManager demonstrerar säkert erhållande och initiering av BluetoothLeScanner. initScanner kontrollerar förekomsten av BLE via hasSystemFeature, aktiverad Bluetooth och framgångsrikt erhållande av skannern. startBasicScan startar skanning utan inställningar och filter — upptäcker alla BLE-enheter inom räckvidd. handleResult analyserar ScanResult: BluetoothDevice (namn, adress), RSSI (signalnivå), scanRecord (reklamdata).

ScanSettings: skanningslägen och callback-typ

ScanSettings — klass för konfiguration av BLE-skanning. Huvudparametern är skanningsläget (scanMode), som bestämmer kompromissen mellan energiförbrukning och detekteringsfördröjning. ScanSettings.Builder möjliggör konfiguration av: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (fördröjning för batchesändning) och phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).

Tre skanningslägen: SCAN_MODE_LOW_POWER (0) — bakgrundsskanning med låg energiförbrukning, detekteringsfördröjning flera sekunder. SCAN_MODE_BALANCED (1) — balanserat läge för de flesta scenarier. SCAN_MODE_LOW_LATENCY (2) — minimal detekteringsfördröjning (cirka 100 ms), maximal energiförbrukning. För aktiv sökning av enheter använd LOW_LATENCY, för bakgrundsövervakning — LOW_POWER.

reportDelay — fördröjning i millisekunder före gruppsändning av resultat. Om reportDelay = 0 skickas resultat omedelbart efter detektering. Om > 0 ackumulerar Android resultaten och skickar en batch via onBatchScanResults. Batchesändning minskar antalet callback-anrop och minskar energiförbrukningen, lämplig för bakgrundsskanning med låg prioritet.

kotlin
// ScanSettings-konfiguration för olika scenarier
class ScanSettingsProvider {

    // 1. Snabb skanning (aktiv sökning)
    fun lowLatencyScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
            .setReportDelay(0)
            .setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
            .build()
    }

    // 2. Energieffektiv skanning (bakgrundsövervakning)
    fun lowPowerScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
            .setReportDelay(2000)  // batch varannan sekund
            .build()
    }

    // 3. BLE Long Range-skanning (Coded PHY)
    fun longRangeScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setPhy(ScanSettings.PHY_LE_CODED)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .build()
    }

    // 4. Skanning endast på 2M PHY (BLE 5.0+)
    fun highSpeedScan(): ScanSettings {
        return ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setPhy(ScanSettings.PHY_LE_2M)
            .build()
    }
}

Klassen ScanSettingsProvider innehåller typiska konfigurationer. lowLatencyScan — för UI-skanning (sökning "här och nu"). lowPowerScan — för bakgrundsövervakning med batch var 2:e sekund och callbackType FIRST_MATCH (aktiveras endast vid första detektering). longRangeScan använder PHY_LE_CODED (BLE Long Range, upp till 1 km). highSpeedScan — PHY_LE_2M (2 Mbit/s, endast BLE 5.0+-enheter).

ScanFilter: filtrering av BLE-enheter efter UUID och namn

ScanFilter — klass för filtrering av BLE-skanningsresultat. Utan filter returnerar BluetoothLeScanner alla BLE-enheter inom räckvidd — i en tät BLE-miljö kan det vara hundratals paket per minut. ScanFilter begränsar resultaten till nödvändiga enheter, vilket minskar energiförbrukning och appbelastning. Filter tillämpas på Bluetooth-stacknivå — olämpliga paket avvisas innan de levereras till appen.

Filtertyper: setServiceUuid — tjänste-UUID (obligatoriskt fullt 128-bitars format). setDeviceName — delsträng av enhetsnamn (skiftlägeskänslig, exakt delsträngsöverensstämmelse). setDeviceAddress — exakt MAC-adress. setManufacturerData — tillverkardata (företags-ID + mask). För en skanning kan flera filter ställas in — enheten måste matcha alla (AND-logik). För OR-logik, starta flera skanningar.

kotlin
// Skapa ScanFilter för olika scenarier
class ScanFilterFactory {

    // 1. Filtrera efter tjänste-UUID (Heart Rate Monitor)
    fun byHeartRateService(): ScanFilter {
        return ScanFilter.Builder()
            .setServiceUuid(
                ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
            )
            .build()
    }

    // 2. Filtrera efter enhetsnamn ("iBeacon*")
    fun byDeviceName(): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceName("Sensor")
            .build()
    }

    // 3.   MAC- (enheter)
    fun byMacAddress(mac: String): ScanFilter {
        return ScanFilter.Builder()
            .setDeviceAddress(mac)
            .build()
    }

    // 4. Kombinerat filter (UUID + )
    fun combinedFilter(): List<ScanFilter> {
        return listOf(
            ScanFilter.Builder()
                .setServiceUuid(
                    ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
                )
                .setDeviceName("MyDevice")
                .build()
        )
    }

    // 5.   tillverkardata
    fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
        return ScanFilter.Builder()
            .setManufacturerData(companyId, data, mask)
            .build()
    }
}

Klassen ScanFilterFactory visar alla filtertyper. byHeartRateService filtrerar enheter med pulstjänst 0x180D. byDeviceName hittar enheter som innehåller "Sensor" i namnet (Apple rekommenderar unika namn för filtrering). byMacAddress — exakt sökning av en specifik enhet. combinedFilter — AND-filter efter UUID och namn. byManufacturer — filter efter tillverkardata (t.ex. för iBeacon används company ID Apple 0x004C).

ScanCallback: bearbetning av skanningsresultat och fel

ScanCallback — abstrakt klass för att ta emot BLE-skanningsresultat. Innehåller tre metoder: onScanResult — enskilt resultat (callback-typ, ScanResult), onBatchScanResults — batch av resultat för reportDelay > 0, onScanFailed — felkod. Alla metoder anropas på Android-huvudtråden (main thread). För långvarig bearbetning i onScanResult, använd coroutines eller HandlerThread.

ScanResult innehåller: BluetoothDevice device (enhet), int rssi (signalnivå i dBm), ScanRecord scanRecord (reklamdata), long timestampNanos (detekteringstid sedan systemstart). ScanRecord tillhandahåller: getServiceData() — UUID + anpassad data, getManufacturerSpecificData() — tillverkardata, getAdvertiseFlags() — BLE-flaggor. Callback-typen (callbackType) indikerar: CALLBACK_TYPE_ALL_MATCHES — matchning med filter, CALLBACK_TYPE_FIRST_MATCH — första detektering, CALLBACK_TYPE_MATCH_LOST — förlust av enhet.

Felkoder för onScanFailed: SCAN_FAILED_ALREADY_STARTED (1) — skanning har redan startats, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — registrering av appen i Bluetooth-stacken misslyckades, SCAN_FAILED_INTERNAL_ERROR (3) — internt stackfel, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE-skanning stöds inte på enheten.

kotlin
// Fullständiga skanningsresultat och felhantering
class ScanResultHandler {

    private val results = mutableListOf<ScanResult>()

    val scanCallback = object : ScanCallback() {

        // 1. Enskilt resultat
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            // callbackType: 1 = ALL_MATCHES, 2 = FIRST_MATCH, 4 = MATCH_LOST
            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
                onDeviceLost(result)
                return
            }

            // Lägg till i listan (deduplicering efter adress)
            val existingIndex = results.indexOfFirst {
                it.device.address == result.device.address
            }
            if (existingIndex >= 0) {
                results[existingIndex] = result  // uppdatera RSSI
            } else {
                results.add(result)
            }

            // Extrahera data från reklampaket
            val record = result.scanRecord
            val serviceData = record?.serviceData
            val manufacturerData = record?.manufacturerSpecificData

            print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
        }

        // 2. Batchresultat (reportDelay > 0)
        override fun onBatchScanResults(results: MutableList<ScanResult>?) {
            results?.let { batch ->
                print("Batch: ${batch.size} devices")
            }
        }

        // 3. Skanningsfel
        override fun onScanFailed(errorCode: Int) {
            val error = when (errorCode) {
                ScanCallback.SCAN_FAILED_ALREADY_STARTED -> "Already scanning"
                ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "Registration failed"
                ScanCallback.SCAN_FAILED_INTERNAL_ERROR -> "Internal error"
                ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED -> "BLE not supported"
                else -> "Unknown error: $errorCode"
            }
            print("Error: $error")
        }
    }

    private fun onDeviceLost(result: ScanResult) {
        results.removeAll { it.device.address == result.device.address }
        print("Device lost: ${result.device.address}")
    }
}

Klassen ScanResultHandler bearbetar alla callback-typer av BluetoothLeScanner. onScanResult uppdaterar enhetslistan med deduplicering efter MAC-adress — RSSI uppdateras för redan hittade enheter. CALLBACK_TYPE_MATCH_LOST signalerar förlust av enhet (borttagning från lista). onBatchScanResults bearbetar batchresultat för reportDelay > 0. onScanFailed mappar felkoder till läsbara meddelanden — viktigt för felsökning av BLE-skanning.

PendingIntent: BLE-bakgrundsskanning via BroadcastReceiver

PendingIntent-skanning — BluetoothLeScanners mekanism för BLE-skanning som fungerar även när appen är i bakgrunden (med begränsningar från Android 8+). Istället för ScanCallback används PendingIntent som skickar en Broadcast till systemets BroadcastReceiver vid detektering av en BLE-enhet. Detta gör att appen kan ta emot meddelanden om BLE-enheter utan att vara i minnet (systemet skapar en process när broadcast tas emot).

Begränsningar för bakgrundsskanning: På Android 8+ (API 26) är bakgrundstjänster begränsade — PendingIntent-skanning kringgår denna begränsning via BroadcastReceiver, som systemet kan starta vid mottagning av en BLE-händelse. På Android 10+ (API 29) begränsas BLE-bakgrundsskanning ytterligare av tillverkares energisparpolicyer (Xiaomi, Huawei, Samsung blockerar BLE-bakgrundsoperationer). För kritiska BLE-scenarier krävs en notis med foreground service.

kotlin
// BLE-bakgrundsskanning via PendingIntent
class BackgroundBLEScanner(private val context: Context) {

    private val scanner: BluetoothLeScanner? by lazy {
        val adapter = BluetoothAdapter.getDefaultAdapter()
        adapter?.bluetoothLeScanner
    }

    fun startBackgroundScan() {
        // Skapa PendingIntent för BroadcastReceiver
        val intent = Intent(context, BLEBroadcastReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // Bakgrundsskanningsinställningar
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
            .build()

        // Starta bakgrundsskanning
        scanner?.startScan(
            null,  // filter
            settings,
            pendingIntent
        )
    }

    fun stopBackgroundScan() {
        val intent = Intent(context, BLEBroadcastReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        scanner?.stopScan(pendingIntent)
    }
}

// BroadcastReceiver   BLE-
class BLEBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // Hämta skanningsresultat
        val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
        results?.let { scanResults ->
            for (result in scanResults) {
                // Skicka notis till användare
                showNotification(context, result.device.name ?: " ")
            }
        }
    }

    private fun showNotification(context: Context, name: String) {
        val notification = Notification.Builder(context, "ble_channel")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentTitle("BLE devices")
            .setContentText("Found: $name")
            .setAutoCancel(true)
            .build()
        val manager = context.getSystemService(Context.NOTIFICATION_SERVICE)
                as NotificationManager
        manager.notify(System.currentTimeMillis().toInt(), notification)
    }
}

Klassen BackgroundBLEScanner startar BLE-bakgrundsskanning via PendingIntent. startBackgroundScan skapar en PendingIntent som vid detektering av en BLE-enhet skickar en Broadcast till BLEBroadcastReceiver. BroadcastReceiver extraherar ScanResult via getPendingIntentScanResults() och kan visa en notis eller skicka data till servern. Detta tillvägagångssätt fungerar även om appen har avslutats av systemet — Android startar BroadcastReceiver vid mottagning av broadcast.

Exempel på BLE-skanner i Kotlin med BluetoothLeScanner

Fullständigt exempel på en BLE-skanner i Kotlin, som använder BluetoothLeScanner med ScanSettings, ScanFilter och ScanCallback för att hitta Heart Rate Monitor-enheter. Skannern visar en lista över hittade enheter med RSSI och tjänste-UUID, med möjlighet att ansluta via BluetoothGatt.

kotlin
// Fullständig BLE-skanner med coroutines i Kotlin
class DeviceScanner(private val context: Context) {

    private val adapter: BluetoothAdapter? by lazy {
        val manager = context.getSystemService(Context.BLUETOOTH_SERVICE)
                as BluetoothManager
        manager.adapter
    }

    private val scanner: BluetoothLeScanner? by lazy {
        adapter?.bluetoothLeScanner
    }

    fun startScan(duration: Long = 10000): Flow<ScanResult> = callbackFlow {
        // Kontrollera Bluetooth-status
        if (adapter?.isEnabled != true) {
            close(IllegalStateException("Bluetooth disabled"))
            return@callbackFlow
        }

        // Skanningskonfiguration
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()

        val filters = listOf(
            ScanFilter.Builder()
                .setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
                .build()
        )

        val callback = object : ScanCallback() {
            override fun onScanResult(callbackType: Int, result: ScanResult) {
                trySend(result)
            }

            override fun onScanFailed(errorCode: Int) {
                close(BLEException("Scan failed: $errorCode"))
            }
        }

        // Starta skanning
        scanner?.startScan(filters, settings, callback)

        // Automatisk stopp efter varaktighet
        delay(duration)
        scanner?.stopScan(callback)
        close()
    }.flowOn(Dispatchers.IO)

    fun stop() {
        scanner?.stopScan(object : ScanCallback() {})
    }
}

class BLEException(message: String) : Exception(message)

Klassen DeviceScanner använder Kotlin Flow (callbackFlow) för reaktiv BLE-skanning. Skanningen startas med LOW_LATENCY-inställningar och filter efter Heart Rate Service-UUID. Resultat skickas via onScanResult till Flow. Automatisk stopp efter angiven varaktighet (10 sekunder som standard). FlowOn(Dispatchers.IO) flyttar BLE-operationer till bakgrundstråden. Detta tillvägagångssätt möjliggör användning av BLE-skanning i MVVM-arkitektur via viewModelScope.launch och collect.

Vanliga frågor

Vad är BluetoothLeScanner i Android?

BluetoothLeScanner — Android-klass (API 21+) för BLE-skanning. Erhålls via BluetoothAdapter.getBluetoothLeScanner(). Stöder tre skanningslägen (LOW_POWER, BALANCED, LOW_LATENCY), filtrering efter UUID, namn och MAC-adress, batchresultat och PendingIntent för bakgrundsskanning. Ersätter den föråldrade metoden BluetoothAdapter.startLeScan().

Vad är skillnaden mellan LOW_POWER och LOW_LATENCY?

SCAN_MODE_LOW_POWER — bakgrundsläge med detekteringsfördröjning på 5–10 sekunder, minimal energiförbrukning. SCAN_MODE_LOW_LATENCY — aktivt läge med fördröjning på cirka 100 ms, maximal energiförbrukning. SCAN_MODE_BALANCED — kompromiss (~2 sekunders fördröjning). För UI-skanning använd LOW_LATENCY, för bakgrundsövervakning — LOW_POWER med PendingIntent.

Varför hittar inte BluetoothLeScanner enheter?

Orsaker: Bluetooth avaktiverat (kontrollera adapter.isEnabled), behörigheter inte beviljade (BLUETOOTH_SCAN på API 31+, ACCESS_FINE_LOCATION på API 23–30), scanner = null (adapter inte tillgänglig), enhet utom räckvidd eller felaktigt filter använt. Kontrollera även onScanFailed — felkoden anger orsaken: SCAN_FAILED_ALREADY_STARTED (1) eller SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).

Hur skannar man BLE i bakgrunden på Android?

Använd PendingIntent-versionen av startScan() — skicka PendingIntent istället för ScanCallback. Vid detektering av en BLE-enhet skickar Android en Broadcast till BroadcastReceiver, som kan startas av systemet även om appen är i bakgrunden. För Android 8+, lägg till BroadcastReceiver i manifestet. På Android 10+, ta hänsyn till tillverkares energisparbegränsningar.

Hur många BLE-enheter kan upptäckas i en skanning?

BluetoothLeScanner har ingen gräns för antalet upptäckbara enheter — begränsningen beror på BLE-mättnaden i miljön. På ett kontor kan det finnas 20–50 aktiva BLE-enheter, i ett köpcentrum hundratals. För filtrering, använd ScanFilter (efter UUID, namn). Utan filtrering, bearbeta resultat asynkront — onScanResult kan anropas tiotals gånger per sekund.

Sammanfattning

  • BluetoothLeScanner — modern Android-klass (API 21+) för BLE-skanning med inställningar och filtrering
  • ScanSettings — tre lägen: LOW_POWER (bakgrund), BALANCED (balanserad), LOW_LATENCY (aktiv)
  • ScanFilter — filtrering efter tjänste-UUID, enhetsnamn, MAC-adress, tillverkardata med AND-logik
  • ScanCallback — onScanResult (enskilda), onBatchScanResults (batch), onScanFailed (felkoder)
  • PendingIntent — bakgrundsskanning via BroadcastReceiver, fungerar med avslutad app
  • ScanRecord — BLE-reklamdata: serviceData, manufacturerSpecificData, advertiseFlags, TX power level
  • Kotlin Flow — callbackFlow möjliggör användning av BluetoothLeScanner i reaktiv arkitektur med automatisk stopp

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också