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

一、ScanCallback概述

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

二、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);
        }
    }
}
相关推荐
黄昏晓x2 小时前
Linux----进程控制
android·linux·运维
我是阿亮啊2 小时前
android中事件分发机制
android·事件分发·事件分发机制
心前阳光3 小时前
Unity 模拟父子关系
android·unity·游戏引擎
2501_915106323 小时前
当 Perfdog 开始收费之后,我重新整理了一替代方案
android·ios·小程序·https·uni-app·iphone·webview
多多*3 小时前
2月3日面试题整理 字节跳动后端开发相关
android·java·开发语言·网络·jvm·adb·c#
习惯就好zz5 小时前
[Android/Linux] 实战记录:利用 Kconfig 精确控制 i.MX8MM 特定 DTB 的编译生成
android·linux·dts·dtb·lunch·多卡板配置
踏雪羽翼5 小时前
android 解决混淆导致AGPBI: {“kind“:“error“,“text“:“Type a.a is defined multiple times
android·java·开发语言·混淆·混淆打包出现a.a
csj505 小时前
安卓基础之《(21)—高级控件(3)翻页类视图》
android
2501_915918415 小时前
中小团队发布,跨平台 iOS 上架,证书、描述文件创建管理,测试分发一体化方案
android·ios·小程序·https·uni-app·iphone·webview