欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android蓝牙Ble基本操作-(扫描1)

程序员文章站 2024-03-24 23:14:40
...

前言:
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" />

注意:动态权限不做说明,若扫描不到,请到设置-应用管理-手动打开定位、蓝牙权限!