Android Bluetooth(一)— 扫描和配对附近的设备

2,814 阅读3分钟

现在大部分手机都有蓝牙功能,可以与其他有蓝牙功能的设备连接、传输数据等。本文介绍如何使用蓝牙框架API扫描和配对附近的设备。

官方文档

申请权限

如果要在App中使用蓝牙功能,需要申请多个权限,具体如下:

targetSdk 31(Android 12)及以上

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!--允许App连接到配对的蓝牙设备(兼容低版本)-->
    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />

    <!--允许App发现和配对蓝牙设备(兼容低版本)-->
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

    <!--若App需要扫描蓝牙设备,申请此权限-->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

    <!--若App使当前设备可供其他蓝牙设备发现,申请此权限-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

    <!--若App与蓝牙设备配对、通讯,申请此权限-->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    
    <!--若App通过蓝牙扫描结果获取物理位置,申请此权限-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

确定不会使用蓝牙扫描结果获取物理位置时,设置BLUETOOTH_SCAN权限的usesPermissionFlags,就无需再申请ACCESS_FINE_LOCATION权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!--若App需要扫描蓝牙设备,申请此权限-->
    <!--当usesPermissionFlags设置为neverForLocation时,无需再申请ACCESS_FINE_LOCATION权限-->
    <uses-permission
        android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation" />
</manifest>

需要注意的是,BLUETOOTH_SCANBLUETOOTH_ADVERTISEBLUETOOTH_CONNECT均为运行时权限,需要申请并获得用户同意后,才能扫描附近蓝牙设备、被其他蓝牙设备发现或与其他蓝牙设备配对和通信,代码如下:

class BluetoothExampleActivity : AppCompatActivity() {

    private val requestMultiplePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions: Map<String, Boolean> ->
        val noGrantedPermissions = ArrayList<String>()
        permissions.entries.forEach {
            if (!it.value) {
                noGrantedPermissions.add(it.key)
            }
        }
        if (noGrantedPermissions.isEmpty()) {
            // 所有申请权限通过,可以执行后续操作
        } else {
            //未同意授权
            noGrantedPermissions.forEach {
                if (!shouldShowRequestPermissionRationale(it)) {
                    //用户拒绝权限并且系统不再弹出请求权限的弹窗
                    //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val requestPermissionNames = arrayOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_ADVERTISE, Manifest.permission.BLUETOOTH_CONNECT)
        if (requestPermissionNames.find { ActivityCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } != null) {
            requestMultiplePermissionLauncher.launch(requestPermissionNames)
        } else {
            // 所有申请权限通过,可以执行后续操作
        }
    }
}

targetSdk 30(Android 11)及以下

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!--允许App请求连接、接受连接或传输数据-->
    <uses-permission android:name="android.permission.BLUETOOTH"/>

    <!--允许App发现和配对蓝牙设备-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    
    <!--在Android 11(30)及以下版本中,位置权限是必须的-->
    <!--在Android 9(28)及以下版本中,可以使用ACCESS_COARSE_LOCATION权限替代ACCESS_FINE_LOCATION权限-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

需要注意的是,位置权限为运行时权限,需要申请并获得用户同意后,才能扫描附近蓝牙设备,代码如下:

class BluetoothExampleActivity : AppCompatActivity() {

    private val requestPermissionName = Manifest.permission.ACCESS_FINE_LOCATION

    private val requestSinglePermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted: Boolean ->
        if (granted) {
            // 申请权限通过,可以执行后续操作
        } else {
            if (!shouldShowRequestPermissionRationale(requestPermissionName)) {
                //用户拒绝权限并且系统不再弹出请求权限的弹窗
                //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (ActivityCompat.checkSelfPermission(this, requestPermissionName) == PackageManager.PERMISSION_GRANTED) {
            // 申请权限通过,可以执行后续操作
        } else {
            requestSinglePermissionLauncher.launch(requestPermissionName)
        }
    }
}

检查与开启蓝牙

检查是否支持蓝牙功能

要使用蓝牙功能,首先需要确定当前设备是否支持蓝牙功能,代码如下:

class BluetoothExampleActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val bluetoothAdapter = (getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
        if (bluetoothAdapter != null) {
            // 当前设备支持蓝牙功能    
        }
    }
}

开启蓝牙

确认当前设备支持蓝牙功能后,检测蓝牙是否开启,未开启的话可以调用系统方法开启蓝牙,代码如下:

class BluetoothExampleActivity : AppCompatActivity() {

    private val intentLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
        if (activityResult.resultCode == Activity.RESULT_OK) {
            // 成功开启蓝牙
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val bluetoothAdapter = (getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
        if (bluetoothAdapter != null) {
            if (bluetoothAdapter.isEnabled != true) {
                // 蓝牙未开启,通过系统启用蓝牙
                intentLauncher.launch(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE))
            }
        }
    }
}

扫描和配对附近的设备

权限已申请完毕、蓝牙功能开启后,可以扫描附近的设备,进行配对。

获取已配对过的设备

当前设备可能已经与某些蓝牙设备配对过,获取已配对过的设备的代码如下:

class BluetoothExampleActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val bluetoothAdapter = (getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
        // 已绑定的设备
        val bondedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
    }
}

扫描附近设备并进行配对

注册广播接收者,监听扫描结果,使用startDiscovery扫描附近其他蓝牙设备。可以使用BluetoothDevicecreateBond方法进行配对,代码如下:

class BluetoothExampleActivity : AppCompatActivity() {

    private val scanResultReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            when (intent?.action) {
                BluetoothDevice.ACTION_FOUND -> {
                    // 发现的蓝牙设备
                    val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
                    // 如果已经找到想要的设备,可以停止扫描
                    bluetoothAdapter?.cancelDiscovery()
                    
                    // 与发现的蓝牙设备配对
                    device?.createBond()
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 注册蓝牙设备扫描结果监听
        registerReceiver(scanResultReceiver, IntentFilter().apply {
            addAction(BluetoothDevice.ACTION_FOUND)
        })
        val bluetoothAdapter = (getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter
        bluetoothAdapter?.startDiscovery()
    }

    override fun onDestroy() {
        super.onDestroy()
        // 取消扫描结果监听
        unregisterReceiver(scanResultReceiver)
    }
}

示例

效果如图:

device-2023-06-23-12 -original-original.gif

完整演示代码已在示例Demo中添加。

ExampleDemo github

ExampleDemo gitee