蓝牙作为一种短距离无线通信技术,在 Android 设备中应用广泛 ------ 从蓝牙耳机、蓝牙音箱等音频设备,到蓝牙打印机、蓝牙传感器等数据传输设备,再到蓝牙手表等穿戴设备,都依赖蓝牙通讯实现交互。本文将从蓝牙技术基础出发,详解 Android 蓝牙通讯的两种核心模式(经典蓝牙、低功耗蓝牙)及开发实战,帮助开发者快速实现蓝牙设备连接与数据交互。
一、Android 蓝牙技术基础
1.1 蓝牙技术分类与应用场景
Android 支持两种主流蓝牙技术,适用场景差异显著:
|--------------|-------------------------------|------------------------------------|------------------------------------------|
| 蓝牙类型 | 全称 | 核心特点 | 适用场景 |
| 经典蓝牙(BR/EDR) | Basic Rate/Enhanced Data Rate | 传输速率较高(1-3Mbps),通信距离约 10 米,功耗中等 | 音频传输(如蓝牙耳机)、大数据传输(如蓝牙打印机) |
| 低功耗蓝牙(BLE) | Bluetooth Low Energy | 传输速率较低(约 1Mbps),通信距离 10-100 米,功耗极低 | 物联网设备(如温湿度传感器)、穿戴设备(如心率手环)、低频次数据传输(如门禁卡) |
关键区别:经典蓝牙适合 "高带宽、中功耗" 场景,BLE 适合 "低带宽、低功耗" 场景。本文将重点讲解两种模式的开发流程。
1.2 蓝牙通讯核心概念
无论哪种蓝牙类型,通讯过程都涉及以下核心角色:
- 蓝牙适配器(BluetoothAdapter):Android 设备的蓝牙硬件接口,负责开启 / 关闭蓝牙、扫描设备、管理连接。
- 蓝牙设备(BluetoothDevice):远程蓝牙设备的抽象,包含设备地址(MAC 地址)、名称、类型等信息。
- UUID(通用唯一识别码):用于标识蓝牙服务或数据通道(如串口服务 UUID 为00001101-0000-1000-8000-00805F9B34FB)。
- GATT(通用属性配置文件):BLE 通讯的核心协议,通过 "服务(Service)- 特征(Characteristic)" 结构定义数据交互格式(经典蓝牙无需 GATT)。
1.3 蓝牙权限配置
Android 蓝牙开发需在AndroidManifest.xml中声明权限,根据蓝牙类型和系统版本调整:
<!-- 基础蓝牙权限(所有蓝牙功能必需) -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 6.0+ 位置权限(蓝牙扫描需获取设备位置,因早期蓝牙与位置服务关联) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Android 12+ 新增蓝牙权限(替代旧权限) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> <!-- 扫描权限,禁用位置关联 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <!-- 广播权限(BLE作为外设时需用) -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!-- 连接权限 -->
<!-- 声明设备支持蓝牙(可选,用于Google Play过滤) -->
<uses-feature
android:name="android.hardware.bluetooth"
android:required="true" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" /> <!-- BLE可选支持 -->
动态权限申请:Android 6.0+ 需在代码中动态申请危险权限(位置权限、蓝牙连接权限),否则扫描和连接会失败。
二、经典蓝牙(BR/EDR)开发实战
经典蓝牙适合 "一对一数据传输" 场景(如蓝牙串口通讯),开发流程为 "开启蓝牙→扫描设备→配对连接→数据传输→断开连接"。
2.1 核心 API 与工具类
经典蓝牙依赖android.bluetooth包下的类:
- BluetoothAdapter:蓝牙适配器,核心入口;
- BluetoothDevice:远程设备,通过 MAC 地址或名称标识;
- BluetoothSocket:数据传输的 socket(类似 TCP socket);
- BluetoothServerSocket:作为服务端时监听连接的 socket。
2.2 开发流程与代码实现
步骤 1:初始化蓝牙适配器并开启蓝牙
java
public class ClassicBluetoothHelper {
private Context mContext;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothSocket mSocket; // 连接成功后的socket
private OutputStream mOutputStream; // 发送数据
private InputStream mInputStream; // 接收数据
private ReadThread mReadThread; // 接收线程
public ClassicBluetoothHelper(Context context) {
mContext = context;
// 获取蓝牙适配器(设备唯一的蓝牙入口)
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
// 检查并开启蓝牙
public boolean enableBluetooth() {
if (mBluetoothAdapter == null) {
// 设备不支持蓝牙
Toast.makeText(mContext, "设备不支持蓝牙", Toast.LENGTH_SHORT).show();
return false;
}
// 若蓝牙未开启,请求开启
if (!mBluetoothAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
mContext.startActivity(enableIntent);
return false; // 需等待用户授权,授权后通过广播接收结果
}
return true;
}
}
监听蓝牙状态变化:注册广播接收者,监听蓝牙开启 / 关闭状态:
java
// 在Activity中注册广播
private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
if (state == BluetoothAdapter.STATE_ON) {
Toast.makeText(context, "蓝牙已开启", Toast.LENGTH_SHORT).show();
// 蓝牙开启后可开始扫描设备
startScan();
}
}
}
};
// 注册广播
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
步骤 2:扫描并发现设备
扫描设备需通过BluetoothAdapter.startDiscovery(),并注册广播接收扫描结果:
java
// 开始扫描设备
public void startScan() {
// 停止正在进行的扫描(避免重复扫描)
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
// 注册扫描结果广播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
mContext.registerReceiver(mDeviceReceiver, filter);
// 开始扫描(耗时操作,约12秒,需在子线程或异步处理)
mBluetoothAdapter.startDiscovery();
}
// 扫描结果广播接收者
private BroadcastReceiver mDeviceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 从intent中获取发现的设备
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null) {
// 设备名称(可能为null)和MAC地址
String name = device.getName() == null ? "未知设备" : device.getName();
String address = device.getAddress();
Log.d("ClassicBT", "发现设备:" + name + ",MAC:" + address);
// 可将设备添加到列表展示
}
}
}
};
步骤 3:连接设备(客户端模式)
经典蓝牙连接需指定UUID(常用串口 UUID:00001101-0000-1000-8000-00805F9B34FB),连接过程需在子线程中进行(避免阻塞主线程):
java
// 连接指定设备(客户端模式)
public void connectDevice(BluetoothDevice device) {
new Thread(() -> {
try {
// 取消扫描(扫描会占用蓝牙资源,影响连接)
mBluetoothAdapter.cancelDiscovery();
// 通过UUID创建socket(使用串口服务UUID)
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
mSocket = device.createRfcommSocketToServiceRecord(uuid);
// 建立连接(阻塞操作,需在子线程)
mSocket.connect();
// 连接成功,获取读写流
mOutputStream = mSocket.getOutputStream();
mInputStream = mSocket.getInputStream();
// 启动接收线程
mReadThread = new ReadThread();
mReadThread.start();
// 通知UI连接成功
((Activity) mContext).runOnUiThread(() ->
Toast.makeText(mContext, "连接成功", Toast.LENGTH_SHORT).show()
);
} catch (IOException e) {
// 连接失败(常见原因:未配对、UUID错误、设备不支持串口服务)
e.printStackTrace();
((Activity) mContext).runOnUiThread(() ->
Toast.makeText(mContext, "连接失败:" + e.getMessage(), Toast.LENGTH_SHORT).show()
);
// 关闭socket
try {
if (mSocket != null) {
mSocket.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}).start();
}
注意:经典蓝牙连接前通常需要 "配对",部分设备可自动配对,部分需用户输入配对码(配对结果通过BluetoothDevice.ACTION_PAIRING_REQUEST广播通知)。
步骤 4:数据传输(发送与接收)
java
// 发送数据(字节数组)
public void sendData(byte[] data) {
if (mOutputStream == null) {
Toast.makeText(mContext, "未连接设备", Toast.LENGTH_SHORT).show();
return;
}
try {
mOutputStream.write(data);
mOutputStream.flush();
Log.d("ClassicBT", "发送数据:" + new String(data));
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(mContext, "发送失败", Toast.LENGTH_SHORT).show();
}
}
// 接收数据的线程(必须在子线程,避免阻塞)
private class ReadThread extends Thread {
@Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
while (true) {
try {
// 读取数据(阻塞操作,无数据时会等待)
bytes = mInputStream.read(buffer);
if (bytes > 0) {
byte[] received = new byte[bytes];
System.arraycopy(buffer, 0, received, 0, bytes);
// 转换为字符串(根据实际协议解析)
String data = new String(received, StandardCharsets.UTF_8);
Log.d("ClassicBT", "收到数据:" + data);
// 通知UI(通过接口回调或Handler)
notifyDataReceived(data);
}
} catch (IOException e) {
// 读取失败(通常是连接断开)
e.printStackTrace();
break;
}
}
}
}
// 数据接收回调(供UI层处理)
private OnDataReceivedListener mListener;
public interface OnDataReceivedListener {
void onReceived(String data);
}
public void setOnDataReceivedListener(OnDataReceivedListener listener) {
mListener = listener;
}
private void notifyDataReceived(String data) {
if (mListener != null) {
((Activity) mContext).runOnUiThread(() -> mListener.onReceived(data));
}
}
步骤 5:断开连接与资源释放
java
// 断开连接
public void disconnect() {
// 停止接收线程
if (mReadThread != null) {
mReadThread.interrupt();
mReadThread = null;
}
// 关闭流
try {
if (mInputStream != null) {
mInputStream.close();
}
if (mOutputStream != null) {
mOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
// 关闭socket
try {
if (mSocket != null) {
mSocket.close();
mSocket = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
2.3 服务端模式(可选)
若需要 Android 设备作为 "蓝牙服务端"(如接收其他设备连接),需使用BluetoothServerSocket监听连接:
java
// 服务端监听连接(在子线程中执行)
private void startServer() {
new Thread(() -> {
BluetoothServerSocket serverSocket = null;
try {
// 创建服务器socket(UUID需与客户端一致)
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
"ClassicBT_Server", uuid); // 名称用于客户端识别
// 阻塞等待客户端连接(只处理一次连接,如需多连接需循环)
mSocket = serverSocket.accept();
// 连接成功后,关闭serverSocket(单连接模式)
serverSocket.close();
// 后续流程:获取流、启动接收线程(同客户端)
mOutputStream = mSocket.getOutputStream();
mInputStream = mSocket.getInputStream();
mReadThread = new ReadThread();
mReadThread.start();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
三、低功耗蓝牙(BLE)开发实战
BLE 适合 "低功耗、频繁小数据传输" 场景(如传感器数据上报),与经典蓝牙的核心区别是基于 GATT 协议,通过 "服务(Service)" 和 "特征(Characteristic)" 定义数据结构。
3.1 BLE 核心概念与 API
BLE 依赖android.bluetooth.le包下的类,核心概念:
- GATT:通用属性配置文件,定义了设备间数据交互的规则;
- Service:服务,包含多个特征(如 "心率服务" 包含 "心率测量特征");
- Characteristic:特征,数据的最小单元(可读写、通知);
- UUID:服务和特征的唯一标识(如心率测量特征 UUID:00002a37-0000-1000-8000-00805f9b34fb);
- Advertising:BLE 设备广播自身信息(如名称、服务 UUID),供其他设备扫描。
核心 API:
- BluetoothLeScanner:BLE 扫描工具(替代经典蓝牙的BluetoothAdapter扫描);
- BluetoothGatt:GATT 客户端,负责连接和数据交互;
- BluetoothGattCallback:GATT 操作的回调(连接状态、数据接收等)。
3.2 开发流程与代码实现
步骤 1:初始化 BLE 适配器与权限
java
public class BLEHelper {
private Context mContext;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeScanner mLeScanner; // BLE扫描器
private BluetoothGatt mGatt; // GATT客户端
private String mDeviceAddress; // 连接的设备MAC地址
public BLEHelper(Context context) {
mContext = context;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null) {
mLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
}
// 检查BLE支持与权限(需动态申请BLUETOOTH_SCAN等权限)
public boolean checkBLESupport() {
if (mBluetoothAdapter == null) {
Toast.makeText(mContext, "设备不支持蓝牙", Toast.LENGTH_SHORT).show();
return false;
}
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(mContext, "设备不支持BLE", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
}
步骤 2:扫描 BLE 设备
BLE 扫描需使用BluetoothLeScanner,支持设置过滤条件(如只扫描指定服务 UUID 的设备):
java
// 开始扫描BLE设备(需BLUETOOTH_SCAN权限)
public void startBLEScan() {
if (mLeScanner == null) {
return;
}
// 扫描设置(可选:设置扫描周期、过滤条件)
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 低延迟模式(功耗较高)
.build();
// 开始扫描(回调在主线程,扫描结果通过ScanCallback返回)
mLeScanner.startScan(null, settings, mScanCallback);
// 扫描一段时间后自动停止(避免功耗过高)
new Handler(Looper.getMainLooper()).postDelayed(this::stopBLEScan, 10000); // 10秒后停止
}
// 停止扫描
public void stopBLEScan() {
if (mLeScanner != null) {
mLeScanner.stopScan(mScanCallback);
}
}
// 扫描回调
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
if (device == null) return;
// 获取设备信息
String name = device.getName() == null ? "未知设备" : device.getName();
String address = device.getAddress();
int rssi = result.getRssi(); // 信号强度(负值,越接近0信号越强)
Log.d("BLE", "发现设备:" + name + ",MAC:" + address + ",信号:" + rssi);
// 获取设备广播的服务UUID(判断是否是目标设备)
List<ScanRecord> records = Collections.singletonList(result.getScanRecord());
for (ScanRecord record : records) {
List<ParcelUuid> uuids = record.getServiceUuids();
if (uuids != null && uuids.contains(ParcelUuid.fromString(TARGET_SERVICE_UUID))) {
// 发现目标设备,可停止扫描并连接
stopBLEScan();
connectDevice(device);
}
}
}
};
步骤 3:连接设备并发现服务
BLE 连接通过BluetoothGatt实现,连接后需 "发现服务"(获取设备支持的 Service 和 Characteristic):
java
// 连接BLE设备(需BLUETOOTH_CONNECT权限)
public void connectDevice(BluetoothDevice device) {
if (device == null) return;
mDeviceAddress = device.getAddress();
// 连接设备(autoConnect设为false表示立即连接,true表示当设备可用时自动连接)
mGatt = device.connectGatt(mContext, false, mGattCallback);
}
// GATT回调(所有BLE操作的结果都通过此回调返回)
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
// 连接状态变化回调
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// 连接成功,开始发现服务(必须在连接成功后调用)
Log.d("BLE", "连接成功,开始发现服务");
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// 连接断开
Log.d("BLE", "连接断开");
// 可尝试重连
}
}
// 发现服务回调(服务发现成功后调用)
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 服务发现成功,获取目标服务和特征
BluetoothGattService service = gatt.getService(UUID.fromString(TARGET_SERVICE_UUID));
if (service != null) {
// 获取可读写的特征(根据设备协议确定特征UUID)
BluetoothGattCharacteristic characteristic = service.getCharacteristic(
UUID.fromString(TARGET_CHARACTERISTIC_UUID));
if (characteristic != null) {
// 启用特征通知(当特征值变化时,设备主动推送数据)
setCharacteristicNotification(characteristic, true);
}
}
} else {
Log.e("BLE", "服务发现失败,状态码:" + status);
}
}
// 特征值变化回调(设备主动推送数据或读取数据成功时触发)
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
// 读取特征值(设备推送的数据)
byte[] data = characteristic.getValue();
if (data != null) {
String value = new String(data, StandardCharsets.UTF_8);
Log.d("BLE", "收到数据:" + value);
// 通知UI层处理
notifyDataReceived(value);
}
}
};
// 启用特征通知(让设备在数据变化时主动推送)
public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enable) {
if (mGatt == null || characteristic == null) return false;
// 1. 启用本地通知
if (!mGatt.setCharacteristicNotification(characteristic, enable)) {
return false;
}
// 2. 写入CCCD描述符(告知设备允许通知,部分设备需要)
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); // 标准CCCD UUID
if (descriptor != null) {
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
: BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
return mGatt.writeDescriptor(descriptor);
}
return true;
}
步骤 4:数据传输(读写特征值)
BLE 数据传输通过 "读写特征值" 实现:向 Characteristic 写入数据(发送),读取 Characteristic 的值(接收),或通过通知接收设备主动推送的数据。
java
// 向特征写入数据(发送数据)
public boolean writeData(String characteristicUuid, byte[] data) {
if (mGatt == null) return false;
// 获取目标特征
BluetoothGattService service = mGatt.getService(UUID.fromString(TARGET_SERVICE_UUID));
if (service == null) return false;
BluetoothGattCharacteristic characteristic = service.getCharacteristic(
UUID.fromString(characteristicUuid));
if (characteristic == null) return false;
// 设置写入类型(根据设备支持的类型选择)
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
characteristic.setValue(data);
// 写入数据(结果通过onCharacteristicWrite回调返回)
return mGatt.writeCharacteristic(characteristic);
}
// 读取特征值(主动获取数据)
public boolean readData(String characteristicUuid) {
if (mGatt == null) return false;
BluetoothGattService service = mGatt.getService(UUID.fromString(TARGET_SERVICE_UUID));
if (service == null) return false;
BluetoothGattCharacteristic characteristic = service.getCharacteristic(
UUID.fromString(characteristicUuid));
if (characteristic == null) return false;
// 读取数据(结果通过onCharacteristicRead回调返回)
return mGatt.readCharacteristic(characteristic);
}
// 在GATT回调中处理读写结果
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("BLE", "数据写入成功");
} else {
Log.e("BLE", "数据写入失败");
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
byte[] data = characteristic.getValue();
Log.d("BLE", "读取数据:" + new String(data, StandardCharsets.UTF_8));
}
}
步骤 5:断开连接与释放资源
java
// 断开连接并释放资源
public void disconnect() {
if (mGatt != null) {
mGatt.disconnect();
mGatt.close(); // 必须调用close释放资源,否则下次连接可能失败
mGatt = null;
}
mDeviceAddress = null;
}
四、经典蓝牙与 BLE 的对比及选型建议
|-------|------------------|-------------------|
| 对比维度 | 经典蓝牙(BR/EDR) | 低功耗蓝牙(BLE) |
| 功耗 | 中(持续连接时较高) | 极低(适合电池供电设备) |
| 传输速率 | 1-3Mbps(适合大数据) | 约 1Mbps(适合小数据) |
| 通信距离 | 约 10 米 | 10-100 米(视环境而定) |
| 连接方式 | 基于 Socket,类似 TCP | 基于 GATT,服务 - 特征模型 |
| 开发复杂度 | 较低(类似网络 Socket) | 较高(需理解 GATT 协议) |
| 典型应用 | 蓝牙串口、蓝牙耳机、蓝牙打印机 | 传感器、穿戴设备、智能家居 |
选型建议:
- 传输音频或大数据(如图片、文件)→ 经典蓝牙;
- 低功耗需求(如电池供电的传感器)→ BLE;
- 需频繁小数据交互(如温湿度上报)→ BLE;
- 设备需兼容旧蓝牙协议(如传统蓝牙串口设备)→ 经典蓝牙。
五、常见问题及解决方案
5.1 扫描不到设备
- 原因:未开启蓝牙、缺少权限(位置权限、蓝牙扫描权限)、设备未处于可发现模式、设备距离过远。
- 解决:
- 确认蓝牙已开启,权限已授予(尤其是 Android 12 + 的 BLUETOOTH_SCAN 权限);
- 经典蓝牙需确保设备处于 "可发现模式"(在蓝牙设置中勾选 "允许被发现");
- BLE 设备需正在广播(部分设备休眠时停止广播);
- 缩短设备距离,避开遮挡物。
5.2 连接失败(经典蓝牙)
- 原因:设备未配对、UUID 不匹配、设备不支持目标服务(如串口服务)、蓝牙被占用。
- 解决:
- 手动配对设备(在系统蓝牙设置中完成配对);
- 确认使用正确的 UUID(串口设备常用00001101-...);
- 关闭其他占用蓝牙的应用(如音乐播放器);
- 检查设备是否支持 RFCOMM 协议(经典蓝牙数据传输依赖此协议)。
5.3 BLE 连接后无法发现服务
- 原因:连接未稳定就调用discoverServices、设备不支持 GATT 服务、蓝牙信号弱导致连接中断。
- 解决:
- 确保discoverServices在onConnectionStateChange回调中连接成功后调用;
- 重试连接(BLE 连接稳定性较差,可多次尝试);
- 靠近设备,确保信号强度(RSSI > -80dBm)。
5.4 数据传输不完整或丢失
- 原因:数据长度超过 MTU(最大传输单元)、未启用通知、写入类型不匹配。
- 解决:
- BLE 需协商 MTU(调用requestMtu设置更大的 MTU,如 512 字节);
- 确认启用特征通知(setCharacteristicNotification);
- 经典蓝牙确保OutputStream正确flush,BLE 使用正确的写入类型(如WRITE_TYPE_NO_RESPONSE适合批量数据)。
六、总结
Android 蓝牙通讯开发需根据场景选择技术类型:经典蓝牙适合高带宽数据传输,BLE 适合低功耗小数据交互。核心开发要点:
- 权限处理:动态申请位置权限和蓝牙权限,尤其是 Android 12 + 的新增权限;
- 线程管理:扫描、连接、数据读写等耗时操作必须在子线程执行,避免 ANR;
- 回调处理:所有蓝牙操作结果通过广播或回调返回,需正确处理连接状态和数据交互;
- 资源释放:断开连接后必须关闭 socket 或 Gatt,否则会导致资源泄漏和下次连接失败。
实际开发中,建议结合设备手册确认 UUID、数据格式和通讯协议(如传感器的数据包结构),并做好异常处理(重试机制、断连重连),以提升蓝牙通讯的稳定性。无论是经典蓝牙还是 BLE,掌握其核心 API 和协议逻辑后,都能快速实现设备间的无线数据交互。