onLeScan 是一个非常重要的回调方法,主要用于在 API 21 (Android 5.0, Lollipop) 之前 的版本中进行蓝牙低功耗设备的扫描,从 API 21 开始,Google 推荐使用新的 BluetoothLeScanner API,它功能更强大、更灵活。

onLeScan 的基本概念
onLeScan 是 BluetoothAdapter.LeScanCallback 接口中的一个方法,当你调用 BluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback) 方法时,系统会开始扫描周围的 BLE 设备,每当发现一个新的设备时,就会回调 onLeScan 方法,并将发现到的设备信息作为参数传递给你。
这个方法就是“发现 BLE 设备时的通知员”。
方法签名和参数详解
onLeScan 方法的签名如下:
/** * 当发现新的 BLE 设备时调用。 * * @param device 被发现的蓝牙设备。 * @param rssi 接收信号强度指示器,表示设备与手机之间的信号强度,单位是 dBm。 * @param scanRecord 设备的广告数据。 */ public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord);
下面我们逐一解析这三个核心参数:

BluetoothDevice device
- 类型:
BluetoothDevice - 含义: 代表被发现的蓝牙低功耗设备本身。
- 作用: 这是获取设备信息的核心对象,你可以通过它来获取设备的:
- MAC 地址:
device.getAddress(),这是唯一标识一个蓝牙设备的地址。 - 设备名称:
device.getName(),注意,这个名称可能为null,因为设备不一定会在广播数据中包含其名称。 - Bonding State (绑定状态):
device.getBondState(),可以判断设备是否已经与手机配对。
- MAC 地址:
示例用法:
BluetoothDevice foundDevice = device;
String macAddress = foundDevice.getAddress();
String deviceName = foundDevice.getName(); // 可能是 null
Log.d("BLE", "发现设备: " + (deviceName != null ? deviceName : "未知") + " @ " + macAddress);
int rssi
- 类型:
int - 含义: 接收信号强度指示器。
- 作用: 它是一个负值(
-55, -89),表示设备广播信号被你的手机接收时的强度,单位是 dBm (decibel-milliwatts)。 - 实际应用:
- 估算距离: RSSI 值越大(即数值越接近 0),表示设备离手机越近,RSSI 值越小(即数值越小,越负),表示设备离手机越远,你可以通过经验公式或查表来估算大致的距离。
- 筛选设备: 你可以根据 RSSI 值来过滤掉距离太远的设备,只处理 RSSI 大于 -70 dBm 的设备,这样可以减少噪音干扰,专注于附近的设备。
- 信号质量指示: 在用户界面中,可以用 RSSI 值来显示信号的强弱(用信号格数表示)。
示例用法:
// RSSI 值越大,信号越强
if (rssi > -70) {
Log.d("BLE", "设备信号强: " + rssi + " dBm");
} else {
Log.d("BLE", "设备信号弱: " + rssi + " dBm");
}
byte[] scanRecord
- 类型:
byte[] - 含义: 设备的 广告数据 或 扫描响应数据。
- 作用: 这是 BLE 设备向外界广播的“名片”,包含了设备希望被其他设备知道的各种信息,解析这个
byte[]数组是 BLE 开发的核心和难点之一。 - 广告数据中常见的内容:
- 设备名称: 设备的本地名称。
- 服务 UUID 列表: 设备提供的服务列表,这是最重要的信息之一,通过它可以判断设备是否是你需要连接的目标设备。
- 服务数据: 与特定服务相关的数据。
- 制造商特定数据: 由制造商定义的数据,通常包含一些私有信息,如设备型号、固件版本等。
如何解析 scanRecord:
直接手动解析这个字节数组非常复杂且容易出错,强烈建议使用 Google 提供的 com.android.bluetooth.le.ScanRecord 类(注意,这是 Android 框架内部的类,在官方 SDK 中没有,但通常可用)或者第三方库。
示例用法 (使用 ScanRecord 解析):

