赞
踩
前言:
Android 4.3(API Level 18)开始引入Bluetooth Low Energy(BLE,低功耗蓝牙)的核心功能并提供了相应的 API, 应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作
Android BLE 使用的蓝牙协议是 GATT 协议,有关该协议的详细内容可以参见蓝牙官方文档:点击此处
1、扫描工具类BleScanUtils:
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.bluetooth.le.BluetoothLeScanner import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanSettings import android.content.Context import android.os.Build import android.text.TextUtils import android.util.Log import androidx.annotation.RequiresApi import java.util.* /** * 类说明:扫描工具类 */ class BleScanUtils private constructor() { private val TAG = "BleScanUtils" //本地蓝牙设备的适配类,所有的蓝牙操作都要通过该类完成 private var bluetoothAdapter: BluetoothAdapter? = null //扫描回调 private var leScanner: BluetoothLeScanner? = null //蓝牙列表 private var scanResultSet: MutableSet<BluetoothDevice>? = null //结果回调 private lateinit var listener: (MutableSet<BluetoothDevice>) -> Unit fun setOnListener(bluetoothDevice: (MutableSet<BluetoothDevice>) -> Unit) { listener = bluetoothDevice } companion object { val getInstance: BleScanUtils by lazy { BleScanUtils() } } /** * 开始搜索蓝牙设备 * Android官方提供的蓝牙扫描方式有三种,分别是: * 1、BluetoothAdapter.startDiscovery()//可以扫描经典蓝牙和ble蓝牙两种 * 2、BluetoothAdapter.startLeScan()//扫描低功耗蓝牙,在api21已经弃用,不过还是可以使用 * 3、BluetoothLeScanner.startScan()//新的ble扫描方法 */ fun startScanBle(context: Context) { if (null == scanResultSet) { scanResultSet = HashSet() } else { scanResultSet!!.clear() } val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter leScanner = bluetoothAdapter!!.bluetoothLeScanner if (bluetoothAdapter != null && bluetoothAdapter!!.isEnabled) { //安卓版本是否大于6.0 val settings: ScanSettings = if (Build.VERSION.SDK_INT >= 23) { ScanSettings.Builder() //设置扫描模式。可选择模式主要三种( 从上到下越来越耗电,扫描间隔越来越短,即扫描速度会越来越快。 //1、SCAN_MODE_LOW_POWER:低功耗模式(默认扫描模式,如果扫描应用程序不在前台,则强制使用此模式。) //2、SCAN_MODE_BALANCED: 平衡模式 .setScanMode(ScanSettings.SCAN_MODE_BALANCED) //设置回调类型。可选择模式主要三种: //1、CALLBACK_TYPE_ALL_MATCHES:1 寻找符合过滤条件的蓝牙广播,如果没有设置过滤条件,则返回全部广播包 //2、CALLBACK_TYPE_FIRST_MATCH:2 与筛选条件匹配的第一个广播包触发结果回调 //3、CALLBACK_TYPE_MATCH_LOST:4 有过滤条件时过滤,返回符合过滤条件的蓝牙广播。无过滤条件时,返回全部蓝牙广播 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) //设置蓝牙扫描滤波器硬件匹配的匹配模式 //MATCH_MODE_STICKY:2 粘性模式,在通过硬件报告之前,需要更高的信号强度和目击阈值 //MATCH_MODE_AGGRESSIVE:1 激进模式,即使信号强度微弱且持续时间内瞄准/匹配的次数很少,hw也会更快地确定匹配。 //.setMatchMode(ScanSettings.MATCH_MODE_STICKY) //设置蓝牙LE扫描的报告延迟的时间(以毫秒为单位)。 //该参数默认为 0,如果不修改它的值,则默认只会在onScanResult(int,ScanResult)中返回扫描到的蓝牙设备,不会触发onBatchScanResults(List)方法。(onScanResult(int,ScanResult) 和 onBatchScanResults(List) 是互斥的。 ) //设置为0以立即通知结果,不开启批处理扫描模式。即ScanCallback蓝牙回调中,不会触发onBatchScanResults(List)方法,但会触发onScanResult(int,ScanResult)方法,返回扫描到的蓝牙设备。 //当设置的时间大于0L时,则会开启批处理扫描模式。即触发onBatchScanResults(List)方法,返回扫描到的蓝牙设备列表。但不会触发onScanResult(int,ScanResult)方法。 //.setReportDelay(0) .build() } else { ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build() } //第一个参数(ScanFilter):筛选条件,可以通过设置过滤器的mDeviceName、mDeviceAddress、mServiceUuid等作为过滤条件进行过滤。 //第二个参数(ScanSettings):设置,可以设置扫描的mScanMode 、mCallbackType 、mScanResultType 等。 //List<ScanFilter> scanFilters = new ArrayList<>(); //scanFilters.add(new ScanFilter.Builder().setDeviceName("deviceName").build()); leScanner!!.startScan(null, settings, leScanCallback) } } /** * 回调函数中尽量不要做耗时操作 */ private val leScanCallback: ScanCallback = object : ScanCallback() { @RequiresApi(api = Build.VERSION_CODES.O) override fun onScanResult(callbackType: Int, result: ScanResult) { super.onScanResult(callbackType, result) //信号强度【在dBm中返回接收到的信号强度。有效范围为[-127,126]。】 val rssi = result.rssi.toString() //ScanRecord record =result.getScanRecord();//搜索记录相关 val device = result.device //设备名称 val deviceName = device.name //设备MAC地址 val deviceAddress = device.address //BOND_NONE:数值 10 表示远程设备未绑定,没有共享链接密钥,因此通信(如果允许的话)将是未经身份验证和未加密的。【默认10未绑定】 //BOND_BONDING:数值 11 表示正在与远程设备进行绑定; //BOND_BONDED:数值 12 表示远程设备已绑定,远程设备本地存储共享连接的密钥,因此可以对通信进行身份验证和加密 val bondState = device.bondState //设备的蓝牙设备类型 //DEVICE_TYPE_CLASSIC 传统蓝牙 常量值:1, //DEVICE_TYPE_LE 低功耗蓝牙 常量值:2【一般为2,表示为BLE设备】 //DEVICE_TYPE_DUAL 双模蓝牙 常量值:3. //DEVICE_TYPE_UNKNOWN:未知 常量值:0) val type = device.type //过滤掉无设备名【注意:无设备名不代表无mac地址】和被绑定的 if (!TextUtils.isEmpty(deviceName) && !scanResultSet!!.contains(device)) { //SET添加不重复元素【当set为BluetoothDevice或基本数据类型不会重复添加 当set为ScanResult会重复添加】 scanResultSet!!.add(device) listener.invoke(scanResultSet!!) } } override fun onBatchScanResults(results: List<ScanResult>) { Log.e(TAG, "onBatchScanResults: " + results.size) } override fun onScanFailed(errorCode: Int) { //手机在startScan扫描的过程中还没来得及stopScan ,就被系统强制杀掉了, 导致mClientIf未被正常释放, //实例和相关蓝牙对象已被残留到系统蓝牙服务中, 打开app后又重新初始化ScanCallback多次被注册, //导致每次的扫描mClientIf的值都在递增, 于是mClientIf的值在增加到一定程度时 //(最大mClientIf数量视国产系统而定不做深究), onScanFailed 返回了 errorCode =2 。 Log.e(TAG, "scan error: $errorCode") } } /** * 关闭扫描 */ fun stopScanBle() { if (leScanner != null && leScanCallback != null) { if (bluetoothAdapter != null && bluetoothAdapter!!.isEnabled) { leScanner!!.stopScan(leScanCallback) } } } }
2、使用类MainActivity:
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import com.helloword.testdemo.BleScanUtils /** * 测试机型:小米10 Anroid版本:11 * 附近设备:1 * 57:4C:42:00:91:7F * IMT60 00917F */ class MainActivity : AppCompatActivity() { private val TAG = "MainActivity" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //开始扫描 BleScanUtils.getInstance.startScanBle(this) //扫描回调 BleScanUtils.getInstance.setOnListener { for (i in it) { Log.e(TAG, "onCreate: " + i.address) Log.e(TAG, "onCreate: " + i.name) } Log.e(TAG, "onCreate: 附近设备:" + it.size) } } override fun onDestroy() { super.onDestroy() //关闭扫描 BleScanUtils.getInstance.stopScanBle() } }
3、AndroidManifest.xml:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
注意:动态权限不做说明,若扫描不到,请到设置-应用管理-手动打开定位、蓝牙权限!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。