Android 蓝牙/Wi-Fi通信协议之:蓝牙扫描ScanCallback详解

一、ScanCallback概述

ScanCallback是Android BLE扫描的核心回调类,用于接收蓝牙低功耗(BLE)设备的扫描结果。当使用BluetoothLeScanner的startScan()方法开始扫描后,所有扫描结果都会通过这个回调返回。

二、ScanCallback的主要方法

1. onScanResult(int callbackType, ScanResult result)

触发时机:每次扫描到一个BLE设备时调用

参数说明:

  • callbackType:扫描回调类型,可能是以下值之一:

    • ScanSettings.CALLBACK_TYPE_ALL_MATCHES (0):所有匹配的广告包

    • ScanSettings.CALLBACK_TYPE_FIRST_MATCH (1):第一次匹配的设备

    • ScanSettings.CALLBACK_TYPE_MATCH_LOST (2):之前匹配但现在丢失的设备

  • result:包含扫描到的设备信息的ScanResult对象

典型实现:

java 复制代码
@Override
public void onScanResult(int callbackType, ScanResult result) {
    super.onScanResult(callbackType, result);
    BluetoothDevice device = result.getDevice();
    String deviceName = device.getName();
    String deviceAddress = device.getAddress();
    int rssi = result.getRssi();
    ScanRecord scanRecord = result.getScanRecord();
    
    // 处理扫描到的设备
}

2. onBatchScanResults(List<ScanResult> results)

触发时机:当系统批量返回扫描结果时调用(不是每次扫描到设备都立即回调)

参数说明:

  • results:包含多个扫描结果的列表

典型实现:

java 复制代码
@Override
public void onBatchScanResults(List<ScanResult> results) {
    super.onBatchScanResults(results);
    for (ScanResult result : results) {
        // 处理每个扫描结果
    }
}

3. onScanFailed(int errorCode)

触发时机:当扫描失败时调用

参数说明:

  • errorCode:错误代码,可能是以下值之一:

    • ScanCallback.SCAN_FAILED_ALREADY_STARTED (1):扫描已经启动

    • ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED (2):应用注册失败

    • ScanCallback.SCAN_FAILED_INTERNAL_ERROR (3):内部错误

    • ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED (4):不支持的特性

    • ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES (5):硬件资源不足(Android 10+)

典型实现:

java 复制代码
@Override
public void onScanFailed(int errorCode) {
    super.onScanFailed(errorCode);
    String errorMessage;
    switch (errorCode) {
        case ScanCallback.SCAN_FAILED_ALREADY_STARTED:
            errorMessage = "Scan already started";
            break;
        case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
            errorMessage = "Application registration failed";
            break;
        case ScanCallback.SCAN_FAILED_INTERNAL_ERROR:
            errorMessage = "Internal error";
            break;
        case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED:
            errorMessage = "Feature unsupported";
            break;
        case ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES:
            errorMessage = "Out of hardware resources";
            break;
        default:
            errorMessage = "Unknown error";
    }
    // 处理扫描失败
}

三、ScanResult详解

ScanResult对象包含扫描到的BLE设备的详细信息:

1. 获取设备基本信息

java 复制代码
BluetoothDevice device = result.getDevice();
String deviceName = device.getName();    // 设备名称(可能为null)
String deviceAddress = device.getAddress(); // 设备MAC地址
int rssi = result.getRssi();           // 信号强度(dBm)
long timestampNanos = result.getTimestampNanos(); // 时间戳(纳秒)

2. 获取扫描记录(ScanRecord)

java 复制代码
ScanRecord scanRecord = result.getScanRecord();
if (scanRecord != null) {
    // 获取设备名称(可能比BluetoothDevice.getName()更可靠)
    String scanRecordName = scanRecord.getDeviceName();
    
    // 获取广播数据
    byte[] advertiseData = scanRecord.getBytes();
    
    // 获取服务UUID列表
    List<ParcelUuid> serviceUuids = scanRecord.getServiceUuids();
    
    // 获取制造商特定数据
    byte[] manufacturerData = scanRecord.getManufacturerSpecificData(manufacturerId);
    
    // 获取服务数据
    byte[] serviceData = scanRecord.getServiceData(serviceUuid);
    
    // 获取TX功率级别
    int txPower = scanRecord.getTxPowerLevel();
}

四、扫描配置详解

1. ScanSettings - 扫描设置

java 复制代码
ScanSettings settings = new ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // 扫描模式
    .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // 回调类型
    .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) // 匹配模式
    .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) // 匹配数量
    .setReportDelay(0L) // 报告延迟(毫秒)
    .build();

扫描模式(ScanMode):

  • SCAN_MODE_LOW_POWER (0):低功耗模式,扫描间隔长

  • SCAN_MODE_BALANCED (1):平衡模式

  • SCAN_MODE_LOW_LATENCY (2):低延迟模式,扫描间隔短

  • SCAN_MODE_OPPORTUNISTIC (-1):机会主义模式(不常用)

匹配模式(MatchMode):

  • MATCH_MODE_AGGRESSIVE (1):积极匹配

  • MATCH_MODE_STICKY (2):粘性匹配

2. ScanFilter - 扫描过滤器

java 复制代码
List<ScanFilter> filters = new ArrayList<>();
filters.add(new ScanFilter.Builder()
    .setDeviceName("MyDevice") // 设备名称
    .setDeviceAddress("00:11:22:33:44:55") // MAC地址
    .setServiceUuid(ParcelUuid.fromString("0000ffe0-0000-1000-8000-00805f9b34fb")) // 服务UUID
    .setServiceData(
        ParcelUuid.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"),
        new byte[]{0x01, 0x02}, // 服务数据
        new byte[]{0xFF, 0xFF} // 服务数据掩码(可选)
    )
    .setManufacturerData(
        0x004C, // 制造商ID(如Apple是0x004C)
        new byte[]{0x01, 0x02}, // 制造商数据
        new byte[]{0xFF, 0xFF} // 制造商数据掩码(可选)
    )
    .build());

五、完整扫描示例

java 复制代码
public class BleScanner {
    private BluetoothLeScanner bluetoothLeScanner;
    private ScanCallback scanCallback;
    
    public void startScan() {
        BluetoothManager bluetoothManager = (BluetoothManager) 
            context.getSystemService(Context.BLUETOOTH_SERVICE);
        BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
        bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
        
        // 扫描设置
        ScanSettings settings = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .setReportDelay(0)
            .build();
            
        // 扫描过滤器(可选)
        List<ScanFilter> filters = new ArrayList<>();
        filters.add(new ScanFilter.Builder()
            .setServiceUuid(ParcelUuid.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"))
            .build());
            
        // 扫描回调
        scanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                processResult(result);
            }
            
            @Override
            public void onBatchScanResults(List<ScanResult> results) {
                for (ScanResult result : results) {
                    processResult(result);
                }
            }
            
            @Override
            public void onScanFailed(int errorCode) {
                Log.e("BleScanner", "Scan failed with error: " + errorCode);
            }
            
            private void processResult(ScanResult result) {
                BluetoothDevice device = result.getDevice();
                String deviceName = device.getName();
                String deviceAddress = device.getAddress();
                int rssi = result.getRssi();
                
                // 处理扫描结果
            }
        };
        
        // 开始扫描
        bluetoothLeScanner.startScan(filters, settings, scanCallback);
    }
    
    public void stopScan() {
        if (bluetoothLeScanner != null && scanCallback != null) {
            bluetoothLeScanner.stopScan(scanCallback);
        }
    }
}
相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui