Android蓝牙通讯:从基础实战到技术对比,附完整代码实例
引言:Android 蓝牙通讯的应用场景与本文核心价值

在移动开发的广袤领域中,蓝牙技术宛如一座桥梁,让 Android 设备与外部世界建立起便捷的无线连接。从我们手腕上实时监测健康数据的智能穿戴设备,到家中通过手机就能远程操控的智能家电,再到工业场景里用于设备间数据交互的蓝牙串口通信,蓝牙的身影无处不在,已然成为 Android 生态中不可或缺的一环。
在智能穿戴领域,智能手表、手环借助蓝牙与手机相连,将运动步数、心率、睡眠质量等数据实时同步到手机 App,让用户对自己的健康状况了如指掌;智能家居场景下,蓝牙让手机与智能灯泡、插座、门锁等设备实现通信,用户轻触手机屏幕就能控制家中设备,打造舒适便捷的生活环境;而在工业及物联网领域,蓝牙串口通信常用于设备调试、数据采集传输,为自动化生产、环境监测等提供稳定的数据交互支持 。
一、 Android 蓝牙开发基础准备
1.1 蓝牙开发核心权限配置(分版本适配)
在 Android 蓝牙开发的旅程中,权限配置是首要跨越的关卡,且不同 Android 版本有着各自独特的权限需求 。 在 Android 6.0(API 级别 23)之前,权限在应用安装时便会被一次性授予,开发者仅需在AndroidManifest.xml文件中静态声明蓝牙相关权限,如:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
BLUETOOTH权限允许应用连接已配对的蓝牙设备,而BLUETOOTH_ADMIN权限则赋予应用发现和配对新蓝牙设备的能力,这两个权限属于普通权限,安装时自动获取。 但从 Android 6.0 起,运行时权限机制登场,蓝牙扫描因可能涉及用户位置隐私,被纳入位置权限管理范畴。此时,除了上述基本蓝牙权限,还需动态申请位置权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
在代码中,通过如下方式动态申请权限:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_LOCATION_PERMISSION);
}
其中REQUEST_LOCATION_PERMISSION是自定义的请求码,用于在权限请求回调中识别请求来源。 当 Android 12(API 级别 31)到来,蓝牙权限体系再次更新,引入BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限 。BLUETOOTH_SCAN用于扫描附近蓝牙设备,BLUETOOTH_CONNECT用于连接蓝牙设备,并且对于BLUETOOTH_SCAN,若不涉及位置访问,需声明usesPermissionFlags="neverForLocation" 。权限申请代码如下:
List<String> permissionList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissionList.add(Manifest.permission.BLUETOOTH_SCAN);
permissionList.add(Manifest.permission.BLUETOOTH_CONNECT);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
}
ActivityCompat.requestPermissions(this,
permissionList.toArray(new String[0]),
REQUEST_BLUETOOTH_PERMISSION);
若权限缺失,扫描时可能无法发现设备,连接功能也会受阻,导致蓝牙通讯无法正常开展。因此,全面了解并适配各版本权限要求,是开启蓝牙开发大门的关键。
1.2 核心 API 与关键类解析
在 Android 蓝牙开发的领域中,掌握核心 API 与关键类是构建稳定高效蓝牙应用的基石。 BluetoothAdapter堪称整个蓝牙开发的 “总入口”,它代表着设备的本地蓝牙适配器,通过BluetoothAdapter.getDefaultAdapter()即可获取其实例。这个类承担着诸多重要职责,比如控制蓝牙的开关状态,通过isEnabled()方法可检查蓝牙是否已开启,若未开启,可借助如下代码发起开启请求:
if (!bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
它还负责设备发现、配对及连接管理等关键任务,如调用startDiscovery()方法便能启动蓝牙设备搜索流程。 BluetoothDevice类则用于表示一个远程蓝牙设备,可通过BluetoothAdapter的设备发现功能获取。每个BluetoothDevice都有其独特的地址和名称,通过getAddress()和getName()方法即可获取相应信息。在建立蓝牙连接时,BluetoothDevice是必不可少的参数,不同类型的蓝牙连接(经典蓝牙或低功耗蓝牙)都会涉及到这个类 。
对于经典蓝牙的数据传输,BluetoothSocket发挥着核心作用。它为两台蓝牙设备之间提供了一个全双工的流连接,类似于网络编程中的套接字。通过BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法可创建一个BluetoothSocket实例,其中UUID(通用唯一识别码)用于标识蓝牙服务,不同的蓝牙应用场景对应着不同的UUID,如常见的串口通信服务就有特定的UUID。建立连接后,就可通过BluetoothSocket的getInputStream()和getOutputStream()方法进行数据的读取和写入操作 。
而在低功耗蓝牙(BLE)开发中,BluetoothGatt类是核心。它基于通用属性配置文件(GATT)协议,用于与 BLE 设备进行通信。通过BluetoothDevice的connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)方法可创建一个BluetoothGatt连接,其中callback用于监听连接状态变化、服务发现、数据读写等事件 。例如,在连接成功后,可调用discoverServices()方法发现 BLE 设备提供的服务,这些服务由BluetoothGattService类表示,每个服务又包含多个特征(BluetoothGattCharacteristic),特征是数据传输的基本单位,通过readCharacteristic(BluetoothGattCharacteristic characteristic)和writeCharacteristic(BluetoothGattCharacteristic characteristic)方法可实现对特征值的读写操作,从而完成与 BLE 设备间的数据交互 。
二、 经典蓝牙(BR/EDR)开发
2.1 蓝牙开启与设备兼容性检测
在 Android 中,要开启蓝牙并检测设备兼容性,首先需要获取BluetoothAdapter实例,这是与蓝牙硬件交互的关键入口 。
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter == null) {
// 设备不支持蓝牙
Toast.makeText(this, "设备不支持蓝牙", Toast.LENGTH_SHORT).show();
return;
}
上述代码通过BluetoothManager获取BluetoothAdapter,若返回null,则表明设备不支持蓝牙功能,直接提示用户并终止相关操作 。 接下来,检查蓝牙是否已开启,若未开启,通过Intent请求用户开启蓝牙:
if (!bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
// 蓝牙已开启,可进行后续操作
}
其中REQUEST_ENABLE_BT是自定义的请求码,用于在onActivityResult方法中处理用户的响应 。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
// 用户允许开启蓝牙
Toast.makeText(this, "蓝牙已开启", Toast.LENGTH_SHORT).show();
} else {
// 用户拒绝开启蓝牙
Toast.makeText(this, "用户拒绝开启蓝牙", Toast.LENGTH_SHORT).show();
}
}
}
为了实时监听蓝牙状态变化,还需注册广播接收器:
private final BroadcastReceiver bluetoothStateReceiver = 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.ERROR);
switch (state) {
case BluetoothAdapter.STATE_ON:
// 蓝牙已开启,可执行更多操作
break;
case BluetoothAdapter.STATE_OFF:
// 蓝牙已关闭,可提示用户或进行相关清理
break;
}
}
}
};
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(bluetoothStateReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(bluetoothStateReceiver);
}
在onResume中注册广播接收器监听蓝牙状态变化,在onPause中注销,避免内存泄漏 。通过这些步骤,可确保蓝牙功能在设备上正常初始化,为后续的设备扫描与连接操作奠定基础 。
2.2 蓝牙设备扫描与发现(已配对 + 未配对)
在经典蓝牙开发中,设备扫描与发现是连接其他蓝牙设备的重要前置步骤,可分为获取已配对设备和扫描未配对设备两个部分 。 获取已配对设备相对简单,通过BluetoothAdapter的getBondedDevices方法即可获取已配对设备的集合:
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
String deviceName = device.getName();
String deviceAddress = device.getAddress();
// 处理已配对设备,如展示在列表中
Log.d("Bluetooth", "已配对设备: " + deviceName + " – " + deviceAddress);
}
} else {
Log.d("Bluetooth", "没有已配对的设备");
}
上述代码遍历已配对设备集合,获取每个设备的名称和地址,并进行相应处理,如记录日志或展示在 UI 上 。 而扫描未配对设备则需要调用startDiscovery方法,并通过广播接收器监听ACTION_FOUND广播来获取设备信息 。首先,在扫描前需停止正在进行的扫描任务(如果有),以避免资源浪费和冲突:
if (bluetoothAdapter.isDiscovering()) {
bluetoothAdapter.cancelDiscovery();
}
bluetoothAdapter.startDiscovery();
接着,注册广播接收器:
private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null) {
String deviceName = device.getName();
String deviceAddress = device.getAddress();
// 处理新发现的未配对设备
Log.d("Bluetooth", "新发现设备: " + deviceName + " – " + deviceAddress);
}
}
}
};
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(discoveryReceiver, filter);
在广播接收器中,当接收到ACTION_FOUND广播时,从Intent中提取BluetoothDevice对象,获取设备名称和地址并处理 。需要注意的是,在使用完广播接收器后,应及时在onDestroy或onPause中注销,防止内存泄漏:
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(discoveryReceiver);
}
通过这两个步骤,即可实现对已配对和未配对蓝牙设备的扫描与发现,为后续的配对与连接操作提供设备列表 。
2.3 配对与连接实现(服务端 + 客户端)
经典蓝牙采用客户端 / 服务器(C/S)架构进行连接,服务端负责监听连接请求,客户端主动发起连接 。 服务端实现时,需创建一个线程AcceptThread来监听连接 。首先定义一个 UUID 用于标识服务,不同的蓝牙应用场景对应不同的 UUID,这里以串口通信服务为例:
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
然后在AcceptThread中通过listenUsingRfcommWithServiceRecord方法监听连接:
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null;
try {
tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord("BluetoothChat", MY_UUID);
} catch (IOException e) {
Log.e("Bluetooth", "Socket's listen() method failed", e);
}
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e("Bluetooth", "Socket's accept() method failed", e);
break;
}
if (socket != null) {
// 处理连接,如启动数据传输线程
manageConnectedSocket(socket);
try {
mmServerSocket.close();
} catch (IOException e) {
Log.e("Bluetooth", "Could not close the connect socket", e);
}
break;
}
}
}
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) {
Log.e("Bluetooth", "Could not close the server socket", e);
}
}
}
客户端发起连接时,同样基于createRfcommSocketToServiceRecord方法创建BluetoothSocket,并使用相同的 UUID :
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
BluetoothSocket tmp = null;
mmDevice = device;
try {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e("Bluetooth", "Socket's create() method failed", e);
}
mmSocket = tmp;
}
public void run() {
bluetoothAdapter.cancelDiscovery();
try {
mmSocket.connect();
} catch (IOException connectException) {
try {
mmSocket.close();
} catch (IOException closeException) {
Log.e("Bluetooth", "Could not close the client socket", closeException);
}
return;
}
// 连接成功,处理数据传输
manageConnectedSocket(mmSocket);
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e("Bluetooth", "Could not close the client socket", e);
}
}
}
在配对过程中,系统会弹出配对请求弹窗,用户确认后完成配对 。连接状态可通过BluetoothSocket的isConnected方法监听,还可注册广播接收器监听蓝牙连接状态变化 。通过上述服务端和客户端代码,可实现经典蓝牙设备间的配对与连接,为数据传输搭建通道 。
2.4 数据传输与交互(输入输出流)
当经典蓝牙设备成功建立连接后,便可以通过BluetoothSocket获取输入输出流进行数据传输 。 首先获取BluetoothSocket实例,假设已在manageConnectedSocket方法中得到:
private void manageConnectedSocket(BluetoothSocket socket) {
try {
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 启动读写线程
new ReadThread(inputStream).start();
new WriteThread(outputStream).start();
} catch (IOException e) {
Log.e("Bluetooth", "Failed to get input/output streams", e);
}
}
在ReadThread中,通过InputStream读取数据,为避免阻塞主线程,需在独立线程中处理:
private class ReadThread extends Thread {
private final InputStream mmInStream;
public ReadThread(InputStream inStream) {
mmInStream = inStream;
}
public void run() {
byte[] buffer = new byte[1024];
int bytes;
while (true) {
try {
bytes = mmInStream.read(buffer);
if (bytes > 0) {
String receivedMessage = new String(buffer, 0, bytes);
// 处理接收到的消息,如更新UI
Log.d("Bluetooth", "Received: " + receivedMessage);
}
} catch (IOException e) {
Log.e("Bluetooth", "Input stream was disconnected", e);
break;
}
}
}
}
WriteThread则通过OutputStream发送数据:
private class WriteThread extends Thread {
private final OutputStream mmOutStream;
public WriteThread(OutputStream outStream) {
mmOutStream = outStream;
}
public void run() {
String message = "Hello, Bluetooth!";
try {
mmOutStream.write(message.getBytes());
} catch (IOException e) {
Log.e("Bluetooth", "Error sending message", e);
}
}
}
实际应用中,可能需要传输更复杂的数据结构,如字节数组 。传输字节数组时,可直接通过OutputStream的write(byte[] buffer)方法发送,在接收端按相同的方式读取并处理 。在数据传输过程中,务必注意异常捕获,如连接断开、流操作失败等情况 。若出现异常,应及时关闭流和连接,尝试重新连接或进行错误提示 。对于大数据量传输,还可考虑实现断点续传机制,确保数据完整传输 。通过这些输入输出流的操作,可实现经典蓝牙设备间稳定的数据交互 。
2.5 连接断开与资源释放
当完成蓝牙数据传输或不再需要蓝牙连接时,正确断开连接并释放资源至关重要,这不仅能避免内存泄漏,还能让系统资源得到合理利用 。 首先,在断开连接时,需关闭BluetoothSocket及其关联的输入输出流 。假设在manageConnectedSocket方法中管理连接,可添加如下断开连接逻辑:
private void manageConnectedSocket(BluetoothSocket socket) {
// 数据传输代码…
// 断开连接逻辑
try {
socket.getOutputStream().close();
socket.getInputStream().close();
socket.close();
} catch (IOException e) {
Log.e("Bluetooth", "Failed to close socket and streams", e);
}
}
上述代码依次关闭输出流、输入流和BluetoothSocket,在捕获到IOException时记录错误日志 。若在服务端使用了BluetoothServerSocket,也需在合适的时机关闭,如在服务端线程结束时:
private class AcceptThread extends Thread {
//…
public void run() {
//…
if (socket != null) {
manageConnectedSocket(socket);
try {
mmServerSocket.close(); // 关闭BluetoothServerSocket
} catch (IOException e) {
Log.e("Bluetooth", "Could not close the server socket", e);
}
break;
}
}
//…
}
此外,在 Activity 的生命周期中,应妥善处理广播接收器和线程资源 。在onDestroy方法中,注销之前注册的广播接收器,如设备扫描和蓝牙状态监听的接收器:
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(discoveryReceiver);
unregisterReceiver(bluetoothStateReceiver);
// 停止相关线程
// 假设在类中定义了AcceptThread和ConnectThread实例
if (acceptThread != null) {
acceptThread.cancel();
acceptThread = null;
}
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
}
通过上述步骤,在连接断开时关闭相关流和套接字,在 Activity 销毁时注销广播接收器和停止线程,确保蓝牙相关资源得到彻底释放,让应用在蓝牙连接管理上更加健壮和高效 。
三、 低功耗蓝牙(BLE)开发
3.1 BLE 核心概念解析(GATT/Service/Characteristic)
低功耗蓝牙(BLE)以其卓越的低功耗特性,在智能穿戴、传感器等领域大放异彩 。与经典蓝牙相比,BLE 采用了更加高效的连接和数据传输机制,它在非工作状态下能够迅速进入低功耗模式,仅在需要传输数据时短暂唤醒,极大地降低了能耗,这使得智能手表、手环等设备能以较小的电池维持长时间运行 。例如,智能手表借助 BLE 技术,可在一次充电后续航数天甚至数周,持续监测用户的心率、睡眠等健康数据,并通过蓝牙实时同步至手机 。在智能家居传感器场景中,温湿度传感器、门窗传感器等通过 BLE 与网关通信,以极低的功耗运行,实现长期稳定的数据采集与传输,为用户打造智能、节能的生活环境 。
在 BLE 的技术体系中,通用属性配置文件(GATT)协议是其数据交互的基石 。GATT 构建了一个基于客户端 – 服务器的架构,其中 BLE 设备作为服务器,持有数据;与之连接的 Android 设备充当客户端,发起数据请求 。服务(Service)和特征(Characteristic)是 GATT 协议中的核心概念,它们共同构成了 BLE 设备的数据组织结构 。
服务(Service)可看作是 BLE 设备功能的逻辑分组,每个服务都有一个唯一的 UUID(通用唯一识别码),用于标识其功能。例如,心率服务(UUID 为 0x180D)专门用于管理与心率监测相关的数据;电池服务(UUID 为 0x180F)则负责处理电池电量信息 。一个 BLE 设备可能包含多个服务,以满足不同的功能需求,如智能手环可能同时具备心率服务、运动数据服务、睡眠监测服务等 。
特征(Characteristic)是服务的基本数据单元,每个特征同样拥有唯一的 UUID,且具备不同的属性,如可读、可写、可通知等 。这些属性决定了数据的传输方向和方式 。例如,心率特征(UUID 为 0x2A37)通常设置为可读和可通知属性,Android 设备可通过读取操作获取当前心率值,当心率数据发生变化时,BLE 设备还能主动通过通知机制将新数据推送给 Android 设备,无需 Android 设备频繁轮询读取,进一步降低了功耗和数据传输量 。再如,某些智能灯泡的控制特征,设置为可写属性,Android 设备通过向该特征写入控制指令(如亮度值、颜色值),即可实现对灯泡的远程控制 。
3.2 BLE 设备扫描(startLeScan 与 ScanCallback)
在 BLE 开发中,设备扫描是连接目标设备的首要步骤,Android 提供了多种方式实现这一功能 。在早期的 Android 版本中,常使用startLeScan方法进行 BLE 设备扫描 。首先获取BluetoothAdapter实例,这是与蓝牙硬件交互的关键入口:
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
// 设备不支持蓝牙
return;
}
然后调用startLeScan方法,并传入LeScanCallback回调:
BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
// 处理扫描到的设备
String deviceName = device.getName();
String deviceAddress = device.getAddress();
Log.d("BLE Scan", "发现设备: " + deviceName + " – " + deviceAddress);
}
};
bluetoothAdapter.startLeScan(leScanCallback);
在上述代码中,onLeScan方法会在每次发现 BLE 设备时被调用,通过BluetoothDevice对象可获取设备的名称、MAC 地址等信息,rssi表示信号强度,scanRecord包含设备的广播数据 。不过,startLeScan方法在 Android 5.0(API 级别 21)后逐渐被弃用,取而代之的是更强大、灵活的BluetoothLeScanner 。
使用BluetoothLeScanner进行扫描时,首先需获取其实例:
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
接着构建ScanSettings和ScanFilter,以配置扫描模式和过滤条件 。例如,设置为低延迟扫描模式,并添加一个过滤条件,只扫描特定 UUID 的设备:
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
List<ScanFilter> filters = new ArrayList<>();
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("目标服务UUID"))
.build();
filters.add(filter);
最后调用startScan方法开始扫描,并传入ScanCallback回调:
bluetoothLeScanner.startScan(filters, settings, new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
int rssi = result.getRssi();
byte[] scanRecord = result.getScanRecord().getBytes();
// 处理扫描结果
Log.d("BLE Scan", "发现设备: " + device.getName() + " – " + device.getAddress());
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
// 处理批量扫描结果
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
// 处理扫描失败情况
Log.e("BLE Scan", "扫描失败,错误码: " + errorCode);
}
});
在ScanCallback中,onScanResult方法会在每次发现设备时触发,onBatchScanResults用于处理批量扫描结果(当启用批量扫描时),onScanFailed则在扫描失败时被调用,通过errorCode可获取具体的错误信息 。为降低功耗,可根据实际需求配置SCAN_MODE_LOW_POWER等扫描模式,在扫描完成后及时停止扫描,避免资源浪费 。
3.3 GATT 连接与数据交互
在 BLE 开发中,成功扫描到设备后,便需建立 GATT 连接,以实现与 BLE 设备的数据交互 。通过调用BluetoothDevice的connectGatt方法来建立连接:
BluetoothDevice device = // 扫描得到的设备
BluetoothGatt bluetoothGatt = device.connectGatt(context, false, gattCallback);
其中context为上下文,false表示不自动连接,gattCallback是用于监听连接状态和 GATT 事件的回调 。
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// 连接成功,发现服务
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// 断开连接,可进行重连等操作
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 服务发现成功,获取服务和特征
List<BluetoothGattService> services = gatt.getServices();
for (BluetoothGattService service : services) {
UUID serviceUuid = service.getUuid();
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
for (BluetoothGattCharacteristic characteristic : characteristics) {
UUID characteristicUuid = characteristic.getUuid();
// 处理特征,如读写操作
}
}
} else {
// 服务发现失败,可重试
}
}
};
在onConnectionStateChange回调中,当newState为BluetoothProfile.STATE_CONNECTED时,表示连接成功,此时调用discoverServices方法发现 BLE 设备提供的服务;当newState为BluetoothProfile.STATE_DISCONNECTED时,需处理连接断开的情况,如尝试重新连接 。
在onServicesDiscovered回调中,若status为BluetoothGatt.GATT_SUCCESS,则表明服务发现成功,可获取服务和特征进行后续操作 。
数据交互主要通过特征(BluetoothGattCharacteristic)的读写操作实现 。读取特征值时:
BluetoothGattCharacteristic characteristic = // 获取到的特征
if (characteristic.getProperties()
& BluetoothGattCharacteristic.PROPERTY_READ) {
gatt.readCharacteristic(characteristic);
}
在BluetoothGattCallback的onCharacteristicRead方法中处理读取结果:
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
byte[] value = characteristic.getValue();
// 处理读取到的数据
String data = new String(value);
Log.d("BLE Read", "读取到数据: " + data);
}
}
写入特征值的操作如下:
BluetoothGattCharacteristic characteristic = // 获取到的特征
if (characteristic.getProperties()
& BluetoothGattCharacteristic.PROPERTY_WRITE) {
byte[] data = "Hello, BLE!".getBytes();
characteristic.setValue(data);
gatt.writeCharacteristic(characteristic);
}
同样在BluetoothGattCallback的onCharacteristicWrite方法中处理写入结果 。
若需实时获取 BLE 设备的数据更新,可设置特征的通知(Notification):
BluetoothGattCharacteristic characteristic = // 获取到的特征
if (characteristic.getProperties()
& BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
gatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
在onCharacteristicChanged方法中接收通知数据:
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
byte[] value = characteristic.getValue();
// 处理通知数据
String data = new String(value);
Log.d("BLE Notify", "接收到通知数据: " + data);
}
由于 BLE 连接可能因信号干扰、设备移动等原因断开,务必添加重连机制,以保证数据交互的稳定性 。
3.4 BLE 开发注意事项
在 BLE 开发过程中,诸多要点需格外留意,以确保开发的顺利进行和应用的稳定运行 。
连接超时处理至关重要 。BLE 设备连接并非总是一帆风顺,可能因设备繁忙、信号不佳等原因导致连接超时 。在建立连接时,可设置一个合理的超时时间,例如 5 秒 。通过CountDownTimer实现连接超时监测:
private CountDownTimer connectTimer;
private static final int CONNECT_TIMEOUT = 5000;
private void connectToDevice(BluetoothDevice device) {
bluetoothGatt = device.connectGatt(context, false, gattCallback);
connectTimer = new CountDownTimer(CONNECT_TIMEOUT, 1000) {
@Override
public void onTick(long millisUntilFinished) {
// 倒计时,可用于显示进度
}
@Override
public void onFinish() {
if (bluetoothGatt != null && bluetoothGatt.getConnectionState()
!= BluetoothProfile.STATE_CONNECTED) {
// 连接超时,处理逻辑,如提示用户、尝试重连
Log.e("BLE", "连接超时");
if (bluetoothGatt != null) {
bluetoothGatt.disconnect();
bluetoothGatt.close();
}
// 尝试重连逻辑
}
}
}.start();
}
特征 UUID 的正确性验证也不容忽视 。每个 BLE 设备的服务和特征都有其特定的 UUID,若 UUID 错误,将无法正确读取或写入数据 。在开发过程中,务必仔细核对 UUID,可将 UUID 存储在常量类中,方便管理和检查 。例如:
public class BLEConstants {
public static final UUID SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805F9B34FB");
public static final UUID CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB");
}
在使用时直接引用常量,避免手动输入错误 。
多设备连接时存在资源限制 。Android 设备同时连接多个 BLE 设备时,系统资源(如内存、线程、蓝牙连接通道)会面临压力 。在设计多设备连接功能时,需充分考虑资源分配,避免因连接过多设备导致系统崩溃或性能急剧下降 。可限制同时连接的设备数量,或采用分时连接策略,确保每个设备都能得到合理的资源分配 。
此外,BLE 数据传输的 MTU(最大传输单元)值优化也十分关键 。默认情况下,BLE 的 MTU 值较小,对于大数据包的传输效率较低 。可通过requestMtu方法尝试协商更大的 MTU 值,提升数据传输效率 。例如:
private static final int MAX_MTU = 512;
private void negotiateMtu() {
if (bluetoothGatt != null) {
bluetoothGatt.requestMtu(MAX_MTU);
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// MTU协商成功,更新MTU值
Log.d("BLE", "MTU协商成功,新MTU值: " + mtu);
} else {
// MTU协商失败,可尝试其他值或采用分包传输
Log.e("BLE", "MTU协商失败,错误码: " + status);
}
}
通过上述优化和注意事项的处理,可有效提升 BLE 开发的质量和应用的稳定性 。
四、 Android 蓝牙通讯技术对比分析

