BluetoothLeScanner — Bluetooth Low Energy qurilmalarini skanerlash ücun Android sinfi, API 21 (Android 5.0) dan boshlab mavjud. BluetoothLeScanner BluetoothAdapter-dagi eskirgan startLeScan metodini almashtirib, moslashuvchan API ni taqdim etadi: skanerlash sozlamalari (ScanSettings), filtrlash (ScanFilter) va fon rejimini qo’llab-quvvatlash (PendingIntent). Namunasi BluetoothAdapter.getBluetoothLeScanner() orqali olinadi. Android Developers, 2026 ma’lumotlariga ko‘ra, BluetoothLeScanner üchta energiya sarfi rejimini qo’llab-quvvatlaydi va BLE reklama paketlarini xizmat UUID-si, qurilma nomi yoki MAC manzili bo‘yicha filtrlash bilan skanerlash imkonini beradi.
Asosiy
BluetoothLeScanner — Android-da BLE skanerlashni boshqarish ücun tizim sinfi. Oddiy LeScanCallback ni qabul qiluvchi BluetoothAdapter.startLeScan() dan farqli o‘laroq, BluetoothLeScanner sozlamalar, filtrlar va kengaytirilgan xato boshqaruvi bilan obyektga yo‘naltirilgan API ni taqdim etadi. Sinf API 21 (Android 5.0) da BLE 4.2 qo‘llab-quvvatlashi bilan birga paydo bo‘ldi va barcha zamonaviy Android versiyalarida BLE skanerlashning asosiy usuli bo‘lib qolmoqda.
BluetoothLeScanner namunasini olish BluetoothAdapter.getBluetoothLeScanner() orqali amalga oshiriladi. Bluetooth adapteri mavjud bo‘lmasa (Bluetooth o‘chirilgan yoki qurilma BLE ni qo‘llab-quvvatlamasa), metod null qaytaradi. Olishdan oldin BluetoothAdapter.isEnabled() va PackageManager orqali FEATURE_BLUETOOTH_LE mavjudligini tekshiring. Skaner olingandan so‘ng, istalgan thread-da skanerlashni boshlashingiz mumkin — Android BLE operatsiyalarini Bluetooth stackining ichki threadida rejalashtiradi.
// BluetoothLeScanner ni olish
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 {
// BLE mavjudligini tekshiring
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false
}
// Bluetooth yoqilganligini tekshiring
if (adapter?.isEnabled != true) {
return false
}
// Skanerni oling
scanner = adapter?.bluetoothLeScanner
return scanner != null
}
// Skaner mavjudligini tekshiring
val isAvailable: Boolean
get() = scanner != null
// Filtrlarsiz asosiy skanerni ishga tushiring
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}")
}
}
BLEScannerManager sinfi BluetoothLeScanner ni xavfsiz olish va ishga tushirishni namoyish etadi. initScanner BLE mavjudligini hasSystemFeature, Bluetooth yoqilganligini va skaner muvaffaqiyatli olinganligini tekshiradi. startBasicScan sozlamalar va filtrsiz skanerlashni boshlaydi — diapazondagi barcha BLE qurilmalarini aniqlaydi. handleResult ScanResult ni tahlil qiladi: BluetoothDevice (nom, manzil), RSSI (signal darajasi), scanRecord (reklama ma’lumotlari).
ScanSettings — BLE skanerlashni konfiguratsiya qilish ücun sinf. Asosiy parametr — energiya sarfi va aniqlash kechikishi o‘rtasidagi kelishuvni belgilaydigan skanerlash rejimi (scanMode). ScanSettings.Builder quyidagilarni sozlash imkonini beradi: scanMode, callbackType (CALLBACK_TYPE_ALL_MATCHES, CALLBACK_TYPE_FIRST_MATCH, CALLBACK_TYPE_MATCH_LOST), matchMode (MATCH_MODE_AGGRESSIVE, MATCH_MODE_STICKY), reportDelay (to‘plamli yuborish kechikishi) va phy (PHY_LE_1M, PHY_LE_2M, PHY_LE_CODED).
Üchta skanerlash rejimi: SCAN_MODE_LOW_POWER (0) — kam energiya sarfi bilan fon skanerlash, aniqlash kechikishi bir necha soniya. SCAN_MODE_BALANCED (1) — ko‘p stsenariylar ücun muvozanatlashgan rejim. SCAN_MODE_LOW_LATENCY (2) — minimal aniqlash kechikishi (taxminan 100 ms), maksimal energiya sarfi. Faol qurilma qidiruvi ücun LOW_LATENCY, fon monitoringi ücun LOW_POWER dan foydalaning.
reportDelay — natijalarni guruhli yuborishdan oldin millisekundlardagi kechikish. Agar reportDelay = 0 bo‘lsa, natijalar aniqlangandan so‘ng darhol yuboriladi. Agar > 0 bo‘lsa, Android natijalarni to‘playdi va onBatchScanResults orqali to‘plamni yuboradi. To‘plamli yuborish callback chaqiruvlari sonini kamaytiradi va energiya sarfini pasaytiradi, past prioritetli fon skanerlash ücun mos keladi.
// Turli stsenariylar ücun ScanSettings konfiguratsiyasi
class ScanSettingsProvider {
// 1. Tez skanerlash (faol qidiruv)
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. Energiya tejamkor skanerlash (fon monitoringi)
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) // har 2 soniyada to‘plam
.build()
}
// 3. BLE Long Range skanerlash (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. Faqat 2M PHY da skanerlash (BLE 5.0+)
fun highSpeedScan(): ScanSettings {
return ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setPhy(ScanSettings.PHY_LE_2M)
.build()
}
}
ScanSettingsProvider sinfi odatiy konfiguratsiyalarni o‘z ichiga oladi. lowLatencyScan — UI skanerlash ücun (‘‘bu yerda va hozir‘‘ qidiruvi). lowPowerScan — har 2 soniyada to‘plam va FIRST_MATCH callbackType bilan fon monitoringi ücun (faqat birinchi aniqlashda ishlaydi). longRangeScan PHY_LE_CODED (BLE Long Range, 1 km gacha) dan foydalanadi. highSpeedScan — PHY_LE_2M (2 Mbit/s, faqat BLE 5.0+ qurilmalari).
ScanFilter — BLE skanerlash natijalarini filtrlash ücun sinf. Filtrsiz BluetoothLeScanner diapazondagi barcha BLE qurilmalarini qaytaradi — zich BLE muhitida bu daqiqada yuzlab paketlarni tashkil qiladi. ScanFilter natijalarni kerakli qurilmalargacha toraytiradi, energiya sarfini va ilova yukini kamaytiradi. Filtrlar Bluetooth stack darajasida qo‘llaniladi — mos kelmaydigan paketlar ilovaga yetkazilishdan oldin rad etiladi.
Filtr turlari: setServiceUuid — xizmat UUID-si (majburiy to‘liq 128-bit format). setDeviceName — qurilma nomining pastki qatori (katta/kichik harf sezgirligi bilan, aniq pastki qator mosligi). setDeviceAddress — aniq MAC manzili. setManufacturerData — ishlab chiqaruvchi ma’lumotlari (kompaniya ID + maska). Bitta skanerlash ücun bir nechta filtr o‘rnatilishi mumkin — qurilma hammasiga mos kelishi kerak (AND mantiqi). OR mantiqi ücun bir nechta skanerlashni ishga tushiring.
// Turli stsenariylar ücun ScanFilter yaratish
class ScanFilterFactory {
// 1. Xizmat UUID si bo‘yicha filtr (Heart Rate Monitor)
fun byHeartRateService(): ScanFilter {
return ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB")
)
.build()
}
// 2. Qurilma nomi bo‘yicha filtr (“iBeacon*")
fun byDeviceName(): ScanFilter {
return ScanFilter.Builder()
.setDeviceName("Sensor")
.build()
}
// 3. MAC- (qurilmalar)
fun byMacAddress(mac: String): ScanFilter {
return ScanFilter.Builder()
.setDeviceAddress(mac)
.build()
}
// 4. Birlashtirilgan filtr (UUID + )
fun combinedFilter(): List<ScanFilter> {
return listOf(
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("0000A001-0000-1000-8000-00805F9B34FB")
)
.setDeviceName("MyDevice")
.build()
)
}
// 5. ishlab chiqaruvchi ma’lumotlari
fun byManufacturer(companyId: Int, data: ByteArray, mask: ByteArray): ScanFilter {
return ScanFilter.Builder()
.setManufacturerData(companyId, data, mask)
.build()
}
}
ScanFilterFactory sinfi barcha filtr turlarini ko‘rsatadi. byHeartRateService puls xizmati 0x180D ga ega qurilmalarni filtrlaydi. byDeviceName nomida “Sensor” bo‘lgan qurilmalarni topadi (Apple filtrlash ücun noyob nomlarni tavsiya qiladi). byMacAddress — aniq qurilmani qidirish. combinedFilter — UUID va nom bo‘yicha AND filtri. byManufacturer — ishlab chiqaruvchi ma’lumotlari bo‘yicha filtr (masalan, iBeacon ücun Apple company ID 0x004C ishlatiladi).
ScanCallback — BLE skanerlash natijalarini olish ücun abstrakt sinf. Uchta metodni o‘z ichiga oladi: onScanResult — yakka natija (callback turi, ScanResult), onBatchScanResults — reportDelay > 0 ücun natijalar to‘plami, onScanFailed — xato kodi. Barcha metodlar Android asosiy threadida (main thread) chaqiriladi. onScanResult da uzoq muddatli qayta ishlash ücun korutinlar yoki HandlerThread dan foydalaning.
ScanResult o‘z ichiga oladi: BluetoothDevice device (qurilma), int rssi (dBm da signal darajasi), ScanRecord scanRecord (reklama ma’lumotlari), long timestampNanos (tizim yuklangandan boshlab aniqlash vaqti). ScanRecord taqdim etadi: getServiceData() — UUID + maxsus ma’lumotlar, getManufacturerSpecificData() — ishlab chiqaruvchi ma’lumotlari, getAdvertiseFlags() — BLE bayroqlari. Callback turi (callbackType) ko‘rsatadi: CALLBACK_TYPE_ALL_MATCHES — filtrga moslik, CALLBACK_TYPE_FIRST_MATCH — birinchi aniqlash, CALLBACK_TYPE_MATCH_LOST — qurilmani yo‘qotish.
onScanFailed xato kodlari: SCAN_FAILED_ALREADY_STARTED (1) — skanerlash allaqachon boshlangan, SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2) — ilovani Bluetooth stack-da ro‘yxatdan o‘tkazish muvaffaqiyatsiz tugadi, SCAN_FAILED_INTERNAL_ERROR (3) — stack ning ichki xatosi, SCAN_FAILED_FEATURE_UNSUPPORTED (4) — BLE skanerlash qurilmada qo‘llab-quvvatlanmaydi.
// To‘liq skanerlash natijalari va xato boshqaruvi
class ScanResultHandler {
private val results = mutableListOf<ScanResult>()
val scanCallback = object : ScanCallback() {
// 1. Yakka natija
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
}
// Ro‘yxatga qo‘shish (manzil bo‘yicha deduplikatsiya)
val existingIndex = results.indexOfFirst {
it.device.address == result.device.address
}
if (existingIndex >= 0) {
results[existingIndex] = result // RSSI ni yangilash
} else {
results.add(result)
}
// Reklama paketidan ma’lumotlarni chiqarish
val record = result.scanRecord
val serviceData = record?.serviceData
val manufacturerData = record?.manufacturerSpecificData
print("Found: ${result.device.name ?: "N/A"}, RSSI: ${result.rssi}")
}
// 2. To‘plamli natijalar (reportDelay > 0)
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.let { batch ->
print("Batch: ${batch.size} devices")
}
}
// 3. Skanerlash xatosi
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}")
}
}
ScanResultHandler sinfi BluetoothLeScanner ning barcha callback turlarini qayta ishlaydi. onScanResult qurilmalar ro‘yxatini MAC manzili bo‘yicha deduplikatsiya bilan yangilaydi — allaqachon topilgan qurilmalar ücun RSSI yangilanadi. CALLBACK_TYPE_MATCH_LOST qurilmani yo‘qotish haqida signal beradi (ro‘yxatdan o‘chirish). onBatchScanResults reportDelay > 0 ücun to‘plamli natijalarni qayta ishlaydi. onScanFailed xato kodlarini o‘qiladigan xabarlarga aylantiradi — BLE skanerlashni tuzatish ücun muhim.
PendingIntent skanerlash — BluetoothLeScanner ning ilova fonda bo‘lganda ham ishlaydigan BLE skanerlash mexanizmi (Android 8+ cheklovlari bilan). ScanCallback o‘rniga PendingIntent ishlatiladi, u BLE qurilmasi aniqlanganda tizim BroadcastReceiver-iga Broadcast yuboradi. Bu ilovaga xotirada bo‘lmasdan BLE qurilmalari haqida bildirishnomalar olish imkonini beradi (tizim broadcast olganda jarayon yaratadi).
Fon skanerlash cheklovlari: Android 8+ (API 26) da fon xizmatlari cheklangan — PendingIntent skanerlash bu cheklovni BroadcastReceiver orqali chetlab o‘tadi. Android 10+ (API 29) da fon BLE skanerlashi qo‘shimcha ravishda ishlab chiqaruvchilarning energiya tejash siyosatlari bilan cheklanadi (Xiaomi, Huawei, Samsung fon BLE operatsiyalarini bloklaydi). Kritik BLE stsenariylari ücun foreground service bilan bildirishnoma talab qilinadi.
// PendingIntent orqali fon BLE skanerlash
class BackgroundBLEScanner(private val context: Context) {
private val scanner: BluetoothLeScanner? by lazy {
val adapter = BluetoothAdapter.getDefaultAdapter()
adapter?.bluetoothLeScanner
}
fun startBackgroundScan() {
// BroadcastReceiver ücun PendingIntent yarating
val intent = Intent(context, BLEBroadcastReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Fon skanerlash sozlamalari
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build()
// Fon skanerlashni boshlang
scanner?.startScan(
null, // filtrlar
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) {
// Skanerlash natijalarini oling
val results = BluetoothLeScanner.getPendingIntentScanResults(intent)
results?.let { scanResults ->
for (result in scanResults) {
// Foydalanuvchiga bildirishnoma yuboring
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)
}
}
BackgroundBLEScanner sinfi PendingIntent orqali fon BLE skanerlashni ishga tushiradi. startBackgroundScan PendingIntent yaratadi, u BLE qurilmasi aniqlanganda BLEBroadcastReceiver ga Broadcast yuboradi. BroadcastReceiver getPendingIntentScanResults() orqali ScanResult ni chiqaradi va bildirishnoma ko‘rsatishi yoki ma’lumotlarni serverga yuborishi mumkin. Bu yondashuv ilova tizim tomonidan tugatilgan bo‘lsa ham ishlaydi — Android broadcast olganda BroadcastReceiver ni ishga tushiradi.
To‘liq namuna — Heart Rate Monitor qurilmalarini qidirish ücun BluetoothLeScanner, ScanSettings, ScanFilter va ScanCallback dan foydalanadigan Kotlin BLE skaneri. Skaner topilgan qurilmalar ro‘yxatini RSSI va xizmat UUID lari bilan ko‘rsatadi, BluetoothGatt orqali ulanish imkoniyati bilan.
// Kotlin-da korutinlar bilan to‘liq BLE skaner
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 {
// Bluetooth holatini tekshiring
if (adapter?.isEnabled != true) {
close(IllegalStateException("Bluetooth disabled"))
return@callbackFlow
}
// Skanerlash konfiguratsiyasi
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"))
}
}
// Skanerlashni boshlang
scanner?.startScan(filters, settings, callback)
// Muddati tugagandan so‘ng avtomatik to‘xtash
delay(duration)
scanner?.stopScan(callback)
close()
}.flowOn(Dispatchers.IO)
fun stop() {
scanner?.stopScan(object : ScanCallback() {})
}
}
class BLEException(message: String) : Exception(message)
DeviceScanner sinfi reaktiv BLE skanerlash ücun Kotlin Flow (callbackFlow) dan foydalanadi. Skanerlash LOW_LATENCY sozlamalari va Heart Rate Service UUID filtri bilan ishga tushiriladi. Natijalar onScanResult orqali Flow ga emit qilinadi. Belgilangan muddatdan so‘ng avtomatik to‘xtash (standart 10 soniya). FlowOn(Dispatchers.IO) BLE operatsiyalarini fon thread-iga o‘tkazadi. Bu yondashuv MVVM arxitekturasida viewModelScope.launch va collect orqali BLE skanerlashdan foydalanish imkonini beradi.
Tez-tez beriladigan savollar
BluetoothLeScanner — BLE skanerlash ücun Android sinfi (API 21+). BluetoothAdapter.getBluetoothLeScanner() orqali olinadi. Uchta skanerlash rejimini (LOW_POWER, BALANCED, LOW_LATENCY), UUID, nom va MAC manzili bo‘yicha filtrlashni, to‘plamli natijalarni va fon skanerlash ücun PendingIntent ni qo‘llab-quvvatlaydi. Eskirgan BluetoothAdapter.startLeScan() metodini almashtiradi.
SCAN_MODE_LOW_POWER — aniqlash kechikishi 5–10 soniya bo‘lgan fon rejimi, minimal energiya sarfi. SCAN_MODE_LOW_LATENCY — taxminan 100 ms kechikishli faol rejim, maksimal energiya sarfi. SCAN_MODE_BALANCED — kelishuv (~2 soniya kechikish). UI skanerlash ücun LOW_LATENCY, fon monitoringi ücun LOW_POWER ni PendingIntent bilan ishlating.
Sabablar: Bluetooth o‘chirilgan (adapter.isEnabled tekshiring), ruxsatlar olinmagan (API 31+ ücun BLUETOOTH_SCAN, API 23–30 ücun ACCESS_FINE_LOCATION), scanner = null (adapter mavjud emas), qurilma diapazondan tashqarida yoki noto‘g‘ri filtr ishlatilgan. Shuningdek onScanFailed ni tekshiring — xato kodi sababni ko‘rsatadi: SCAN_FAILED_ALREADY_STARTED (1) yoki SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2).
PendingIntent versiyasi startScan() dan foydalaning — ScanCallback o‘rniga PendingIntent ni uzating. BLE qurilmasi aniqlanganda Android BroadcastReceiver ga Broadcast yuboradi, u tizim tomonidan ilova fonda bo‘lsa ham ishga tushirilishi mumkin. Android 8+ ücun BroadcastReceiver ni manifestga qo‘shing. Android 10+ da ishlab chiqaruvchilarning energiya tejash cheklovlarini hisobga oling.
BluetoothLeScanner aniqlanadigan qurilmalar sonida cheklovga ega emas — cheklov muhitning BLE to‘yinganligiga bog‘liq. Ofisda 20–50 faol BLE qurilmasi, savdo markazida esa yuzlab bo‘lishi mumkin. Filtrlash ücun ScanFilter (UUID, nom bo‘yicha) dan foydalaning. Filtrsiz natijalarni asinxron qayta ishlang — onScanResult soniyada o‘nlab marta chaqirilishi mumkin.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.