// 注意:ScanRecord 是 Android 框架内部的类,使用时需注意兼容性
// 通常通过反射或将其作为依赖项引入
// ScanRecord record = ScanRecord.parseFromBytes(scanRecord);
// if (record != null) {
// String localName = record.getDeviceName();
// List<UUID> serviceUuids = record.getServiceUuids();
// Map<ParcelUuid, byte[]> serviceData = record.getServiceData();
// int manufacturerId = record.getManufacturerSpecificData() != null ?
// record.getManufacturerSpecificData().keyAt(0) : -1;
//
// Log.d("BLE", "设备名称: " + localName);
// Log.d("BLE", "服务UUIDs: " + serviceUuids);
// Log.d("BLE", "制造商ID: " + manufacturerId);
// }
完整使用示例
下面是一个在旧版 Android 上使用 onLeScan 进行扫描的完整流程。
public class BleScannerActivity extends AppCompatActivity {
private BluetoothAdapter mBluetoothAdapter;
private LeScanCallback mLeScanCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ble_scanner);
// 1. 检查蓝牙是否支持并获取 BluetoothAdapter
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// 设备不支持蓝牙
Toast.makeText(this, "设备不支持蓝牙", Toast.LENGTH_SHORT).show();
finish();
return;
}
// 2. 检查蓝牙是否开启
if (!mBluetoothAdapter.isEnabled()) {
// 蓝牙未开启,可以提示用户开启
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// 3. 创建 LeScanCallback 实例
mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
// 在主线程中更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
// 解析广播数据
// ... 使用 ScanRecord 或其他工具解析 ...
Log.d("BLE_Scanner", "发现设备: " + device.getName() +
", MAC: " + device.getAddress() +
", RSSI: " + rssi + " dBm");
// 在这里可以更新UI列表,将发现的设备展示给用户
}
});
}
};
}
// 开始扫描
private void startScan() {
// 参数可以指定要扫描的 UUID,null 表示扫描所有设备
// UUID targetUuid = ...; // 你的目标服务 UUID
boolean success = mBluetoothAdapter.startLeScan(mLeScanCallback); // 可以传入 UUID
if (success) {
Log.d("BLE_Scanner", "开始扫描 BLE 设备...");
} else {
Log.e("BLE_Scanner", "启动扫描失败");
}
}
// 停止扫描
private void stopScan() {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
Log.d("BLE_Scanner", "停止扫描 BLE 设备");
}
@Override
protected void onResume() {
super.onResume();
// 在 Activity 可见时开始扫描
startScan();
}
@Override
protected void onPause() {
super.onPause();
// 在 Activity 不可见时停止扫描,以节省电量
stopScan();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 确保在销毁时停止扫描
stopScan();
}
}
重要注意事项
-
API 版本限制:
onLeScan在 API 21 (Android 5.0) 中被废弃。- 从 API 21 开始,必须使用新的
BluetoothLeScannerAPI。 - 如果你的应用需要兼容低于 API 21 的版本,你必须使用
onLeScan,如果只支持 API 21+,则应使用新 API。
-
电量消耗:
- 扫描 BLE 设备是持续消耗电量的操作,一旦找到目标设备或者不再需要扫描,务必调用
stopLeScan()来停止扫描,否则会严重影响设备续航。
- 扫描 BLE 设备是持续消耗电量的操作,一旦找到目标设备或者不再需要扫描,务必调用
-
运行线程:
onLeScan回调是在一个 后台线程 中执行的,如果你需要更新 UI(将设备添加到ListView或RecyclerView),你必须使用runOnUiThread()或Handler将操作切换到主线程。
-
广播数据解析:
- 手动解析
scanRecord是复杂的,对于新项目,应优先考虑使用BluetoothLeScanner,并结合ScanResult和ScanRecord类,它们提供了更友好的 API 来解析广播数据。
- 手动解析
onLeScan vs. BluetoothLeScanner (新 API)
| 特性 | onLeScan (旧 API) |
BluetoothLeScanner (新 API, API 21+) |
|---|---|---|
| 可用版本 | API 3 - 20 (已废弃) | API 21+ |
| 回调方式 | BluetoothAdapter.LeScanCallback |
ScanCallback |
| 回调方法 | onLeScan(device, rssi, scanRecord) |
onScanResult(callbackType, result) 或 onBatchScanResults(results) |
| 参数对象 | 直接传递 BluetoothDevice, rssi, byte[] |
传递 ScanResult 对象,其中包含 device, rssi, getScanRecord() |
| 功能 | 基础扫描功能 | 强大得多:支持设置扫描模式、回调类型、过滤条件(UUID, 设备名称, MAC地址, 制造商数据等) |
| 功耗控制 | 功能单一 | 提供低功耗、平衡、低延迟等多种扫描模式,更省电 |
| 推荐度 | 仅用于兼容旧版本 | 强烈推荐 用于所有新项目 |
onLeScan 的三个参数 device, rssi, scanRecord 分别代表了 “发现谁”、“离多远” 和 “它有什么信息”,理解这三个参数的含义和使用方法是进行 BLE 设备发现和连接的第一步,虽然它已被新 API 取代,但在处理 Android 生态的向后兼容性时,它仍然是一个不可或缺的知识点。
