4. 低功耗蓝牙扫描
任务说明
本案例实现一个低功耗蓝牙(BLE)扫描器,能够扫描周围的BLE广播设备,并按照指定的服务UUID进行过滤,仅显示包含目标服务的设备。最终通过列表界面呈现扫描结果,支持开始/停止扫描操作。
实现方案
使用Android系统提供的BluetoothLeScanner API完成扫描。核心逻辑包括:申请必要权限、初始化扫描器、构造包含服务UUID过滤条件的ScanFilter、通过ScanCallback接收结果并更新UI。过滤条件在扫描请求层生效,系统底层会优先丢弃不匹配的广播包,从而降低功耗与回调频率。
代码实现
// BleScannerActivity.java
public class BleScannerActivity extends AppCompatActivity {
private static final int REQ_PERM = 100;
private static final UUID TARGET_SERVICE_UUID =
UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb"); // Battery Service
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bleScanner;
private ScanCallback scanCallback;
private ArrayAdapter<String> adapter;
private boolean isScanning = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ble_scan);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
((ListView) findViewById(R.id.list_devices)).setAdapter(adapter);
BluetoothManager manager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
bluetoothAdapter = manager.getAdapter();
bleScanner = bluetoothAdapter.getBluetoothLeScanner();
findViewById(R.id.btn_scan).setOnClickListener(v -> toggleScan());
requestPermissions();
}
private void requestPermissions() {
if (Build.VERSION.SDK_INT >= 31) {
requestPermissions(new String[]{
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_FINE_LOCATION
}, REQ_PERM);
} else {
requestPermissions(new String[]{
Manifest.permission.ACCESS_FINE_LOCATION
}, REQ_PERM);
}
}
private void toggleScan() {
if (isScanning) {
stopScan();
} else {
startScan();
}
}
private void startScan() {
if (bleScanner == null) return;
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid(new ParcelUuid(TARGET_SERVICE_UUID))
.build();
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build();
scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();
String info = device.getName() + " [" + device.getAddress() + "] RSSI:" + result.getRssi();
runOnUiThread(() -> {
if (adapter.getPosition(info) < 0) {
adapter.add(info);
}
});
}
@Override
public void onScanFailed(int errorCode) {
runOnUiThread(() ->
Toast.makeText(BleScannerActivity.this,
"扫描失败,错误码:" + errorCode, Toast.LENGTH_SHORT).show());
}
};
bleScanner.startScan(Collections.singletonList(filter), settings, scanCallback);
isScanning = true;
((Button) findViewById(R.id.btn_scan)).setText("停止扫描");
}
private void stopScan() {
if (bleScanner != null && scanCallback != null) {
bleScanner.stopScan(scanCallback);
}
isScanning = false;
((Button) findViewById(R.id.btn_scan)).setText("开始扫描");
}
}
// activity_ble_scan.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<Button
android:id="@+id/btn_scan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始扫描" />
<ListView
android:id="@+id/list_devices"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="12dp" />
</LinearLayout>
// AndroidManifest.xml 关键权限声明
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
关键逻辑说明
扫描过滤器使用setServiceUuid()指定目标服务UUID(示例为Battery Service 0x180F)。当BLE外设广播中包含该服务UUID时,系统才会将结果回调给应用。列表项显示设备名称、MAC地址和RSSI信号强度。重复设备通过getPosition()检查避免重复添加。
SVG:BLE扫描过滤流程
落地与验证
运行方式:在Android 8.0及以上设备安装应用,确保手机蓝牙已开启。点击“开始扫描”按钮,系统会弹出权限请求,全部允许后扫描自动开始。
验证方法:准备一个广播包含Battery Service(0x180F)的BLE外设(如某些心率计、蓝牙温湿度计),放置于手机附近。观察列表是否出现该设备名称与MAC地址。再准备一个不包含该服务的普通BLE设备(如蓝牙耳机),确认其不会出现在列表中。
判定标准:包含目标服务UUID的设备在数秒内出现在列表且RSSI值合理(-30~-90 dBm);不包含目标服务的设备始终不出现;点击“停止扫描”后列表不再新增设备,即判定通过。