1 扫描工具类
package com.example.yjh_erji.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import com.tjf.lib_utils.LogsUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* BLE 扫描工具类,兼容Android 5.0+版本,修复了扫描不稳定问题
*/
public class BleScanner {
private static final String TAG = "BleScanner";
private final Context context;
private final BluetoothAdapter bluetoothAdapter;
private final Handler handler = new Handler(Looper.getMainLooper());
private boolean isScanning = false;
private final Map<String, BluetoothDevice> scannedDevices = new HashMap<>();
private BleScanCallback callback;
// 扫描配置参数
private long scanPeriod = 10000; // 扫描10秒
private boolean isDuplicateFilterEnabled = true;
private int scanMode = ScanSettings.SCAN_MODE_BALANCED; // 默认平衡模式
// 回调实例
private android.bluetooth.le.ScanCallback leScanCallback;
private BluetoothAdapter.LeScanCallback oldLeScanCallback;
public BleScanner(Context context) {
this.context = context.getApplicationContext();
// 获取蓝牙管理器和适配器
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
} else {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
}
/**
* 设置扫描结果回调
*/
public void setScanCallback(BleScanCallback callback) {
this.callback = callback;
}
/**
* 设置扫描时间(毫秒)
*/
public void setScanPeriod(long scanPeriod) {
this.scanPeriod = scanPeriod;
}
/**
* 设置是否过滤重复设备
*/
public void setDuplicateFilterEnabled(boolean enabled) {
isDuplicateFilterEnabled = enabled;
}
/**
* 设置扫描模式
* @param scanMode 参考ScanSettings中的扫描模式常量
*/
public void setScanMode(int scanMode) {
this.scanMode = scanMode;
}
/**
* 检查设备是否支持BLE
*/
public boolean isBleSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 &&
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
/**
* 检查蓝牙是否已启用
*/
public boolean isBluetoothEnabled() {
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
/**
* 检查是否拥有必要的权限
*/
public boolean hasRequiredPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+ 权限检查
boolean hasScanPermission = ActivityCompat.checkSelfPermission(context,
android.Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED;
boolean hasConnectPermission = ActivityCompat.checkSelfPermission(context,
android.Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
if (!hasScanPermission || !hasConnectPermission) {
return false;
}
// 检查是否需要位置权限(根据BLUETOOTH_SCAN权限的声明)
if (!hasNeverForLocationFlag()) {
return ActivityCompat.checkSelfPermission(context,
android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
return true;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android 6.0-11 需要位置权限
return ActivityCompat.checkSelfPermission(context,
android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
return true; // Android 6.0以下无需特殊权限
}
/**
* 检查清单文件中BLUETOOTH_SCAN是否包含neverForLocation标记
*/
private boolean hasNeverForLocationFlag() {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
if (packageInfo.requestedPermissions != null) {
for (String perm : packageInfo.requestedPermissions) {
if (perm.equals(android.Manifest.permission.BLUETOOTH_SCAN)) {
// 检查权限标记(实际需要解析AndroidManifest.xml)
// 这里简化处理,实际项目中可通过PackageManager更精确检查
return true;
}
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package not found", e);
}
return false;
}
/**
* 开始扫描BLE设备
*/
public void startScan() {
if (!isBleSupported()) {
notifyScanFailed(BleScanCallback.ERROR_BLE_NOT_SUPPORTED);
return;
}
if (!isBluetoothEnabled()) {
notifyScanFailed(BleScanCallback.ERROR_BLUETOOTH_DISABLED);
return;
}
if (!hasRequiredPermissions()) {
notifyScanFailed(BleScanCallback.ERROR_MISSING_PERMISSIONS);
return;
}
if (isScanning) {
stopScan();
}
scannedDevices.clear();
isScanning = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startScanWithNewApi();
} else {
// 对于Android 4.3-4.4,建议提示用户升级系统
Log.w(TAG, "Old Android version may have limited BLE support");
startScanWithOldApi();
}
// 设置扫描超时
handler.postDelayed(this::stopScan, scanPeriod);
}
/**
* 使用新API (Android 5.0+) 开始扫描
*/
private void startScanWithNewApi() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
android.bluetooth.le.BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
if (scanner == null) {
notifyScanFailed(BleScanCallback.ERROR_SCAN_FAILED);
return;
}
// 配置扫描设置
ScanSettings.Builder settingsBuilder = new ScanSettings.Builder()
.setScanMode(scanMode);
// 兼容Android 6.0+的报告延迟设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
settingsBuilder.setReportDelay(0); // 实时报告
}
// 兼容Android 8.0+的匹配模式设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
settingsBuilder.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT);
}
ScanSettings settings = settingsBuilder.build();
// 创建扫描过滤器(默认空列表,不过滤任何设备)
List<ScanFilter> filters = new ArrayList<>();
// 初始化扫描回调
leScanCallback = new android.bluetooth.le.ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (result != null) {
processScanResult(
result.getDevice(),
result.getRssi(),
result.getScanRecord() != null ? result.getScanRecord().getBytes() : null
);
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result : results) {
processScanResult(
result.getDevice(),
result.getRssi(),
result.getScanRecord() != null ? result.getScanRecord().getBytes() : null
);
}
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "Scan failed with error code: " + errorCode);
isScanning = false;
notifyScanFailed(errorCode);
}
};
// 开始扫描,增加权限异常捕获
try {
scanner.startScan(filters, settings, leScanCallback);
Log.d(TAG, "Started BLE scan with new API");
} catch (SecurityException e) {
Log.e(TAG, "Permission denied while starting scan", e);
isScanning = false;
notifyScanFailed(BleScanCallback.ERROR_MISSING_PERMISSIONS);
}
}
/**
* 使用旧API (Android 4.3-4.4) 开始扫描
*/
private void startScanWithOldApi() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) return;
oldLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
processScanResult(device, rssi, scanRecord);
}
};
bluetoothAdapter.startLeScan(oldLeScanCallback);
Log.d(TAG, "Started BLE scan with old API");
}
/**
* 处理扫描结果
*/
private void processScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) {
if (device == null || !isScanning) return;
String deviceAddress = device.getAddress();
// 处理重复设备(可更新信息而非完全过滤)
boolean isNewDevice = !scannedDevices.containsKey(deviceAddress);
if (isDuplicateFilterEnabled && !isNewDevice) {
return; // 过滤重复设备
}
scannedDevices.put(deviceAddress, device);
// 解析广播数据
BleAdvertisementData advertisementData = new BleAdvertisementData();
advertisementData.rssi = rssi;
advertisementData.rawBytes = scanRecord;
parseRawAdvertisementData(advertisementData);
LogsUtils.i("processScanResult",
"device: " + device.getName() + " - " + device.getAddress(),
"rssi: " + rssi,
"advertisementData: " + advertisementData);
// 回调通知
if (callback != null && isNewDevice) {
callback.onDeviceFound(device, rssi, advertisementData);
}
}
/**
* 解析原始广播数据(根据蓝牙规范)
*/
private void parseRawAdvertisementData(BleAdvertisementData data) {
if (data.rawBytes == null) return;
int offset = 0;
while (offset < data.rawBytes.length - 1) {
int len = data.rawBytes[offset++] & 0xFF;
if (len == 0) break;
if (offset + len > data.rawBytes.length) break; // 防止数组越界
int type = data.rawBytes[offset] & 0xFF;
byte[] fieldData = Arrays.copyOfRange(data.rawBytes, offset + 1, offset + len);
// 根据类型解析数据
switch (type) {
case 0x01: // Flags
data.flags = fieldData[0];
break;
case 0x02: // Incomplete List of 16-bit Service Class UUIDs
case 0x03: // Complete List of 16-bit Service Class UUIDs
parseServiceUuids(fieldData, data);
break;
case 0x09: // Complete Local Name
data.deviceName = new String(fieldData);
break;
case 0x0A: // TX Power Level
data.txPower = fieldData[0];
break;
case 0xFF: // Manufacturer Specific Data
if (fieldData.length >= 2) {
int manufacturerId = ((fieldData[1] & 0xFF) << 8) | (fieldData[0] & 0xFF);
byte[] manufacturerSpecificData = Arrays.copyOfRange(fieldData, 2, fieldData.length);
data.manufacturerData.put(manufacturerId, manufacturerSpecificData);
}
break;
}
offset += len;
}
}
/**
* 解析服务UUID
*/
private void parseServiceUuids(byte[] data, BleAdvertisementData advertisementData) {
if (advertisementData.serviceUuids == null) {
advertisementData.serviceUuids = new ArrayList<>();
}
int uuidLength = data.length;
for (int i = 0; i < uuidLength; i += 2) {
if (i + 1 >= uuidLength) break; // 防止数组越界
// 16-bit UUID
long uuidValue = ((data[i + 1] & 0xFF) << 8) | (data[i] & 0xFF);
String uuidString = String.format("%08X-0000-1000-8000-00805F9B34FB", uuidValue);
try {
advertisementData.serviceUuids.add(UUID.fromString(uuidString));
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid UUID: " + uuidString);
}
}
}
/**
* 停止扫描BLE设备
*/
public void stopScan() {
if (isScanning) {
isScanning = false;
handler.removeCallbacksAndMessages(null); // 移除所有延迟任务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 新API停止扫描
android.bluetooth.le.BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
if (scanner != null && leScanCallback != null) {
try {
scanner.stopScan(leScanCallback);
} catch (Exception e) {
Log.e(TAG, "Error stopping scan", e);
}
}
} else {
// 旧API停止扫描
if (oldLeScanCallback != null) {
bluetoothAdapter.stopLeScan(oldLeScanCallback);
}
}
// 释放回调引用
leScanCallback = null;
oldLeScanCallback = null;
Log.d(TAG, "Stopped BLE scan. Found " + scannedDevices.size() + " devices");
if (callback != null) {
callback.onScanCompleted(new ArrayList<>(scannedDevices.values()));
}
}
}
/**
* 通知扫描失败
*/
private void notifyScanFailed(int errorCode) {
if (callback != null) {
callback.onScanFailed(errorCode);
}
}
/**
* 判断扫描是否正在进行
*/
public boolean isScanning() {
return isScanning;
}
/**
* 获取已扫描到的设备列表
*/
public List<BluetoothDevice> getScannedDevices() {
return new ArrayList<>(scannedDevices.values());
}
/**
* 扫描结果回调接口
*/
public interface BleScanCallback {
int ERROR_BLE_NOT_SUPPORTED = 1001;
int ERROR_BLUETOOTH_DISABLED = 1002;
int ERROR_MISSING_PERMISSIONS = 1003;
int ERROR_SCAN_FAILED = 1004;
/**
* 发现新设备
*/
void onDeviceFound(BluetoothDevice device, int rssi, BleAdvertisementData advertisementData);
/**
* 扫描完成
*/
void onScanCompleted(List<BluetoothDevice> devices);
/**
* 扫描失败
*/
void onScanFailed(int errorCode);
}
/**
* 封装BLE广播数据的类
*/
public static class BleAdvertisementData {
public String deviceName;
public int rssi;
public int txPower;
public byte flags;
public List<UUID> serviceUuids;
public Map<Integer, byte[]> manufacturerData = new HashMap<>();
public byte[] rawBytes;
/**
* 将字节数组转换为十六进制字符串
*/
public static String bytesToHex(byte[] bytes) {
if (bytes == null) return "";
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02X ", b));
}
return result.toString().trim();
}
@Override
public String toString() {
return "BleAdvertisementData{" +
"deviceName='" + deviceName + '\'' +
", rssi=" + rssi +
", txPower=" + txPower +
", serviceUuids=" + serviceUuids +
", manufacturerData size=" + manufacturerData.size() +
", rawBytes length=" + (rawBytes != null ? rawBytes.length : 0) +
'}';
}
}
}
2 使用
// 初始化 BLE 扫描器
bleScanner = new BleScanner(this);
bleScanner.setScanCallback(new BleScanner.BleScanCallback() {
@Override
public void onDeviceFound(BluetoothDevice device, int rssi, BleScanner.BleAdvertisementData advertisementData) {
// 处理发现的设备
runOnUiThread(() -> {
LogsUtils.d("BleScanner", "发现设备: " + device.getName() + " (" + device.getAddress() + ")");
LogsUtils.d("BleScanner", "RSSI: " + rssi);
if (advertisementData.deviceName != null) {
LogsUtils.d("BleScanner", "设备名称: " + advertisementData.deviceName);
}
// 处理服务 UUID
if (advertisementData.serviceUuids != null && !advertisementData.serviceUuids.isEmpty()) {
StringBuilder uuidStr = new StringBuilder();
for (UUID uuid : advertisementData.serviceUuids) {
uuidStr.append(uuid.toString()).append("\n");
}
LogsUtils.d("BleScanner", "服务 UUID:\n" + uuidStr);
}
// 处理制造商数据(如 iBeacon)
if (advertisementData.manufacturerData != null) {
for (int manufacturerId : advertisementData.manufacturerData.keySet()) {
byte[] data = advertisementData.manufacturerData.get(manufacturerId);
LogsUtils.d("BleScanner", "制造商 ID: 0x" + String.format("%04X", manufacturerId));
LogsUtils.d("BleScanner", "制造商数据: " + BleScanner.BleAdvertisementData.bytesToHex(data));
// 示例:解析 iBeacon 数据
if (manufacturerId == 0x004C) { // Apple 公司 ID
parseIBeaconData(data);
}
}
}
});
}
@Override
public void onScanCompleted(List<BluetoothDevice> devices) {
// 扫描完成回调
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "扫描完成,发现 " + devices.size() + " 个设备", Toast.LENGTH_SHORT).show();
LogsUtils.d("BleScanner", "扫描完成,共发现 " + devices.size() + " 个设备");
});
}
@Override
public void onScanFailed(int errorCode) {
// 扫描失败回调
runOnUiThread(() -> {
String errorMsg = "扫描失败,错误码: " + errorCode;
Toast.makeText(MainActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
Log.e("BleScanner", errorMsg);
});
}
});
/**
* 开始 BLE 扫描
*/
private void startBleScan() {
// 配置扫描参数(可选)
bleScanner.setScanPeriod(15000); // 扫描15秒
bleScanner.setDuplicateFilterEnabled(true); // 过滤重复设备
// 开始扫描
bleScanner.startScan();
Toast.makeText(this, "正在扫描 BLE 设备...", Toast.LENGTH_SHORT).show();
}
/**
* 停止 BLE 扫描
*/
private void stopBleScan() {
if (bleScanner.isScanning()) {
bleScanner.stopScan();
Toast.makeText(this, "已停止扫描", Toast.LENGTH_SHORT).show();
}
}
3 权限配置
<!-- BLE基础权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- Android 12+ 权限 -->
<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"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
android:maxSdkVersion="30" />
<!-- 蓝牙和BLE功能声明 -->
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />