Android蓝牙开发源码解析:经典蓝牙与BLE的完整实践指南

发布时间:2026/9/7 18:52:44
Android蓝牙开发源码解析:经典蓝牙与BLE的完整实践指南 简介面向 Android 蓝牙开发学习者与物联网应用开发者这份源码压缩包聚焦设备扫描、连接与数据传输等核心环节完整演示 BluetoothManager、BluetoothAdapter、BLE 扫描回调以及 BluetoothGatt 等关键 API 的实际用法。资源共 23 个文件约 91KB以 Java 源码、class 编译文件、AndroidManifest.xml 等配置资源为主同时包含可直接安装验证的 apk 与界面 png 截图便于对照源码理解扫描模式、权限配置和 GATT 通信流程。已有 208 人学习下载。通过该示例工程读者可以快速掌握 startScan 触发扫描、ScanCallback 处理结果、connectGatt 建立连接等编写方式并了解不同 Android 版本下扫描限制与兼容性处理适合作为蓝牙功能开发入门或项目排错的参考。 聊到android蓝牙开发源码很多人的第一反应就是权限难配、机型难调、调试起来各种玄学。我早期做蓝牙项目的时候也踩过不少坑——搜不到设备、连接秒断、配对弹窗不出现、A2DP和SCO切换把音频搞丢这些问题一个比一个让人头大。后来把源码层面的调用链路捋清楚了才发现大部分问题其实都有固定套路可以解。这篇东西适合谁看正好在做Android蓝牙开发、想通过源码项目快速上手的朋友也适合那些被经典蓝牙和BLE两种协议栈绕晕的嵌入式或应用层开发。我会从源码结构、API调用、协议栈差异、常见兼容性坑这几个维度来讲尽量给你一套可以直接拿去用的思路。1. 立项前先想清楚经典蓝牙还是低功耗蓝牙1.1 核心源码结构与应用场景很多人拿到“android蓝牙开发源码”这个需求就开始搜代码其实第一步应该先确认你要做的是经典蓝牙BR/EDR还是低功耗蓝牙BLE。这俩在Android源码体系里走的完全是两套API混着用会非常痛苦。经典蓝牙对应的是传统蓝牙2.0/3.0时代的协议栈走的是BluetoothSocket的RFCOMM通道典型场景就是蓝牙聊天、文件传输、蓝牙串口透传、车机连接。它的特点是配对流程完整、数据吞吐量大、连接建立后是持续链路但功耗高、连接速度慢。低功耗蓝牙BLE走的是GATTGeneric Attribute Profile协议代码里都是BluetoothGatt、BluetoothGattCallback这套东西。它的特点是扫描、连接、通信的机制和经典蓝牙完全不同功耗低适合做手环、体温计、beacon这类需要长时间待机的设备。源码结构上建议把项目按功能模块拆开扫描模块、连接管理模块、协议解析模块、UI交互模块。别把蓝牙逻辑全堆在Activity里否则后面调试到想哭。1.2 技术选型背后的取舍逻辑选哪种方案不只是看设备支持什么还要看你自己的开发成本和维护成本。我见过不少项目明明用BLE就够了却因为硬件采购时买了个只支持经典蓝牙的模块导致整个软件架构都得迁就它。经典蓝牙的优点是稳定、通用几乎所有Android手机都支持和PC、车机、耳机的兼容性也更好。缺点也明显权限要求严格从Android 12开始模糊定位权限必须动态申请扫描结果还要处理一堆厂商定制ROM的差异。BLE的优势是灵活、开发模型清晰scan → connect → discoverService → 读写characteristic这套流程在各种平台上都很统一。缺点是要处理MTU协商、分包收发、连接间隔这些底层参数刚开始会有点懵。我在实际项目里通常这样判断如果设备需要持续传音频或大数据流选经典蓝牙如果只是传传感器数据、控制指令这种小数据包选BLE。这个决定直接影响后面源码怎么写所以放在第一步。2. 从源码层面拆解Android蓝牙API链路2.1 蓝牙权限配置的这些坑看一份蓝牙源码我一般先看AndroidManifest这里能看出作者对系统版本适配有没有经验。基础权限是三件套但不同Android版本权限模型差距很大uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION /Android 12API 31及以上需要把targetSdkVersion提上去并声明BLUETOOTH_SCAN、BLUETOOTH_CONNECT、BLUETOOTH_ADVERTISE这三个运行时权限。很多人在这里翻车明明权限都写了扫描却返回空列表原因就是没在代码里动态申请权限或者忘了同时申请定位权限。还有一个容易忽略的点部分国产ROM比如MIUI、EMUI对定位权限还有额外的“定位服务”总开关要求即使你在代码里申请了ACCESS_FINE_LOCATION用户在系统设置里把定位服务关了同样扫不到设备。源码里最好加一个引导用户开启定位服务的逻辑省得测试的时候莫名其妙。2.2 经典蓝牙连接的核心代码走读经典蓝牙的开发模型不复杂核心就是四步获取BluetoothAdapter、开启蓝牙、扫描设备、通过BluetoothSocket连接。但源码里的细节决定了稳定性。先看适配器获取和蓝牙开启BluetoothAdapter bluetoothAdapter BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter null) { // 设备不支持蓝牙 return; } if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }这段代码看着简单但有两点值得注意。第一getDefaultAdapter()在Android 13以下的某些机型上可能返回空需要判空。第二ACTION_REQUEST_ENABLE这个弹窗在部分定制ROM上会被系统拦截不会弹出所以源码里最好同时保留直接调用bluetoothAdapter.enable()降级方案。然后是扫描bluetoothAdapter.startDiscovery();广播接收器里处理BluetoothDevice.ACTION_FOUND。很多人会在这里遇到两个典型问题一是重复扫描导致广播风暴解决办法是加一个set去重二是扫描过程中去连接设备会失败因为Discovery会占用蓝牙资源源码里最好在连接前先调用stopDiscovery()再cancelDiscovery()。连接这块经典蓝牙要用BluetoothSocket。服务端和客户端的创建方式不一样客户端通过device.createRfcommSocketToServiceRecord(uuid)创建服务端通过listenUsingRfcommWithServiceRecord(name, uuid)监听。UUID必须两端一致这个看似基础却很容易踩坑尤其是从网上抄来的源码UUID经常是错的或者被系统占用。2.3 BLE扫描与连接的关键代码BLE的源码写法和经典蓝牙完全不同核心是ScanFilter和ScanCallback。Android 6.0以上推荐用BluetoothLeScanner替代startLeScan因为前者支持批量扫描、更省电BluetoothLeScanner scanner bluetoothAdapter.getBluetoothLeScanner(); ScanFilter filter new ScanFilter.Builder() .setServiceUuid(new ParcelUuid(UUID.fromString(0000ffe0-0000-1000-8000-00805f9b34fb))) .build(); ScanSettings settings new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build(); scanner.startScan(Collections.singletonList(filter), settings, scanCallback);这里要记住一个原则扫描结束后必须停掉不然蓝牙会一直处于高频工作状态不仅耗电还可能导致连接不稳定。我在代码里习惯在onPause()和onDestroy()里都调一次stopScan(scanCallback)双重保障。BLE连接后不能直接发数据要先走GATT流程connectGatt → onConnectionStateChange → discoverServices → 拿到BluetoothGattService和Characteristic。写数据时还要注意writeCharacteristic的返回值Android 13以上推荐用writeCharacteristic(characteristic, value, writeType)这个新接口旧接口在targetSdk 33以上会报错或者被忽略。3. 高频场景源码剖析蓝牙聊天、测距与A2DP切SCO3.1 蓝牙设备搜索与配对源码走读搜索设备这块经典蓝牙用startDiscoveryBLE用startScan这两条路线都要处理设备信息回调。我看到很多初学者直接把所有设备都罗列出来不做过滤结果界面上全是各种奇奇怪怪的MAC地址。建议源码里加三层过滤rssi强度过滤、设备名称过滤、广播数据过滤。尤其是BLE设备广播包里通常会带厂商ID或服务UUID通过ScanFilter可以有效减少无用设备。配对过程一般走系统UI代码里只需要注册ACTION_BOND_STATE_CHANGED广播监听配对状态变化。这里有个经验某些设备配对弹窗不出现可能是设备名称里有中文导致系统UI崩溃或者设备本身在可发现模式超时窗口之外。源码里配对前先调用createBond()如果返回false就得考虑设备是否已经配对了BOND_BONDED。3.2 蓝牙测距的实现思路蓝牙测距是蓝牙开发中很常见又很难做准的功能。BLE测距主要通过RSSI接收信号强度来估算距离公式一般是distance 10 ^ ((measuredPower - rssi) / (10 * n))这里measuredPower是设备在1米处的参考RSSI值n是环境衰减因子通常取2~4。实际项目里n很难固定因为水泥墙、人体遮挡、WiFi信号都会影响结果。从源码层面看测距不是靠蓝牙协议栈本身提供的而是靠应用层对RSSI的连续采样和滤波。我常用的做法是滑动窗口滤波取最近N次扫描的RSSI去掉最大最小值后求平均再做平滑。如果直接拿单次扫描的RSSI去算距离数据波动会非常大根本没法用。再补充一点经典蓝牙的RSSI默认只返回一个区间比如0~255的档位并不适合做精确测距。现在主流做法都是走BLE的广播包或者连接后的RSSI读取。3.3 A2DP切SCO模式的产品逻辑平时大家听歌、看视频走的是A2DPAdvanced Audio Distribution Profile音质好是双向高速音频流。但一旦蓝牙耳机进入通话模式系统会自动切到SCOSynchronous Connection Oriented链路这种链路是窄带语音音质明显变差。源码层面这个切换一般不是你主动调的而是系统音频策略根据AudioManager模式变化自动触发的。但开发“蓝牙耳机类App”或者“车机互联App”时经常需要主动感知当前是A2DP还是SCO状态。可以通过AudioDeviceInfo判断当前输出设备类型AudioManager audioManager (AudioManager) getSystemService(Context.AUDIO_SERVICE); AudioDeviceInfo[] devices audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); for (AudioDeviceInfo device : devices) { if (device.isSink()) { int type device.getType(); // TYPE_BLUETOOTH_A2DP 或 TYPE_BLUETOOTH_SCO } }还有一种情况是App需要强制走SCO来录制蓝牙耳机麦克风的声音比如微信语音、对讲机类应用。这时候要通过startBluetoothSco()启动SCO连接并设置AudioManager.MODE_IN_COMMUNICATION。这里有一个隐藏问题startBluetoothSco()在Android 8.0以后的行为有所变化部分机型必须先注册BluetoothProfile的A2DP和HEADSET监听等profile连接就绪后再启动SCO否则SCO建立失败。4. 驱动与系统兼容性实战常见问题排查4.1 CSR8510 A10这类USB蓝牙适配器的问题看到搜索词里有人关注CSR8510 A10蓝牙驱动这个是PC上USB蓝牙适配器的经典方案和Android开发直接关系不大但Android开发者在做嵌入式或外设联调时经常会碰见它。它的核心问题是Windows系统自带的驱动能用但功能不全需要装厂商驱动才能开启蓝牙耳机、低功耗蓝牙广播等高级功能。如果在开发中遇到“设备管理器有generic adapter但无法启用”这类报错大概率是驱动版本不兼容或者蓝牙服务被禁用。解决思路是先卸载驱动去芯片厂商官网下载对应版本驱动再在设备管理器里重新扫描硬件改动。注意CSR Harmony驱动对Windows 10/11支持一般必要时得用兼容模式安装。4.2 Android系统蓝牙协议栈差异Android的蓝牙协议栈从4.2开始从BlueZ换成了Bluedroid后来是Fluoride不同厂商又在这个基础上做了大量定制。这就导致一个问题同一份源码在Pixel上跑得好好的换到某国产手机上就搜不到设备。从实践角度我建议在源码里建立一套针对ROM差异的适配层。比如扫描策略上三星要延迟开始扫描小米需要先申请定位权限再调用扫描华为部分机型在系统设置里关闭“蓝牙扫描优化”后扫描结果会不同。把这些判断都封装成独立的策略类避免散落在业务代码里。另外调试蓝牙不要只连一台手机。我的习惯是至少准备一台高通芯片手机、一台联发科芯片手机、一台Pixel原生系统手机三个平台的协议栈实现差异够你把兼容性坑踩完一遍。4.3 从源码角度解决设备兼容问题设备兼容问题的本质是协议实现不标准。很多硬件厂商的BLE设备只实现了部分GATT服务甚至characteristic属性写得不标准比如需要writeTypeWRITE_TYPE_NO_RESPONSE却声明成WRITE_TYPE_DEFAULT。遇到这种情况源码里要做容错处理先读Characteristic的getProperties()通过PROPERTY_WRITE_NO_RESPONSE判断用哪种写入方式。对于只支持Indicate不支持Notify的设备要在setCharacteristicNotification之后再把CCCDClient Characteristic Configuration Descriptor的value写成0x0002而不是0x0001。有些设备连接成功后会主动断开可能是连接参数协商失败比如它期望的连接间隔不在你发送的范围内。这些坑不踩一遍很难记住踩完之后最好的沉淀方式就是把这些异常处理逻辑写进源码的公共模块里下次接新设备直接复用。5. 我的几点调试心法最后分享几个我说了很多遍的经验。第一日志一定要打好。建议在源码里统一封装一个BluetoothLog类把连接状态、扫描结果、GATT回调、错误码全部打出来。蓝牙调试最怕的就是黑盒操作有了完整日志90%的问题都能定位到具体环节。第二用Android Studio的Logcat按进程过滤。不要开一堆App同时连蓝牙实测下来多App抢占蓝牙会造成大量连接失败。第三把抓包也纳入调试工具链。如果条件允许可以抓一下蓝牙HCI日志Android开发者选项里有“蓝牙HCI日志”开关抓完用Wireshark打开能看清楚协议栈里的每一步交互。这个对排查A2DP切SCO、GATT错误码之类的问题特别好用。第四硬件层面的问题要单独排查。有时候问题不在源码而是模块本身的天线设计太差RSSI低到没法看。遇到这种情况在源码里加一套RSSI阈值告警设备距离稍远就提示“信号弱”体验会好很多。做Android蓝牙开发源码只是起点。真正决定项目成败的是你对协议栈的理解、对设备差异的容忍以及对日志和测试的重视程度。希望这篇能帮你少走点弯路。本文还有配套的精品资源点击获取