如果你也跟我一样大半夜还在调试一块 HC-05 或 ESP32 蓝牙模块手机每隔几十秒弹一次“蓝牙配对请求”自动化测试脚本跑着跑着就被一个确认框怼停你大概率会想把系统设置里那个蓝牙配对对话框直接抠掉。我这两年因为做车载蓝牙、智能设备和各类老模块调试跟这个弹窗缠斗了很久试过改设置、刷模块、写 Hook、拦截广播最后稳定留下来的是三套思路免 root 的广播拦截、root 环境下的 LSPosed Hook、以及最省事的无障碍自动点击。文章末尾还附了一套日志分析流程专门解决“配不上、总弹窗、闪断”这类玄学问题内容偏实操可以直接照着抄。1. 配对确认框到底从哪来先搞清楚它为什么缠着你1.1 弹窗的真实身份Settings 里的 BluetoothPairingDialog这个确认框不是蓝牙协议栈自己弹的而是 Android 系统设置应用里的一个 Activity类名通常叫com.android.settings.bluetooth.BluetoothPairingDialog。它在 Android 的 Settings、SystemUI、SetupWizard 几个进程里换来换去但角色一直没变接收系统下发的配对请求广播然后用一个前台对话框问你“是否与 XX 设备配对”。我见过很多人在论坛上问“怎么关掉这个弹窗”下面有人回答“开发者选项里关”其实是错的。开发者选项里的“蓝牙音频设置”“蓝牙 AVRCP 版本”都跟这个无关蓝牙默认也没有“允许自动配对”这种开关。真正想关掉它得理解它背后的触发链而不是在设置里瞎翻。1.2 必经之路从蓝牙协议栈到系统 UI 的广播链一次典型的经典蓝牙配对请求在 Android 里大致走这么一条链路远端设备HC-05、车机、耳机发起配对或者你的手机调用BluetoothDevice.createBond()主动去配对。蓝牙协议栈通常叫 BTIF / BTM收到底层 HCI 层的配对请求事件。协议栈把事件上报给蓝牙进程蓝牙进程再通过 Binder 通知系统框架。框架层发出一个有序广播android.bluetooth.device.action.PAIRING_REQUEST。Settings 里的BluetoothPairingRequest接收器收到这个广播判断配对变体PIN、确认、Passkey 等然后启动BluetoothPairingDialog弹窗。关键就在第 4 步这是一个有序广播。有序广播的特点是接收器按优先级顺序排队处理谁先声明android:priority高谁就先收到而且在任意一个接收器里都可以调用abortBroadcast()把这个广播“掐断”。系统 Settings 里那个接收器默认优先级是 0所以我只要让我的接收器声明一个足够高的优先级抢在它前面把这个广播截下来系统就根本收不到配对请求自然也就不会弹窗。1.3 最容易遇到反复弹窗的三种场景根据我踩过的情况真正会被这个弹窗烦到的人基本逃不出下面几类固定 PIN 的老模块HC-05、HC-06 这类蓝牙串口模块出厂默认 PIN 一般是 1234 或 0000。你只要用 App 触发一次配对系统就弹一个“输入 PIN 码”的框。更麻烦的是每次模块重新上电如果手机没有记住 link key或者上层 App 反复调用createBond()这个框就会频繁出现。车机 / 蓝牙音箱重连车载蓝牙每次启动如果 link key 没保存成功系统就要重新配对并弹确认框。有些车机的安全模式要求每次连接都做“数字确认”这时候弹出的不是你熟悉的“配对吗”而是“代码 123456 是否匹配”的确认框。安卓 TV / 盒子加蓝牙遥控器TV 端有些遥控器休眠唤醒后频繁重连只要固件写得不规范每次都会触发确认用户的体验就是“遥控器又连不上了电视又弹窗了”。搞清楚自己是哪种场景后面选方案才有方向。固定 PIN 模块最优先考虑第二章的广播拦截车机、耳机这类确认型配对可以用第二章或第四章的无障碍方案如果已经 root 了那就别折腾了直接上第三章的 LSPosed Hook。2. 免 root 方案用广播接收器抢在系统弹窗前自动完成配对2.1 核心思路有序广播的优先级游戏这个方案不需要 root但需要你写一个小 App。它的核心就是利用有序广播的优先级排序我在自己的BroadcastReceiver的IntentFilter上声明android:priority999这样配对请求广播发出后我的接收器先于系统 Settings 的接收器默认优先级 0收到。收到之后做什么根据不同的配对变体调用BluetoothDevice的几个“自动完成配对”APIsetPin(byte[])用于旧版 PIN 输入型配对。setPairingConfirmation(boolean)用于数字确认/仅确认类型配对。setPasskey(int)用于需要输入 6 位数字 Passkey 的配对。调用完成后直接abortBroadcast()把广播吞掉。系统 Settings 的接收器收不到广播就不再启动对话框。这里有个前提我必须在收到广播的当下立刻完成配对动作不能在onReceive里开子线程再慢慢处理否则系统 UI 可能已经弹窗了。整个逻辑是同步的实际执行耗时也就几毫秒到几十毫秒完全来得及。2.2 完整实测代码直接抄的 AutoPair 接收器先写接收器本体我用的 Kotlin逻辑里做了两类最常见处理PIN 型直接设置固定 PIN确认型直接确认。class AutoPairReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action ! BluetoothDevice.ACTION_PAIRING_REQUEST) return val device if (android.os.Build.VERSION.SDK_INT 33) { intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java) } else { Suppress(DEPRECATION) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) } ?: return val variant intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, -1) val key intent.getByteArrayExtra(BluetoothDevice.EXTRA_PAIRING_KEY) try { when (variant) { BluetoothDevice.PAIRING_VARIANT_PIN - { // HC-05/HC-06 默认 1234按你自己的设备改 val pin 1234.toByteArray(Charsets.UTF_8) device.setPin(pin) } BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION, BluetoothDevice.PAIRING_VARIANT_CONSENT - { // 数字确认/仅确认直接确认即可 device.setPairingConfirmation(true) } BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY - { // 远端显示数字等待手机确认 device.setPairingConfirmation(true) } BluetoothDevice.PAIRING_VARIANT_PASSKEY - { // 由手机输入 6 位数字从 EXTRA_PAIRING_KEY 里取 key?.let { val passkey String(it, Charsets.UTF_8).toInt() device.setPasskey(passkey) } } else - { // 未知类型不拦截放行给系统弹窗 return } } // 关键吞掉广播系统 Settings 就不会再弹窗 abortBroadcast() } catch (e: Exception) { // 权限不足或设备未连接等情况捕获后放行系统弹窗 Log.e(AutoPair, auto pair failed, variant$variant, e) } } }注意setPin和setPairingConfirmation这些 API 都是同步的只要设备还处于配对等待状态调用后蓝牙服务会立刻把 PIN 或确认结果回给远端设备。这也是为什么必须抢在系统弹窗之前处理。接收器的注册我建议动态注册而不是写在 Manifest 里静态注册。动态注册的优先级是可以在IntentFilter里指定的而且不需要担心 Android 8 的隐式广播限制。为了让接收器一直在监听需要配合一个前台服务class AutoPairService : Service() { private lateinit var receiver: BroadcastReceiver override fun onCreate() { super.onCreate() receiver AutoPairReceiver() val filter IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST) filter.priority 999 if (Build.VERSION.SDK_INT 33) { registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED) } else { Suppress(DEPRECATION) registerReceiver(receiver, filter) } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { startForeground(1, Notification.Builder(this, auto_pair_channel) .setSmallIcon(android.R.drawable.ic_popup_sync) .setContentTitle(蓝牙自动配对服务运行中) .build()) return START_STICKY } override fun onDestroy() { unregisterReceiver(receiver) super.onDestroy() } override fun onBind(intent: Intent?): IBinder? null }Manifest 里把BLUETOOTH_CONNECT、BLUETOOTH_SCAN、FOREGROUND_SERVICE、POST_NOTIFICATIONS这些权限带上Android 12 以后需要在运行时申请BLUETOOTH_CONNECT和POST_NOTIFICATIONS否则调用setPin/setPairingConfirmation会直接抛 SecurityException。这里我不展开权限申请代码你用常规的运行时权限请求就行。2.3 踩过的坑abortBroadcast 失效、版本差异、进程被杀这个方案我实测了大概几十台设备、五六种 ROM真正能直接“彻底无弹窗”的场景大概有七成。剩下三成会碰到这几个问题第一个别国产 ROM 把 Settings 的BluetoothPairingRequest优先级改了或者系统不是通过有序广播发这个请求导致abortBroadcast()掐不断弹窗还是会出现。这种情况也不能说方案完全失败因为我的接收器已经把配对动作完成了弹窗出来之后你点一下“配对”只是走个形式甚至有时候弹窗会出现但设备已经配对成功你直接按 Home 键就能离开。如果连点都不点系统会在十几秒后自动关闭这个对话框。第二Android 12 之后setPin这些 API 需要BLUETOOTH_CONNECT权限而且这个权限是运行时权限必须动态申请。如果没申请成功调用会抛异常弹窗照样出现。Android 13 之后动态注册接收器时必须指定RECEIVER_EXPORTED或RECEIVER_NOT_EXPORTED我上面代码写的RECEIVER_NOT_EXPORTED是安全做法因为它只接收系统 UID 发出的广播不会接收别的 App 伪造的配对请求避免被恶意应用利用。第三也是最重要的这个方案的接收器依赖应用进程活着。如果应用被系统杀后台或者用户没有允许自启动服务起不来接收器自然不工作。我的做法是前台服务 引导用户把应用加入电池白名单同时把服务做成START_STICKY被杀后系统会尽量重建。这一套在普通手机上能做到“大多数时候都在监听”但如果你长期不打开 App某些激进 ROM 还是会杀掉服务这一点要有心理准备。给开发模块、自己折腾的场景这个方案基本够用。如果实在接受不了“偶尔还是被杀”那就上第三章的 root 方案。3. 已 root 方案用 LSPosed 从源头让对话框直接消失3.1 hook 点选择直接干掉 BluetoothPairingDialog有 root 环境之后事情就简单多了。我用 LSPosed 写了个小模块直接 HookBluetoothPairingDialog的onCreate在系统对话框创建出来之前就拿到BluetoothDevice和配对变体字段自动完成配对然后调用finish()把对话框关掉。核心逻辑跟第二章一样只不过这次是在系统进程里执行不需要担心进程被杀也不需要申请BLUETOOTH_CONNECT运行时权限因为系统 App 自己就有权限。真正做到了“完全无感”。3.2 LSPosed 模块代码反射加 Hook传统 Xposed 风格的实现如下public class AutoPairHook implements IXposedHookLoadPackage { Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { // 只处理设置应用 if (!com.android.settings.equals(lpparam.packageName)) { return; } Class? dialogCls; try { dialogCls XposedHelpers.findClass( com.android.settings.bluetooth.BluetoothPairingDialog, lpparam.classLoader); } catch (Throwable t) { return; } XposedHelpers.findAndHookMethod(dialogCls, onCreate, Bundle.class, new XC_MethodHook() { Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { try { Object deviceObj getBluetoothDeviceField(param.thisObject); int variant getPairingVariantField(param.thisObject); if (deviceObj instanceof BluetoothDevice) { BluetoothDevice device (BluetoothDevice) deviceObj; autoPair(device, variant); } // 关闭对话框 XposedHelpers.callMethod(param.thisObject, finish); } catch (Throwable t) { // hook 失败就放行至少不影响正常弹窗 } } }); } private void autoPair(BluetoothDevice device, int variant) throws Exception { switch (variant) { case BluetoothDevice.PAIRING_VARIANT_PIN: device.setPin(1234.getBytes()); break; case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: case BluetoothDevice.PAIRING_VARIANT_CONSENT: case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: device.setPairingConfirmation(true); break; default: break; } } }3.3 兼容性经验字段名在各 ROM 上不一样我最初照着 AOSP 源码写认为字段就叫mDevice和mPairingVariant结果在某个知名定制 ROM 上直接崩了——人家把字段改成了mBtDeviceint 字段还改成了mType。所以后来我改成反射遍历不硬编码字段名private Object getBluetoothDeviceField(Object dialog) { for (java.lang.reflect.Field field : dialog.getClass().getDeclaredFields()) { field.setAccessible(true); if (BluetoothDevice.class.isAssignableFrom(field.getType())) { try { return field.get(dialog); } catch (IllegalAccessException e) { // ignore } } } return null; } private int getPairingVariantField(Object dialog) { for (java.lang.reflect.Field field : dialog.getClass().getDeclaredFields()) { field.setAccessible(true); if (field.getType() ! int.class) continue; try { int value field.getInt(dialog); // 只关心 0 到 7 之间的配对变体值避免拿错字段 if (value 0 value 7) return value; } catch (IllegalAccessException e) { // ignore } } return -1; }这个“按类型找字段”的思路在 Hook 系统应用时非常管用因为各个 ROM 厂商改动太随意了硬编码字段名就是给自己挖坑。另外你也可以 HookBluetoothPairingRequest这个BroadcastReceiver的onReceive在里面直接abortBroadcast()并自动配对效果类似只是实现角度不同。我选BluetoothPairingDialog是因为它稳而且finish()是公开方法反射调用简单。3.4 效果和风险LSPosed 方案一旦跑通配对确认框是真正意义上的“不存在”任何设备配对都会自动完成。这同时也带来明显的安全风险只要手机蓝牙处于可发现状态附近任何人都能尝试配对并连接你的手机自动确认等于完全放行。所以我建议在模块里做成“白名单模式”——只在配对指定设备地址时自动确认其他设备继续走系统弹窗。这个逻辑就是在autoPair前面加一个macAddress判断代码量不大但安全上稳妥得多。我自己的使用习惯是调试 HC-05 和 ESP32 的时候开白名单自动确认日常出门还是把模块关掉的毕竟蓝牙这东西安全边界没那么乐观。4. 不想写代码无障碍自动点击也能解决4.1 原理模拟人类点屏幕如果你不想写广播接收器也没有 root最省心的办法就是无障碍服务自动点击。原理很简单用AccessibilityService监听窗口变化当蓝牙配对对话框出现时读取屏幕上的控件树找到“配对”按钮调用performAction(ACTION_CLICK)模拟一次点击。它跟广播拦截有个本质区别广播拦截是“抢在弹窗前把配对做完弹窗根本不出来”无障碍是“弹窗出来了我自动帮你点确认”。前者无感后者你肉眼能看到对话框闪一下然后立刻消失体验也还行但偶尔会有闪烁感。需要注意不是所有配对都是点一下“配对”就行。输入 PIN 码的框需要你填数字纯无障碍自动点击没法处理输入场景除非再配合剪贴板粘贴。所以这个方案最适配的是“数字确认/仅确认”这类两键确认的配对也就是PAIRING_VARIANT_PASSKEY_CONFIRMATION和PAIRING_VARIANT_CONSENT。4.2 一个几十行代码的自动点击服务自己写一个无障碍服务也不复杂。AccessibilityService的代码加上配置文件总共不到一百行。下面是核心部分class AutoPairAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent?) { if (event?.eventType ! AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) return val root rootInActiveWindow ?: return val pairButton findPairButton(root) ?: return pairButton.performAction(AccessibilityNodeInfo.ACTION_CLICK) } private fun findPairButton(node: AccessibilityNodeInfo): AccessibilityNodeInfo? { val text node.text?.toString() ?: if (text.contains(配对) || text.equals(Pair, ignoreCase true)) { return node } for (i in 0 until node.childCount) { val child node.getChild(i) ?: continue val result findPairButton(child) if (result ! null) return result } return null } override fun onInterrupt() {} }配套的无障碍服务配置文件res/xml/accessibility_service_config.xmlaccessibility-service xmlns:androidhttp://schemas.android.com/apk/res/android android:accessibilityEventTypestypeWindowStateChanged android:accessibilityFeedbackTypefeedbackGeneric android:notificationTimeout100 android:canRetrieveWindowContenttrue /Manifest 里声明服务并加上权限service android:name.AutoPairAccessibilityService android:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICE android:exportedtrue intent-filter action android:nameandroid.accessibilityservice.AccessibilityService / /intent-filter meta-data android:nameandroid.accessibilityservice android:resourcexml/accessibility_service_config / /service这个方案有两个明显缺点一是每次弹窗会闪一下视觉上有点烦二是它依赖“窗口状态变化”事件如果某些 ROM 的蓝牙配对框不是标准 Activity 而是悬浮窗可能捕获不到。不过对于很多车机和耳机场景它已经够用了。4.3 现成工具折腾党路线Auto.js 脚本如果连上面的代码都不想写直接用 Auto.js 这类自动化脚本工具也能实现。脚本核心就两三行auto.waitFor(); while (true) { text(配对).findOne().click(); sleep(500); }开启 Auto.js 的无障碍服务后脚本会循环寻找屏幕上的“配对”文本并点击。实测在配对确认场景下能跑但对 ROM 的兼容性和无障碍服务的稳定性要求较高后台被清理之后脚本就断了。Auto.js 最新官方版本也做了收费和联网校验破解版水又深我建议还是用自己写的无障碍服务或者更正规的自动化工具如 Tasker AutoInput更放心。4.4 三种方案对比我自己用下来三个方案的取舍是这样的对比维度广播拦截免rootLSPosed Hookroot无障碍自动点击免root是否需要 root否是否弹窗是否可见不可见不可见会闪一下是否有代码门槛需要写 App需要会 Hook可用现成工具进程被杀风险有需保活无有需保活长期稳定性较好最稳定依赖 ROM安全风险中高建议白名单中适合谁开发调试、固定 PIN 模块已 root、追求无感不想写代码、只对付确认框我的建议很直接有 root 就上 LSPosed 白名单没有 root 但是要跟 HC-05 这类固定 PIN 设备长期打交道就写广播拦截 App。只是想临时解决一个蓝牙耳机的配对闪断无障碍方案最省事。5. 日志分析配不上、总弹窗、闪断看日志才知道怎么回事5.1 logcat 快速定位别在设置界面瞎猜很多“配不上”的问题看日志能得到比界面更准确的原因。连接手机开 USB 调试复现一次配对过程然后抓日志adb logcat -v threadtime -d bt_log.txt再用关键字过滤grep -iE bluetooth|pairing|bond|a2dp|hci bt_log.txt我常用的 tag 大致有这些Tag对应内容BluetoothManagerService设备管理、bond 状态切换、profile 连接BluetoothAdapterService底层适配器服务、配对变体相关bt_btif/btif_dm协议栈到框架的桥接、配对动作BondStateMachine绑定状态机看 BOND_NONE/BOND_BONDING/BOND_BONDEDSMP/sm_Security Manager Protocol配对加密过程BTA_Dm底层发现/认证动作举个例子我调试一块新买的兼容 HC-05 模块时logcat 里反复出现btif_dm_remove_bond表面现象是“手机显示已配对但模块一重启就又要确认”。日志说明模块在重启后丢了 link key手机端也没有重新保存成功所以每次都走完整配对流程自然每次都弹窗。解决思路就是固定 PIN 自动输入或者换一个固件保存 link key 更稳定的模块。5.2 dumpsys 查看配对状态确认 bond 到底成没成配对完成后用dumpsys确认 bond 状态是哪里来的“看似配对成功”adb shell dumpsys bluetooth_manager输出里重点看这几行Bonded devices: AA:BB:CC:DD:EE:FF Name: HC-05如果设备出现在Bonded devices列表里说明 bond 已经建立。如果配对后设备不在列表里说明createBond在最后阶段失败了常见原因就是 PIN 没通过或底层安全认证失败。这个时候回去看 HCI 日志最有用。也可以查更细的绑定状态adb shell dumpsys bluetooth_manager | grep -iE bond|state不过 dumpsys 的具体字段在不同 Android 版本差异很大别死记硬背主要是看有没有目标设备地址以及设备状态的文字常量。5.3 HCI snoop 抓包分析最底层也是最可信的证据如果 logcat 和 dumpsys 还看不出问题就得开 HCI snoop 抓底层蓝牙报文了。这个功能在开发者选项里叫“蓝牙 HCI 信息收集日志”或者“Bluetooth HCI snoop log”打开后系统会把蓝牙控制器发出的所有 HCI 包写到文件里。具体操作流程打开开发者选项里的 HCI snoop log 开关。关闭再打开蓝牙让蓝牙协议栈重新初始化。触发一次配对或连接尽量让问题复现。关闭 HCI snoop log。拉取日志文件adb pull /data/misc/bluetooth/logs/部分 ROM 路径会变成/sdcard/btsnoop_hci.log或者/data/log/bluetooth/找不到的话直接adb bugreport然后解压在 zip 里搜btsnoop_hci.log。用 Wireshark 打开这个文件它会自动识别成蓝牙 HCI 报文。新手别被一堆十六进制吓到主要看 Info 列的事件名Authentication Complete带 Status0x00 表示成功其他值要看具体错误码。PIN Code Request说明设备走的是旧版 PIN 配对。IO Capability Request/Response看两端设备的安全能力。Link Key Notificationlink key 生成和保存通知。Encryption Change加密启用通常表示连接已安全建立。最常见的失败是Authentication Complete带Status: Authentication Failure (0x05)对应 HC-05 这类模块就是 PIN 输入错误。你要么去模块手册查默认 PIN要么直接用广播拦截里的setPin(0000)或setPin(1234)试一遍。5.4 从日志到结论常见的三种“反复弹窗”定位结合日志我把最常看到的三种“反复弹窗”归纳如下方便你对号入座现象日志线索结论与处理每次连接都弹确认BOND_NONE - BOND_BONDING反复出现btif_dm_remove_bond没有保存 link key或上层 App 反复调用createBond考虑自动确认或检查 App 逻辑输入 PIN 后失败Authentication Failure (0x05)PIN 错误去设备文档找 PIN或用固定 PIN 自动输入连接后秒断Disconn Complete的 Reason 为 0x08超时或 0x13远端断开距离/干扰/模块供电不足检查信号和电源而不是配对问题日志分析的最大价值是让你把问题归因到“配对认证失败”“link key 未保存”“连接被断开”这三个方向上不用在一个问题上反复绕圈。6. 分享几个实操经验与一个安全警告6.1 哪些设备适合自动确认哪些千万别自动确认配对这件事本质上是把 Android 的安全防线主动降级。我自己的判断标准很简单设备是我自己买的开发模块、固件是可控的、使用环境是封闭的比如家里、工作室、车里就开自动确认设备是公用的、带支付性质的、或者周围人流量大绝对不开。像蓝牙钥匙、智能门锁这类设备每次配对确认都不能省这是防中间人攻击最基础的一环。如果只是想让某个固定 MAC 的设备免弹窗在方案一和方案三里加一个设备地址白名单判断就行代码上也就是一个if (device.address AA:BB:CC:DD:EE:FF)的事。这个习惯值得养成。6.2 临时救急adb 模拟点击的技巧有时候只是临时遇到一个配对框又不想为它装 App可以用开发者的老办法先看控件位置再模拟点击。adb shell uiautomator dump /sdcard/ui.xml adb pull /sdcard/ui.xml在 ui.xml 里找到“配对”按钮的 bounds 属性比如bounds[600,1200][720,1350]中心点大概就是 (660, 1275)然后adb shell input tap 660 1275这个方法适合电脑连着设备、偶尔用一次的临时场景。它不解决“每次都弹”的根源问题但能在你调试的时候少点一下鼠标。6.3 我最终留下来的配置调试环境和日常使用我用的其实是两套配置。开发调试时我用第四章的无障碍自动点击脚本因为要频繁切换设备不想维护一个专门 App 的保活和权限脚本随开随关。长期固定设备比如车里那台老车机我用第二章的广播拦截配一个固定 PIN 和一个白名单长期稳定运行几乎没再被弹窗打断过。另外强调一句不要试图靠删除系统文件或者改 build.prop 之类的方法关掉配对弹窗我早期试过成功率低不说还容易把蓝牙服务搞崩最终都要刷机救砖得不偿失。这次就分享到这儿。折腾蓝牙配对这类系统级问题核心思路就一句先看日志定位原因再选合适的自动化手段安全白名单别偷懒。把这几步走完那个烦人的确认框基本就能从你的世界里消失了。