4.1 经典蓝牙 vs BLE 核心差异对比
经典蓝牙与低功耗蓝牙(BLE)虽同属蓝牙技术阵营,但在诸多关键特性上存在显著差异 。
从功耗层面来看,经典蓝牙在运行过程中需持续保持较高的工作频率以维持稳定连接和数据传输,这导致其功耗相对较高,一般适用于连接电源或配备大容量电池的设备,如蓝牙音箱、车载蓝牙等 。而 BLE 则专为低功耗场景设计,采用 “快速连接 – 传输 – 休眠” 机制,在非传输状态下能迅速进入超低功耗的休眠模式,仅在短暂的数据传输时被唤醒,使得其功耗极低,一枚普通的纽扣电池就能支撑设备运行数月甚至数年,这也是智能手环、智能手表等可穿戴设备广泛采用 BLE 技术的重要原因 。
传输速度方面,经典蓝牙凭借其较高的带宽,传输速度表现出色,最高可达 2.1Mbps,能够满足高清音频传输、大文件传输等对数据速率要求较高的场景,如蓝牙耳机播放无损音乐、蓝牙传输高清图片等 。BLE 则更侧重于小数据量的快速传输,其传输速度相对较低,最高为 2Mbps,但在实际应用中,多数 BLE 设备的数据量较小,如传感器数据、设备状态信息等,较低的传输速度足以满足需求 。
连接时间上,经典蓝牙建立连接时需经历完整的握手流程,这一过程相对复杂,耗时较长,大约在 100ms 左右 。而 BLE 采用广播模式,支持无连接数据传输,连接建立时间大幅缩短,最快可在 3ms 内完成连接,这使得 BLE 设备能够快速响应数据请求,实现高效的数据交互 。
在适用场景上,两者也各有侧重 。经典蓝牙适用于需要高带宽、稳定连接和持续双向通信的场景,像无线耳机、游戏手柄、车载通话系统等,这些场景要求实时性强、数据传输量大且稳定 。BLE 则在对功耗敏感、数据量小且传输频次低的场景中大放异彩,如健康监测设备(心率带、血糖仪)、位置服务(室内导航信标)、智能家居传感器(温湿度计、智能门锁)等,这些设备通常以电池供电,需要长时间运行,BLE 的低功耗特性完美契合了这一需求 。
为更直观地对比两者差异,如下表所示:
| 功耗 | 高,持续工作时耗电快 | 低,可实现长时间电池供电 |
| 传输速度 | 最高 2.1Mbps,适合大数据量传输 | 最高 2Mbps,适合小数据量快速传输 |
| 连接时间 | 约 100ms,连接流程复杂 | 最快 3ms,基于广播模式,连接迅速 |
| 适用场景 | 高带宽、持续通信场景,如音频、文件传输 | 低功耗、小数据量、高频次交互场景,如传感器数据传输 |
4.2 Android 不同版本蓝牙特性变化(以 Android 13 为例)
随着 Android 系统的不断迭代升级,蓝牙功能也在持续优化和演进,以提供更丰富的功能、更高的安全性和更出色的用户体验 。Android 13 在蓝牙方面带来了一系列重要更新 。
权限控制上,Android 13 引入了更精细的权限管理机制 。以往的BLUETOOTH_ADMIN权限在 Android 13 中被BLUETOOTH_CONNECT所替代,BLUETOOTH_CONNECT权限赋予应用连接特定蓝牙设备的能力,同时,BLUETOOTH_SCAN权限用于控制应用扫描蓝牙设备的行为 。并且对于BLUETOOTH_SCAN,若不涉及位置访问,需声明usesPermissionFlags="neverForLocation" 。在AndroidManifest.xml文件中的权限声明示例如下:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
在代码中动态申请权限时,需注意版本适配:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
List<String> permissionList = new ArrayList<>();
permissionList.add(Manifest.permission.BLUETOOTH_SCAN);
permissionList.add(Manifest.permission.BLUETOOTH_CONNECT);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
ActivityCompat.requestPermissions(this,
permissionList.toArray(new String[0]),
REQUEST_BLUETOOTH_PERMISSION);
}
新增的设备选择器 API,为用户提供了更便捷的设备连接体验 。开发者可通过调用设备选择器,让用户在系统提供的界面中直观地选择要连接的蓝牙设备,无需自行构建复杂的设备列表展示和连接逻辑 。使用示例如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Intent intent = new Intent(BluetoothDevicePicker.ACTION_SELECT_DEVICE);
intent.putExtra(BluetoothDevicePicker.EXTRA_CONNECTION_POLICY,
BluetoothDevicePicker.CONNECTION_POLICY_ALLOWED);
startActivityForResult(intent, REQUEST_SELECT_DEVICE);
}
在onActivityResult方法中处理用户选择的设备:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_SELECT_DEVICE && resultCode == RESULT_OK) {
BluetoothDevice device = data.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null) {
// 连接选择的设备
connectToDevice(device);
}
}
}
Android 13 还对蓝牙连接稳定性进行了优化,减少了因信号波动、设备移动等原因导致的连接中断情况 。在开发过程中,为确保应用在不同 Android 版本中都能稳定运行,需根据系统版本进行相应的代码适配 。例如,在连接蓝牙设备时,可通过Build.VERSION.SDK_INT判断系统版本,针对不同版本采用不同的连接方式或权限处理逻辑 ,以兼容新旧系统的蓝牙特性差异 。
4.3 蓝牙 vs USB 通信技术对比
蓝牙与 USB 作为两种常见的通信技术,在 Android 设备开发中扮演着不同的角色,它们在传输特性、使用便捷性和适用场景等方面存在明显差异 。
传输速度上,USB 占据显著优势 。USB 2.0 的传输速率可达到 480Mbps,而最新的 USB 4.0 速度更是高达 40 Gbps,这使得 USB 在大数据量传输时表现出色,如快速拷贝大容量文件、高速传输高清视频等 。相比之下,蓝牙的传输速度相对较慢,经典蓝牙最高 2.1Mbps,BLE 最高 2Mbps,主要适用于小数据量的传输场景 。
稳定性方面,USB 由于采用有线连接方式,数据传输过程受外界干扰较小,连接稳定可靠 。而蓝牙作为无线通信技术,容易受到其他无线信号干扰,如 Wi-Fi 信号、微波炉等设备产生的干扰,导致信号波动甚至连接中断 ,在复杂电磁环境下的稳定性不如 USB 。
使用便捷性上,蓝牙的无线特性使其无需物理线缆连接,设备间可自由配对和通信,用户在使用蓝牙设备(如蓝牙耳机、蓝牙鼠标)时更加方便,摆脱了线缆束缚,可在一定范围内自由活动 。USB 则需要通过物理线缆连接设备,使用时不够灵活,尤其是在设备位置变动频繁的场景下,线缆连接会带来诸多不便 。
在适用场景上,蓝牙适用于短距离无线交互场景,如智能穿戴设备与手机的数据同步、智能家居设备的远程控制等,这些场景对传输速度要求不高,但对无线连接的便捷性有较高需求 。USB 更适合需要高速、稳定传输的场景,如 Android 设备与电脑之间的文件传输、外接存储设备的数据读写、设备调试时的高速数据交互等 。
五、 实战案例与开发优化技巧
5.1 蓝牙串口(SPP 协议)通信实现
在实际开发中,蓝牙串口(SPP 协议)通信常用于模拟工业设备的串口交互场景,如传感器数据采集、设备状态监控等 。下面我们基于经典蓝牙来实现 SPP 串口协议通信 。
首先,我们使用标准 UUID(00001101-0000-1000-8000-00805F9B34FB)来标识串口服务,这是蓝牙串口通信的标准 UUID,确保不同设备间的兼容性 。在连接设备时,通过BluetoothDevice的createRfcommSocketToServiceRecord方法创建BluetoothSocket,并传入该 UUID:
private static final UUID SERIAL_PORT_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// …
BluetoothDevice device = // 扫描得到的设备
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(SERIAL_PORT_UUID);
socket.connect();
建立连接后,就可进行数据的收发操作 。数据接收逻辑如下:
private class ReadThread extends Thread {
private final InputStream inputStream;
public ReadThread(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
while (true) {
try {
bytes = inputStream.read(buffer);
if (bytes > 0) {
String receivedData = new String(buffer, 0, bytes);
// 处理接收到的数据,如更新UI显示
runOnUiThread(() -> {
// 假设存在一个TextView用于显示接收到的数据
TextView receivedDataTextView = findViewById(R.id.received_data_text_view);
receivedDataTextView.append(receivedData);
});
}
} catch (IOException e) {
Log.e("Bluetooth", "接收数据失败", e);
break;
}
}
}
}
数据发送逻辑相对简单,通过OutputStream的write方法即可:
private class WriteThread extends Thread {
private final OutputStream outputStream;
private final String data;
public WriteThread(OutputStream outputStream, String data) {
this.outputStream = outputStream;
this.data = data;
}
@Override
public void run() {
try {
outputStream.write(data.getBytes());
} catch (IOException e) {
Log.e("Bluetooth", "发送数据失败", e);
}
}
}
为了方便用户操作,我们结合 ListView 展示设备列表与通信日志 。在扫描设备时,将发现的设备添加到 ListView 中,用户点击设备即可发起连接:
private ArrayAdapter<String> deviceListAdapter;
private ListView deviceListView;
// 初始化ListView和Adapter
deviceListAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
deviceListView = findViewById(R.id.device_list_view);
deviceListView.setAdapter(deviceListAdapter);
// 扫描设备并添加到ListView
private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device != null) {
String deviceName = device.getName();
String deviceAddress = device.getAddress();
String deviceInfo = deviceName + " – " + deviceAddress;
deviceListAdapter.add(deviceInfo);
}
}
}
};
通信日志则在数据收发过程中实时更新,让用户直观了解通信状态:
// 假设存在一个TextView用于显示通信日志
TextView logTextView = findViewById(R.id.log_text_view);
// 在ReadThread和WriteThread中更新日志
logTextView.append("接收数据: " + receivedData + "\\n");
logTextView.append("发送数据: " + data + "\\n");
通过以上代码实现,可模拟工业设备的串口交互场景,实现数据的稳定传输与监控 。
5.2 智能手表蓝牙连接实战
在智能穿戴设备蓬勃发展的今天,实现 Android 手机与智能手表的蓝牙连接成为许多开发者的重要任务 。下面我们结合 BLE 开发流程,详细阐述如何实现这一连接,并实时获取手表传感器数据 。 设备扫描阶段,为了精准找到智能手表,我们根据手表设备名称进行过滤 。例如,假设智能手表设备名称为 “SmartWatch_X”,扫描代码如下:
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
if (device != null && "SmartWatch_X".equals(device.getName())) {
// 发现目标智能手表,停止扫描并连接
bluetoothLeScanner.stopScan(this);
connectToDevice(device);
}
}
};
bluetoothLeScanner.startScan(scanCallback);
连接设备后,进入 GATT 服务发现环节 。智能手表通常会提供心率、步数等服务,我们通过 UUID 来识别这些服务 。假设心率服务 UUID 为 “0000180D-0000-1000-8000-00805F9B34FB”,步数服务 UUID 为 “00001812-0000-1000-8000-00805F9B34FB”,服务发现代码如下:
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// 处理连接断开情况
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
List<BluetoothGattService> services = gatt.getServices();
for (BluetoothGattService service : services) {
UUID serviceUuid = service.getUuid();
if ("0000180D-0000-1000-8000-00805F9B34FB".equals(serviceUuid.toString())) {
// 处理心率服务
handleHeartRateService(service);
} else if ("00001812-0000-1000-8000-00805F9B34FB".equals(serviceUuid.toString())) {
// 处理步数服务
handleStepCountService(service);
}
}
}
}
};
在处理心率服务和步数服务时,需要解析对应的特征数据 。以心率服务为例,假设心率特征 UUID 为 “00002A37-0000-1000-8000-00805F9B34FB”,解析代码如下:
private void handleHeartRateService(BluetoothGattService service) {
BluetoothGattCharacteristic heartRateCharacteristic = service.getCharacteristic(
UUID.fromString("00002A37-0000-1000-8000-00805F9B34FB"));
if (heartRateCharacteristic != null) {
if (heartRateCharacteristic.getProperties()
& BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
gatt.setCharacteristicNotification(heartRateCharacteristic, true);
BluetoothGattDescriptor descriptor = heartRateCharacteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if ("00002A37-0000-1000-8000-00805F9B34FB".equals(characteristic.getUuid().toString())) {
byte[] value = characteristic.getValue();
if (value != null && value.length > 0) {
int heartRate = value[0] & 0xff;
// 处理心率数据,如更新UI显示
runOnUiThread(() -> {
TextView heartRateTextView = findViewById(R.id.heart_rate_text_view);
heartRateTextView.setText("心率: " + heartRate + " 次/分钟");
});
}
}
}
步数服务的处理方式类似,通过解析对应特征数据,实时获取手表传感器数据,为用户提供健康监测和运动记录功能 。
5.3 蓝牙开发性能优化技巧
在蓝牙开发过程中,优化性能对于提升应用的稳定性与用户体验至关重要 。下面分享一些实用的优化技巧 。 扫描频率控制是关键 。频繁调用startDiscovery方法会消耗大量电量和系统资源,降低设备续航能力和运行效率 。可设置合理的扫描时间间隔,例如每 10 秒扫描一次,使用Handler和Runnable实现定时扫描:
long scanInterval = 10000; // 每10秒扫描一次
Handler handler = new Handler();
Runnable scanRunnable = new Runnable() {
@Override
public void run() {
startBluetoothScan();
handler.postDelayed(this, scanInterval);
}
};
handler.post(scanRunnable);
子线程异步处理必不可少 。蓝牙操作(如连接、数据传输)往往耗时较长,若在主线程中执行,会导致 UI 卡顿甚至 ANR(应用无响应) 。将这些操作放在子线程中执行,确保主线程的流畅运行 。例如,在连接设备时,开启一个新线程进行连接:
new Thread(() -> {
BluetoothDevice device = // 目标设备
BluetoothGatt bluetoothGatt = device.connectGatt(context, false, gattCallback);
}).start();
连接池管理可有效复用蓝牙连接 。对于频繁连接同一设备或多个设备的场景,创建连接池,当需要连接时,先从池中获取已有的连接,若不存在再创建新连接 。这样可减少连接建立的开销,提升连接速度 。可使用HashMap来实现简单的连接池:
private final Map<BluetoothDevice, BluetoothGatt> connectionPool = new HashMap<>();
public BluetoothGatt getConnection(BluetoothDevice device) {
if (connectionPool.containsKey(device)) {
return connectionPool.get(device);
} else {
BluetoothGatt bluetoothGatt = device.connectGatt(context, false, gattCallback);
connectionPool.put(device, bluetoothGatt);
return bluetoothGatt;
}
}
异常重连机制能增强应用的稳定性 。蓝牙连接可能因信号波动、设备距离过远等原因断开,监听连接断开广播,当连接断开时,及时尝试重新连接 。注册广播接收器监听连接断开事件:
private final BroadcastReceiver connectionLostReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 尝试重新连接设备
reconnectDevice(device);
}
}
};
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
registerReceiver(connectionLostReceiver, filter);
六、 常见问题与解决方案
6.1 扫描类问题(扫描不到设备、扫描结果不全)
在蓝牙开发中,扫描类问题较为常见,给开发者带来诸多困扰 。扫描不到设备或扫描结果不全,可能由多种原因导致 。 权限缺失是一个关键因素 。从 Android 6.0 开始,蓝牙扫描需要位置权限,若未动态申请并获取ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限,将无法正常扫描设备 。在 Android 12 及以上版本,还需申请BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限 。权限申请代码如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
List<String> permissionList = new ArrayList<>();
permissionList.add(Manifest.permission.BLUETOOTH_SCAN);
permissionList.add(Manifest.permission.BLUETOOTH_CONNECT);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
ActivityCompat.requestPermissions(this,
permissionList.toArray(new String[0]),
REQUEST_BLUETOOTH_PERMISSION);
}
设备未处于可发现模式也会导致扫描失败 。经典蓝牙设备需设置为可被发现状态,低功耗蓝牙设备需正确广播数据 。对于经典蓝牙,可在设备设置中开启可发现性;对于 BLE 设备,若广播间隔过长,可能导致短时间内扫描不到,可适当延长扫描时间,如:
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
bluetoothLeScanner.startScan(null, settings, new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
// 处理扫描结果
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
// 处理批量扫描结果
}
@Override
public void onScanFailed(int errorCode) {
Log.e("BLE Scan", "扫描失败,错误码: " + errorCode);
}
});
// 20秒后停止扫描
new Handler().postDelayed(() -> bluetoothLeScanner.stopScan(this), 20000);
蓝牙适配器未初始化同样会引发问题 。在进行蓝牙操作前,务必确保BluetoothAdapter已正确获取并初始化,可通过BluetoothAdapter.getDefaultAdapter()获取实例,并检查是否为null 。 当遇到扫描问题时,排查步骤如下:首先检查权限是否已正确申请和授予,可通过ContextCompat.checkSelfPermission方法检查;接着确认设备是否处于可发现模式,对于 BLE 设备,可尝试调整扫描时间和模式;最后检查蓝牙适配器状态,若未初始化,重新获取并初始化 。通过这些排查步骤和解决方案,可有效解决扫描类问题 。
6.2 连接类问题(连接超时、配对失败)
连接类问题是蓝牙开发中的另一大难题,连接超时和配对失败会严重影响用户体验 。 连接超时通常是由于设备响应缓慢或连接过程受阻 。可能的原因包括 UUID 不匹配,若客户端和服务端使用的 UUID 不一致,将无法建立连接;设备已被其他设备连接,导致无法再与新设备建立连接 。解决连接超时问题,可添加连接超时重试逻辑,如:
private static final int CONNECT_TIMEOUT = 5000;
private CountDownTimer connectTimer;
private void connectToDevice(BluetoothDevice device) {
BluetoothGatt bluetoothGatt = device.connectGatt(context, false, gattCallback);
connectTimer = new CountDownTimer(CONNECT_TIMEOUT, 1000) {
@Override
public void onTick(long millisUntilFinished) {
// 倒计时,可用于显示进度
}
@Override
public void onFinish() {
if (bluetoothGatt != null && bluetoothGatt.getConnectionState()
!= BluetoothProfile.STATE_CONNECTED) {
// 连接超时,处理逻辑,如提示用户、尝试重连
Log.e("BLE", "连接超时");
if (bluetoothGatt != null) {
bluetoothGatt.disconnect();
bluetoothGatt.close();
}
// 尝试重连逻辑
connectToDevice(device);
}
}
}.start();
}
配对失败的原因也较为复杂,可能是系统配对缓存异常,之前的配对信息干扰了新的配对过程 。此时可尝试清除蓝牙配对缓存,不同设备的清除方式略有差异,一般可在系统设置中找到蓝牙选项,进入配对设备列表,选择忘记配对设备,清除缓存 。还可能是因为配对过程中出现错误,如输入的配对码错误 。在代码中,可通过监听配对请求和错误回调来处理,如:
BluetoothDevice device = // 目标设备
device.setPairingConfirmation(true);
device.setPin("1234".getBytes());
device.createBond();
device.setPairingListener(new BluetoothDevice.PairingListener() {
@Override
public void onPairingRequest(BluetoothDevice device, int type, byte[] pin) {
// 处理配对请求
}
@Override
public void onPairingError(BluetoothDevice device, int error) {
Log.e("Bluetooth", "配对错误: " + error);
// 处理配对错误,如提示用户重新配对
}
});
6.3 数据传输类问题(数据丢失、传输卡顿)
数据传输类问题直接影响蓝牙通信的质量和效率,数据丢失和传输卡顿是常见的问题表现 。 主线程阻塞是导致数据传输异常的一个重要原因 。蓝牙数据读写操作应在子线程中进行,若在主线程执行,会阻塞 UI 响应,导致数据处理不及时,进而出现数据丢失或传输卡顿 。可使用线程池处理数据读写,如ThreadPoolExecutor:
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
// 数据读取逻辑
InputStream inputStream = bluetoothSocket.getInputStream();
byte[] buffer = new byte[1024];
int bytes;
while ((bytes = inputStream.read(buffer))!= –1) {
// 处理读取到的数据
}
});
executorService.submit(() -> {
// 数据写入逻辑
OutputStream outputStream = bluetoothSocket.getOutputStream();
String data = "Hello, Bluetooth!";
outputStream.write(data.getBytes());
});
输入输出流未及时关闭也可能引发问题 。在数据传输完成后,若未及时关闭InputStream和OutputStream,可能导致资源占用,影响后续数据传输 。务必在合适的时机关闭流,如在连接断开时:
private void disconnect() {
try {
if (inputStream!= null) {
inputStream.close();
}
if (outputStream!= null) {
outputStream.close();
}
if (bluetoothSocket!= null) {
bluetoothSocket.close();
}
} catch (IOException e) {
Log.e("Bluetooth", "关闭流和连接失败", e);
}
}
数据包过大也是一个潜在问题 。蓝牙传输的 MTU(最大传输单元)有限,若数据包超过 MTU 大小,可能导致数据丢失或传输异常 。对于大数据量传输,可分批次传输,将大文件拆分成多个小数据包发送 。还可添加数据校验机制,如 CRC 校验,确保数据完整性 。在发送端计算 CRC 校验值并随数据一起发送,接收端接收到数据后,重新计算 CRC 校验值并与接收到的校验值对比,若不一致,则说明数据可能丢失或损坏,可请求重发 。
七、 总结
经典蓝牙凭借其成熟的技术体系,在需要高带宽、稳定连接的场景中发挥着关键作用,如无线音频传输、大文件传输等 。其开发流程涵盖蓝牙开启、设备扫描、配对连接以及数据传输等环节,每个步骤都有着明确的 API 调用和实现逻辑 。通过BluetoothAdapter实现蓝牙状态控制和设备发现,利用BluetoothSocket完成数据的收发,为开发者构建了一套稳定可靠的通信框架 。
BLE 则以其卓越的低功耗特性,成为物联网、智能穿戴等领域的宠儿 。从核心概念上,GATT 协议、服务(Service)和特征(Characteristic)构成了 BLE 数据交互的基础 。开发过程中,通过BluetoothLeScanner进行设备扫描,使用BluetoothGatt建立连接并实现数据交互 。在智能手表与手机的数据同步场景中,BLE 能够以极低的功耗持续传输心率、步数等健康数据,为用户提供便捷的健康监测服务 。
经典蓝牙功耗较高但传输速度快,适用于对带宽要求高的场景;
BLE 功耗低、连接迅速,更适合小数据量、低功耗的应用场景 。
同时,随着 Android 版本的不断更新,蓝牙特性也在持续演进,开发者需要紧跟版本变化,做好代码适配 。
附录:参考资料
- Android 官方蓝牙开发文档:https://developer.android.com/guide/topics/connectivity/bluetooth,全面权威的官方指南,涵盖蓝牙开发的各个方面,从基础概念到高级特性,为开发者提供了详尽的技术参考。






