BleScanner ble广播扫描

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" />

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容