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);
        }
    }
}
相关推荐
云起SAAS16 小时前
抖音小游戏源码 - 消消乐 | 含激励广告+成就系统 | 开箱即用商业级消除游戏模板
android·游戏·广告联盟·看激励广告联盟流量主·抖音小游戏源码 - 消消乐
大貔貅喝啤酒17 小时前
基于Windows下载安装Android Studio 3.3.2版本教程(2026详细图文版)
android·java·windows·android studio
程序员码歌17 小时前
OpenSpec 到 Superpowers:AI 编码从说清到做对
android·前端·人工智能
2501_9151063217 小时前
深入解析无源码iOS加固原理与方案,保护应用安全
android·安全·ios·小程序·uni-app·cocoa·iphone
黄林晴21 小时前
重磅官宣:Android UI 开发正式进入 Compose-first 时代
android·google io
Kapaseker21 小时前
搞懂变换!精通 Compose 绘制(二)
android·kotlin
美狐美颜SDK开放平台21 小时前
美颜SDK开发详解:如何优化美颜SDK在低端安卓机上的性能?
android·ios·音视频·直播美颜sdk·视频美颜sdk
Gary Studio21 小时前
深入MTK Android BSP:如何确定编译目标与查找项目设备树
android
casual_clover21 小时前
【Android】实现状态栏背景透明,系统时间/图标直接显示在页面背景上
android·透明状态栏
blackorbird1 天前
Android Pixel 10 零点击漏洞利用链
android