
1. 項目背景與問題現象最近在適配Android 12API 31及以上版本的藍牙功能時一個非常典型且高頻的“坑”出現了你的應用明明在Android 11上運行得好好的藍牙掃描能發現一堆設備但一到Android 12的設備上掃描結果就空空如也日志里除了你自己的應用日志系統關于藍牙發現的日志也少得可憐。這絕不是你的代碼邏輯突然失效了而是Google在Android 12中引入了一套更嚴格的運行時權限模型特別是針對藍牙掃描這一涉及位置信息的行為。這個問題困擾了相當多的開發者尤其是那些需要與藍牙低功耗BLE設備或經典藍牙設備交互的應用比如智能家居控制、健康設備數據同步、文件傳輸工具等。從用戶和測試的角度看應用“失靈”了但從開發角度看這是一個必須跨越的權限門檻。我花了些時間把Android 12及更高版本上藍牙掃描所需的權限梳理了一遍并記錄了完整的排查和解決方案。如果你也遇到了“掃描不到設備”的靈異事件這篇筆記應該能幫你快速定位問題。2. Android 12藍牙權限模型的核心變化要解決問題首先得理解Google為什么這么做以及具體改了哪里。在Android 12之前進行藍牙掃描特別是BLE掃描主要需要兩個權限ACCESS_FINE_LOCATION精確定位和BLUETOOTH_SCAN在Android 11引入。當時的邏輯是因為藍牙掃描結果特別是BLE設備的信號強度RSSI可以被用來進行粗略的室內定位所以它被歸類為一種“位置信息”訪問需要位置權限。到了Android 12權限管理變得更加精細和嚴格。核心變化在于將“藍牙掃描”這個行為本身從“位置信息”的范疇中部分剝離出來并強調了其“鄰近設備”訪問的特性。這帶來了幾個關鍵點2.1 新增的BLUETOOTH_SCAN權限成為絕對主角在Android 12API 31及以上版本BLUETOOTH_SCAN權限從之前的“普通”權限升級為“運行時”權限。這意味著必須聲明在AndroidManifest.xml中必須聲明此權限。必須動態申請在代碼中必須在運行時向用戶彈窗請求授權就像請求相機、位置權限一樣。獨立控制用戶可以在系統設置中單獨控制某個應用是否擁有“藍牙掃描”的權限而不再完全與位置權限綁定。2.2 與位置權限的“脫鉤”與“有條件關聯”這是最容易讓人困惑的地方。Android 12允許你的應用在不訪問設備位置信息的前提下進行藍牙掃描。這是通過BLUETOOTH_SCAN權限的android:usesPermissionFlags屬性實現的。你可以在聲明權限時添加neverForLocation標志。這樣系統就會明白“這個應用掃描藍牙只是為了連接設備不是為了獲取位置?!?用戶授予BLUETOOTH_SCAN權限時系統可能就不會再強制要求位置權限。但是“脫鉤”是有條件的如果你的應用需要從掃描結果中獲取位置信息例如你需要使用ScanResult中的Rssi信號強度或TimestampNanos等信息來進行三角定位或位置推斷那么你仍然需要ACCESS_FINE_LOCATION權限。如果你的應用僅為了發現和連接設備例如連接一個藍牙音箱、智能燈泡你只需要知道附近有這些設備并獲取其地址MAC地址用于連接那么你可以聲明neverForLocation標志從而可能避免申請位置權限。2.3 其他必要權限的鞏固除了掃描連接和管理藍牙設備還需要其他權限BLUETOOTH_CONNECT用于連接藍牙設備、與已配對設備通信、訪問設備信息等。這在Android 12也成為了運行時權限。BLUETOOTH_ADVERTISE如果你的應用自身要作為藍牙外圍設備廣播例如讓手機模擬一個Beacon則需要此權限。ACCESS_BACKGROUND_LOCATION如果你的應用需要在后臺應用不可見時持續進行藍牙掃描那么即使你聲明了neverForLocation在Android 10及以上版本后臺掃描行為本身就可能觸發系統對后臺位置訪問的限制可能需要此權限。但情況較為復雜通常前臺掃描足夠。下表總結了不同場景下的權限需求操作場景Android 11及以前Android 12及以后 (聲明neverForLocation)Android 12及以后 (需要位置信息)前臺掃描發現BLE設備BLUETOOTH_SCAN(普通) ACCESS_FINE_LOCATION(運行時)BLUETOOTH_SCAN(運行時)BLUETOOTH_SCAN(運行時) ACCESS_FINE_LOCATION(運行時)連接已發現/已配對設備BLUETOOTH(普通) BLUETOOTH_ADMIN(普通)BLUETOOTH_CONNECT(運行時)BLUETOOTH_CONNECT(運行時)應用作為外圍設備廣播BLUETOOTH_ADVERTISE(普通)BLUETOOTH_ADVERTISE(運行時)BLUETOOTH_ADVERTISE(運行時)后臺持續掃描上述權限 ACCESS_BACKGROUND_LOCATION(運行時)可能仍需ACCESS_BACKGROUND_LOCATION取決于掃描結果用途ACCESS_BACKGROUND_LOCATION(運行時)3. 完整解決方案與代碼實現理解了原理我們來看具體怎么做。這里以一個典型的“掃描并連接BLE設備”的應用為例假設我們不需要位置信息僅為了設備連接。3.1 AndroidManifest.xml 權限聲明這是第一步也是最容易出錯的一步。聲明必須準確無誤。manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.mybluetoothapp !-- 對于 Android 12 (API 31) 藍牙掃描聲明不用于定位 -- uses-permission android:nameandroid.permission.BLUETOOTH_SCAN android:usesPermissionFlagsneverForLocation / !-- 對于 Android 12 (API 31) 藍牙連接 -- uses-permission android:nameandroid.permission.BLUETOOTH_CONNECT / !-- 對于 Android 6.0 到 Android 11 (API 23-30) 藍牙掃描需要的位置權限 -- !-- 注意即使你的 targetSdkVersion 31為了兼容舊設備最好也加上 -- uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / !-- 如果需要后臺掃描則還需要這個謹慎使用 -- !-- uses-permission android:nameandroid.permission.ACCESS_BACKGROUND_LOCATION / -- !-- 傳統藍牙權限用于 Android 11 及以下 -- uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN / !-- 如果只針對 BLE 設備可以聲明此 feature -- uses-feature android:nameandroid.hardware.bluetooth_le android:requiredtrue/ application ... ... /application /manifest關鍵點解析android:usesPermissionFlagsneverForLocation這是Android 12權限聲明的精髓。它明確告知系統你申請BLUETOOTH_SCAN權限的目的不是為了獲取位置。這能帶來兩個好處1) 在Android 12設備上系統可能不會強制要求你同時擁有位置權限2) 在申請彈窗中向用戶說明的理由會更清晰例如“允許應用發現附近的設備”而不是“允許應用訪問位置”。兼容性聲明即使targetSdkVersion設為31或更高你仍然需要聲明ACCESS_FINE_LOCATION。因為當你的應用安裝在Android 11API 30的設備上時系統會忽略BLUETOOTH_SCAN的聲明因為該系統版本不存在此權限轉而檢查ACCESS_FINE_LOCATION。這是一種標準的向后兼容做法。BLUETOOTH和BLUETOOTH_ADMIN在舊版本系統上這些是必需的。即使在新系統上聲明它們也無害系統會忽略已廢棄的權限。3.2 運行時權限動態申請聲明了權限接下來就是在合適的時機例如進入掃描界面時向用戶申請。我們需要處理Android 12的新權限和舊版本的位置權限。import android.Manifest import android.bluetooth.BluetoothAdapter import android.content.pm.PackageManager import android.os.Build import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat class BluetoothScanActivity : AppCompatActivity() { private lateinit var bluetoothAdapter: BluetoothAdapter // 使用 Activity Result API 簡化權限請求回調處理 private val requestPermissionLauncher registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions - val allGranted permissions.entries.all { it.value } if (allGranted) { // 所有必要權限都已授予可以開始掃描 startBluetoothScan() } else { // 有權限被拒絕向用戶解釋為什么需要這些權限 showPermissionDeniedDialog() } } override fun onStart() { super.onStart() checkAndRequestPermissions() } private fun checkAndRequestPermissions() { val permissionsToRequest mutableListOfString() // 檢查藍牙硬件支持 bluetoothAdapter BluetoothAdapter.getDefaultAdapter() ?: run { showToast(該設備不支持藍牙) return } // 1. 檢查并添加 Android 12 (API 31) 的權限 if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { // BLUETOOTH_SCAN 權限用于發現設備 if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) ! PackageManager.PERMISSION_GRANTED ) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_SCAN) } // BLUETOOTH_CONNECT 權限用于連接設備如果你在掃描后需要連接 if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) ! PackageManager.PERMISSION_GRANTED ) { permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT) } } else { // 2. 檢查并添加 Android 6.0 到 Android 11 (API 23-30) 的位置權限 // 注意在 Android 11 上即使聲明了 BLUETOOTH_SCAN普通權限掃描 BLE 仍需位置權限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ! PackageManager.PERMISSION_GRANTED ) { permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION) } } // 3. 對于所有版本檢查藍牙開關這不是權限但必須開啟 if (!bluetoothAdapter.isEnabled) { // 可以引導用戶打開藍牙這里使用一個簡單的提示 showEnableBluetoothDialog() return // 先不請求權限等藍牙打開再說 } // 4. 如果有需要申請的權限則發起請求 if (permissionsToRequest.isNotEmpty()) { requestPermissionLauncher.launch(permissionsToRequest.toTypedArray()) } else { // 所有權限都已具備藍牙已開啟直接開始掃描 startBluetoothScan() } } private fun startBluetoothScan() { // 這里實現你的藍牙掃描邏輯例如使用 BluetoothLeScanner // val scanner bluetoothAdapter.bluetoothLeScanner // val settings ScanSettings.Builder().build() // val filters listOfScanFilter() // 可以添加過濾條件 // scanner.startScan(filters, settings, scanCallback) showToast(權限檢查通過開始掃描...) // ... 實際掃描代碼 } // 輔助方法顯示對話框等UI交互 private fun showEnableBluetoothDialog() { /* ... */ } private fun showPermissionDeniedDialog() { /* ... */ } private fun showToast(message: String) { /* ... */ } }代碼邏輯拆解版本判斷是核心我們首先根據Build.VERSION.SDK_INT判斷設備系統版本。對于Android 12S/API 31及以上我們動態申請BLUETOOTH_SCAN和BLUETOOTH_CONNECT。對于Android 11及以下我們申請ACCESS_FINE_LOCATION。權限檢查前置在申請前先用ContextCompat.checkSelfPermission檢查權限是否已授予。只申請未被授予的權限避免不必要的彈窗打擾用戶。藍牙開關檢查無論權限如何藍牙硬件必須開啟。這是一個獨立于權限的系統狀態檢查。通常我們會先確保藍牙開啟再處理權限問題。使用現代APIregisterForActivityResult配合ActivityResultContracts.RequestMultiplePermissions是處理運行時權限的最佳實踐它避免了重寫onRequestPermissionsResult方法的繁瑣使代碼更清晰。優雅降級如果用戶拒絕了權限應該有一個友好的界面showPermissionDeniedDialog向用戶解釋這些權限對于應用核心功能如連接智能設備的必要性并引導用戶去應用設置頁面手動開啟。3.3 掃描邏輯的實現與適配權限搞定后掃描邏輯本身也需要針對Android 12進行微調。主要是ScanSettings的配置。import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanFilter import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanSettings import android.os.Build import android.os.Handler import android.os.Looper private val scanCallback object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { // 處理掃描到的設備 val device result.device val rssi result.rssi // 注意在聲明了 neverForLocation 后此值可能為0 val scanRecord result.scanRecord // ... 更新UI將設備添加到列表 } override fun onScanFailed(errorCode: Int) { // 掃描失敗處理常見錯誤碼如 SCAN_FAILED_APPLICATION_REGISTRATION_FAILED應用注冊失敗通常權限問題 // 或 SCAN_FAILED_FEATURE_UNSUPPORTED不支持BLE等 showToast(掃描失敗錯誤碼: $errorCode) } } private fun buildScanSettings(): ScanSettings { val builder ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 掃描模式低延遲發現設備最快 // Android 12 的重要適配如果你聲明了 neverForLocation且不需要RSSI可以設置此標志 if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { // 這個標志告訴硬件可以為了省電而降低掃描報告頻率因為我們不關心精確的RSSI // 注意設置此標志后onScanResult回調的頻率可能會降低且RSSI可能不準確或為0 builder.setLegacy(false) // 對于只關心新BLE設備的情況可以設為false // 如果你確實需要RSSI做信號篩選就不要加下面這行 // builder.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED) } return builder.build() } fun startScan() { val scanner bluetoothAdapter.bluetoothLeScanner val filters listOfScanFilter() // 可以按設備名、服務UUID等過濾提高效率 val settings buildScanSettings() // 開始掃描 scanner.startScan(filters, settings, scanCallback) showToast(掃描已啟動) // 可選掃描10秒后自動停止節省電量 Handler(Looper.getMainLooper()).postDelayed({ stopScan() }, 10000) } fun stopScan() { bluetoothAdapter.bluetoothLeScanner?.stopScan(scanCallback) showToast(掃描已停止) }關鍵點與避坑指南setLegacy(false)在Android 12上這個設置與neverForLocation標志協同工作。它告訴底層藍牙棧你不需要為了向后兼容舊版掃描報告格式而做額外處理。對于只針對Android 5.0以上新BLE規范的應用設置為false可能更高效。但如果你需要掃描一些舊設備或遇到掃描問題可以嘗試設為true或移除這行。RSSI值可能為0這是聲明neverForLocation后一個非常重要的副作用。系統或硬件為了保護位置隱私可能會在掃描結果中屏蔽或歸零RSSI值。如果你的邏輯依賴RSSI來篩選信號強的設備比如只連接-70dBm以上的設備那么你就不能聲明neverForLocation必須同時申請位置權限。掃描過濾使用ScanFilter能極大提升掃描效率減少不必要的回調和處理。例如如果你只尋找特定服務UUID的設備務必加上過濾器。4. 問題排查與深度調試即使按照上面的步驟做了有時候掃描依然不工作。這時候就需要系統化的排查。以下是我總結的排查清單按照從外到內、從易到難的順序進行。4.1 基礎環境檢查設備藍牙是否真的開啟通過系統設置確認而不僅僅是代碼檢查。有時代碼檢查返回true但硬件或驅動層可能有問題。目標藍牙設備是否處于可發現模式BLE設備通常需要處于廣播狀態經典藍牙設備需要處于“可被檢測”狀態。物理距離和干擾將設備靠近1米內排除信號弱的問題。遠離Wi-Fi路由器、微波爐等2.4GHz干擾源。其他應用能否掃描到使用“nRF Connect”、“LightBlue”等通用的藍牙調試APP測試。如果它們也掃不到很可能是目標設備或手機藍牙硬件的問題。如果它們能掃到而你的應用不能問題就在你的應用上。4.2 應用權限與配置檢查檢查AndroidManifest.xml確保權限聲明沒有拼寫錯誤uses-permission標簽在正確的層級。特別注意android:usesPermissionFlagsneverForLocation是否只附加在BLUETOOTH_SCAN上。檢查targetSdkVersion在app/build.gradle中確保targetSdkVersion至少為31如果你要適配Android 12的新權限模型。如果targetSdkVersion低于31系統會使用舊版的權限規則但你又聲明了新權限可能導致行為不一致。動態權限狀態復查在申請權限的回調中打印或記錄所有請求權限的授予狀態。使用ActivityResultContracts.RequestMultiplePermissions返回的MapString, Boolean來確認。private val requestPermissionLauncher registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions - permissions.entries.forEach { entry - Log.d(PermissionDebug, ${entry.key} : ${entry.value}) } // ... 后續處理 }檢查是否在后臺從Android 10開始對后臺應用獲取位置信息包括通過藍牙掃描間接獲取有嚴格限制。確保你的掃描代碼是在應用處于前臺Activity可見時執行的。如果你需要在后臺掃描那將是一個更復雜的課題涉及前臺服務、后臺位置權限和更嚴格的審核。4.3 日志與系統信息分析當基礎檢查都通過后就需要深入系統內部看日志。查看應用日志過濾你的應用Tag檢查是否有SecurityException或類似“Permission Denial”的異常拋出。查看系統藍牙日志這是最有效的手段。使用adb logcat命令過濾藍牙相關的系統日志。adb logcat | grep -E (Bluetooth|BT|BLE|ScanManager|AdapterService)或者更精確地在Android Studio的Logcat中選擇設備后添加過濾器tag:Bluetooth或tag:BluetoothAdapter。重點觀察以下日志當你調用startScan()時是否有類似App your.package.name is not allowed to use Bluetooth或Scan failed, app uid XXXX not allowed to scan的錯誤。這直接指向權限問題。是否有Scanning too frequent之類的警告Android對掃描頻率有限制過于頻繁的啟停掃描會被系統限制。檢查系統權限設置頁面手動進入手機的設置 應用 你的應用 權限查看“附近設備”對應BLUETOOTH_SCAN和“位置信息”權限是否確認為“允許”。注意在Android 13位置權限可能細分為“僅在使用中允許”和“始終允許”確保符合你的場景。4.4 代碼邏輯與API使用復查掃描回調是否注冊成功確保startScan被調用并且傳入的scanCallback對象是有效的、未被垃圾回收的例如作為Activity的成員變量持有。掃描設置是否過于苛刻檢查ScanSettings比如setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)在低功耗模式下發現設備會很慢。對于測試建議使用SCAN_MODE_LOW_LATENCY。過濾器是否過濾掉了所有設備如果你使用了ScanFilter檢查過濾條件如設備名稱、MAC地址、服務UUID是否寫錯導致所有設備都被過濾掉。嘗試不設過濾器或設置一個寬泛的過濾器進行測試。是否在掃描過程中停止了藍牙適配器確保在掃描生命周期內從startScan到stopScanBluetoothAdapter對象是有效的并且藍牙沒有在中間被系統或用戶關閉。4.5 針對特定機型或系統的特殊處理有些廠商如小米、華為、OPPO、Vivo的定制系統MIUI、EMUI等有更激進的權限管理或后臺限制。自啟動管理在部分系統上即使授予了所有權限應用如果被“禁止自啟動”或“關聯啟動”后臺掃描可能被殺死。需要引導用戶手動在手機管家中設置。省電策略檢查是否將你的應用加入了“電池優化”的白名單。在設置中搜索“電池優化”找到你的應用選擇“不優化”。懸浮窗權限等雖然與藍牙掃描無直接關系但某些系統會將這些非常規權限與后臺活動關聯導致應用行為異常。如果排查所有可能性后仍無效可以查閱該機型開發者的官方論壇或社區看是否有已知的特定限制。5. 進階考量與最佳實踐解決了基本掃描問題后為了構建一個健壯的藍牙應用還需要考慮以下幾點。5.1 后臺掃描的復雜性如前所述在后臺進行藍牙掃描是一個“深水區”。從Android 10開始后臺應用訪問位置信息受到嚴格限制。即使你聲明了neverForLocation持續的后臺掃描行為本身也可能被系統視為潛在的位置訪問行為從而觸發限制。建議盡量避免持續后臺掃描這是最省心省電的做法??紤]使用以下替代方案前臺服務當應用需要長時間掃描時啟動一個帶有持續通知的前臺服務。這明確告知用戶應用正在活動并通常能獲得更穩定的系統資源。定時掃描使用WorkManager或AlarmManager安排定期、短暫的掃描任務而不是7x24小時不間斷掃描。使用系統廣播有限對于已配對設備的連接狀態變化可以監聽BluetoothDevice.ACTION_ACL_CONNECTED和ACTION_ACL_DISCONNECTED廣播但這無法發現新設備。如果必須后臺掃描確保申請了ACCESS_BACKGROUND_LOCATION權限這是一個敏感權限上架Google Play需要額外聲明和審核。在應用商店的描述中清晰說明為什么需要此權限。準備處理用戶拒絕該權限的情況并提供優雅降級方案例如僅支持前臺掃描功能。5.2 權限申請的時機與用戶體驗不要一進入應用就彈出一堆權限請求這會讓用戶反感。適時請求在用戶即將使用需要該權限的功能時再請求。例如在用戶點擊“掃描設備”按鈕后再執行權限檢查和申請流程。解釋必要性在請求權限前尤其是BLUETOOTH_SCAN和位置權限用一個簡單的對話框或界面文字向用戶解釋“我們需要掃描附近藍牙設備的權限以便為您連接智能音箱/手環。” 這能顯著提高授權率。處理“不再詢問”如果用戶拒絕了權限并勾選了“不再詢問”下次shouldShowRequestPermissionRationale()會返回false。此時你應該引導用戶前往系統設置頁面手動開啟權限。提供一個清晰的指引按鈕和說明文字。5.3 兼容舊版本Android的優雅處理你的應用可能需要支持Android 11甚至更早的版本。權限代碼需要做良好的分支處理。private fun checkPermissions(): ListString { val neededPermissions mutableListOfString() if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { // Android 12 if (!hasPermission(Manifest.permission.BLUETOOTH_SCAN)) { neededPermissions.add(Manifest.permission.BLUETOOTH_SCAN) } if (!hasPermission(Manifest.permission.BLUETOOTH_CONNECT)) { neededPermissions.add(Manifest.permission.BLUETOOTH_CONNECT) } // 注意在聲明了 neverForLocation 后可以不主動請求位置權限 // 但如果功能需要RSSI則仍需檢查并請求 // if (needLocationForScan !hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { // neededPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION) // } } else { // Android 6.0 - 11 if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) { neededPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION) } // Android 12以下BLUETOOTH 和 BLUETOOTH_ADMIN 是普通權限無需運行時申請 // 但需要在 Manifest 中聲明 } return neededPermissions } private fun hasPermission(permission: String): Boolean { return ContextCompat.checkSelfPermission(this, permission) PackageManager.PERMISSION_GRANTED }這種結構清晰的版本判斷使得權限邏輯一目了然便于維護。5.4 測試策略藍牙開發測試至關重要。準備多版本測試機至少準備一臺Android 12和一臺Android 11的設備進行測試。模擬權限拒絕場景在開發者選項中可以手動撤銷應用的具體權限測試應用在權限缺失時的表現是否崩潰以及引導用戶開啟權限的流程是否順暢。使用模擬器謹慎Android模擬器對藍牙的支持有限尤其是低功耗藍牙。大部分模擬器無法模擬真實的藍牙掃描。真機測試是唯一可靠的方式。日志是朋友養成在關鍵節點如權限檢查前后、掃描開始/停止、回調觸發時打Log的習慣并熟練使用adb logcat查看系統藍牙日志。這是定位疑難雜癥的最強武器。我在實際項目中正是通過系統日志發現了一條關鍵的Permission denial for startLeScan日志才最終鎖定是Android 12上新權限模型導致的問題。從那時起我就把權限檢查和適配作為藍牙功能開發的第一步再也沒在這個坑里摔倒過。藍牙開發本身就有很多坑從協議到硬件兼容性權限問題只是第一道關卡但跨過它你的應用才算是拿到了入場券。希望這篇詳細的筆記能幫你節省大量排查時間。