最近正好项目有蓝牙需求,折腾了小一会儿,特意记录一下~

网上好多搜索到的文章没法用,尴尬死。。。或者就是 Java 版本,转 Kotlin 多少有点坑,不过更加加深了要好好学习一下 Kotlin 的想法~

image.png

附上脑图:

image.png

Demo GitHub 地址:

蓝牙简述

蓝牙的出现,让移动设备多了一种交换数据的方式,而 Android 应用可以通过 Bluetooth Api 执行如下操作:

  • 扫描其他蓝牙设备
  • 查询本地蓝牙适配器的配对蓝牙设备
  • 建立 RFCOMM 通道
  • 通过服务发现连接到其他设备
  • 与其他设备进行双向数据传输
  • 管理多个连接

而我们今天,主要是进行第二点,查询本地蓝牙适配器的配对蓝牙设备。

传统的蓝牙适用于较为耗电的操作,其中包括 Android 设备之间的流式传输和通信等。而针对具有低功耗要求的蓝牙设备,Android 4.3(API 18)中引入面向低功耗蓝牙的 API 支持。

1、基础知识

为了让蓝牙设备可以在彼此之间传输数据,必须先通过配对过程形成通道。

其中一台设备需要将自身设置为可接收传入的连接请求,另一台设备则通过服务发现过程并找到可检测的设备。

随后在检测到的设备接受配对请求后,设备之间完成绑定操作,并在此期间交换安全密钥。二者会缓存这些密钥,以供日后使用。

完成配对和绑定过程后,两台设备会交换信息。

当会话完成时,发起配对请求的设备会将其链接到可检测设备的通道。

并且这俩台设备仍然保持绑定状态,因此在未来的会话期间,只要两者在彼此的范围内且均为移除绑定,便会自动连接。

2、蓝牙权限

基础权限必须声明:

<uses-permission android:name="android.permission.BLUETOOTH" />

如果想对蓝牙进行相关操作,比如打开蓝牙等,需要配置如下权限:

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

而当应用需要扫描其他设备时,需要声明如下权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

这里需要注意的:

  • 如果应用适配 Android 9(Api 28)或者更低版本,则可以声明 ACCESS_COARSE_LOCATION 权限,而非 ACCESS_FINE_LOCATION 权限。

如果需要声明当前应用依然适用于不支持 BLE 的设备,则需要在权限中添加如下元素:

<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>

蓝牙 Demo 搞起(获取已配对列表以及已连接蓝牙名称)

1、必不可少的权限

<uses-permission android:name="android.permission.BLUETOOTH" />

2、工具类

package com.hlq.bluetoothpro.utils

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothProfile
import android.content.Context
import android.media.AudioManager
import android.util.Log
import java.lang.reflect.InvocationTargetException

/**
 * @author:HLQ_Struggle
 * @date:2021/8/29
 * @desc:
 */

private var mBluetoothAdapter: BluetoothAdapter? = null

/**
 * 实例化 BluetoothAdapter
 */
private fun getInstance(): BluetoothAdapter? {
    if (mBluetoothAdapter == null) {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
    }
    return mBluetoothAdapter
}

/**
 * 检测设备是否支持蓝牙
 */
fun checkBluetoothEnable(): Boolean {
    return getInstance() == null
}

/**
 * 判断当前蓝牙是否打开
 */
fun checkBluetoothStateEnable(): Boolean {
    return getInstance()?.isEnabled == true
}

/**
 * 获取蓝牙耳机连接状态
 */
private fun isWiredHeadsetConnected(context: Context): Boolean {
    try {
        val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
        return audioManager.isWiredHeadsetOn
    } catch (e: Exception) {
    }
    return false
}

/**
 * 判断蓝牙耳机是否已连接
 */
fun hasBluetoothAudioDevice(): Boolean {
    val adapter = BluetoothAdapter.getDefaultAdapter()
    var a2dp = false
    var headset = false
    try {
        a2dp =
            adapter.getProfileConnectionState(BluetoothProfile.A2DP) != BluetoothProfile.STATE_DISCONNECTED
        headset =
            adapter.getProfileConnectionState(BluetoothProfile.HEADSET) != BluetoothProfile.STATE_DISCONNECTED
    } catch (e: Throwable) {
    }
    return a2dp || headset
}

/**
 * 获取到已配对成功蓝牙设备
 */
fun fetchAlReadyConnection() {
    getInstance()?.let {
        val devices = it.bondedDevices
        for (device in devices) {
            Log.e(
                "HLQ", "----> " +
                        "name ${device.name} " +
                        "address ${device.address} " +
                        "bondState ${device.bondState} " +
                        "type ${device.type} ${device.uuids.size}"
            )
        }
    }
}

fun getConnectedBtDevice(): String? {
    //获取蓝牙适配器
    val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
    //得到已匹配的蓝牙设备列表
    val bondedDevices = bluetoothAdapter.bondedDevices
    if (bondedDevices != null && bondedDevices.size > 0) {
        for (bondedDevice in bondedDevices) {
            try {
                //使用反射调用被隐藏的方法
                val isConnectedMethod =
                    BluetoothDevice::class.java.getDeclaredMethod(
                        "isConnected"
                    )
                isConnectedMethod.isAccessible = true
                val isConnected =
                    isConnectedMethod.invoke(bondedDevice) as Boolean
                if (isConnected) {
                    return bondedDevice.name
                }
            } catch (e: NoSuchMethodException) {
                e.printStackTrace()
            } catch (e: IllegalAccessException) {
                e.printStackTrace()
            } catch (e: InvocationTargetException) {
                e.printStackTrace()
            }
        }
    }
    return null
}

3、创建监听蓝牙广播

package com.hlq.bluetoothpro.receiver

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import com.hlq.bluetoothpro.utils.checkBluetoothStateEnable
import com.hlq.bluetoothpro.utils.fetchAlReadyConnection
import com.hlq.bluetoothpro.utils.getConnectedBtDevice
import com.hlq.bluetoothpro.utils.hasBluetoothAudioDevice

/**
 * @author:HLQ_Struggle
 * @date:2021/8/28
 * @desc:
 */
class BluetoothReceiver : BroadcastReceiver() {

    companion object {

        fun registerIntentFilter(): IntentFilter {
            val intentFilter = IntentFilter()
            intentFilter.apply {
                addAction(BluetoothAdapter.ACTION_STATE_CHANGED); // 蓝牙状态改变
                addAction("android.bluetooth.BluetoothAdapter.STATE_OFF"); // 本地蓝牙适配器已关闭
                addAction("android.bluetooth.BluetoothAdapter.STATE_ON"); // 本地蓝牙适配器已打开,可以使用
                addAction(BluetoothDevice.ACTION_ACL_CONNECTED); // 已和远程设备建立 ACL 连接
                addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); // 与远程设备 ACL 断开连接
                priority = Int.MAX_VALUE
            }
            return intentFilter
        }

    }

    override fun onReceive(context: Context?, intent: Intent?) {
        val action = intent?.action
        action ?: return
        val bluetoothLog = when (action) {
            BluetoothAdapter.ACTION_STATE_CHANGED -> { // 监听蓝牙状态
                when (val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)) {
                    BluetoothAdapter.STATE_TURNING_ON -> {
                        "STATE_TURNING_ON 蓝牙开启中"
                    }
                    BluetoothAdapter.STATE_ON -> {
                        "STATE_ON 蓝牙开启"
                    }
                    BluetoothAdapter.STATE_CONNECTING -> {
                        "STATE_CONNECTING 蓝牙连接中"
                    }
                    BluetoothAdapter.STATE_CONNECTED -> {
                        "STATE_CONNECTED 蓝牙已连接"
                    }
                    BluetoothAdapter.STATE_DISCONNECTING -> {
                        "STATE_DISCONNECTING 蓝牙断开中"
                    }
                    BluetoothAdapter.STATE_DISCONNECTED -> {
                        "STATE_DISCONNECTED 蓝牙已断开"
                    }
                    BluetoothAdapter.STATE_TURNING_OFF -> {
                        "STATE_TURNING_OFF 蓝牙关闭中"
                    }
                    BluetoothAdapter.STATE_OFF -> {
                        "STATE_OFF 蓝牙关闭"
                    }
                    else -> "ACTION_STATE_CHANGED EXTRA_STATE $state"
                }
            }
            BluetoothDevice.ACTION_ACL_CONNECTED -> { // 蓝牙已连接
                Log.e("HLQ", "----> ACTION_ACL_CONNECTED 蓝牙已连接 ") // 蓝牙已打开 且 已连接
                Log.e("HLQ", "----> 蓝牙已打开且已连接")
                Log.e("HLQ", "----> 输出已配对成功蓝牙列表")
                Log.e("HLQ", "----> ${fetchAlReadyConnection()}")

                "----> 当前连接蓝牙名称:${getConnectedBtDevice()}"
            }
            BluetoothDevice.ACTION_ACL_DISCONNECTED -> { // 蓝牙已断开
                "ACTION_ACL_DISCONNECTED 蓝牙已断开"
            }
            else -> "action $action"
        }
        Log.e("HLQ", "----> bluetoothLog $bluetoothLog")
    }

}

4、蓝牙注册、移除以及获取

package com.hlq.bluetoothpro

import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.hlq.bluetoothpro.receiver.BluetoothReceiver
import com.hlq.bluetoothpro.utils.*

class MainActivity : AppCompatActivity() {

    /**
     * 蓝牙监听 行车模式
     */
    private var mBluetoothFilter: IntentFilter? = null
    private var mBluetoothReceiver: BluetoothReceiver? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        handleBluetooth()
    }

    private fun handleBluetooth() {
        // 验证当前设备是否支持蓝牙 支持便进行初始化
        if (!checkBluetoothEnable()) {
            Log.e("HLQ", "----> 当前设备支持蓝牙")
            initBluetooth()
            if (checkBluetoothStateEnable() && hasBluetoothAudioDevice()) { // 蓝牙已打开 且 已连接
                Log.e("HLQ", "----> 蓝牙已打开且已连接")
                Log.e("HLQ", "----> 输出已配对成功蓝牙列表")
                Log.e("HLQ", "----> ${fetchAlReadyConnection()}")
                Log.e("HLQ", "----> 当前连接蓝牙名称:${getConnectedBtDevice()}")
            }
        }
    }

    /**
     * 初始化行车模式 蓝牙监听
     */
    private fun initBluetooth() {
        if (mBluetoothReceiver == null) {
            mBluetoothReceiver = BluetoothReceiver()
        }
        if (mBluetoothFilter == null) {
            mBluetoothFilter = BluetoothReceiver.registerIntentFilter()
        }
        if (mBluetoothReceiver != null && mBluetoothFilter != null) {
            registerReceiver(mBluetoothReceiver, mBluetoothFilter)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 清理蓝牙广播
        if (mBluetoothReceiver != null) {
            unregisterReceiver(mBluetoothReceiver)
            mBluetoothReceiver = null
        }
    }

}

来吧,日志展示

  • 当蓝牙已打开且蓝牙已连接
E/HLQ: ----> 当前设备支持蓝牙
E/HLQ: ----> 输出已配对成功蓝牙列表
E/HLQ: ----> 蓝牙已打开且已连接
E/HLQ: ----> name Earbuds X1 address E0:9D:FA:CA:4C:DF bondState 12 type 1 3
E/HLQ: ----> 当前连接蓝牙名称:Earbuds X1
  • 当蓝牙已打开且开始连接蓝牙(之前配对成功过,自动连接)
 E/HLQ: ----> 当前设备支持蓝牙
 E/HLQ: ----> 蓝牙已打开且已连接
 E/HLQ: ----> 输出已配对成功蓝牙列表
 E/HLQ: ----> name Earbuds X1 address E0:9D:FA:CA:4C:DF bondState 12 type 1 3 
 E/HLQ: ----> 当前连接蓝牙名称:Earbuds X1
  • 再来个蓝牙开关展示
 E/HLQ: ----> bluetoothLog STATE_TURNING_OFF 蓝牙关闭中
 E/HLQ: ----> bluetoothLog STATE_OFF 蓝牙关闭
 E/HLQ: ----> bluetoothLog STATE_TURNING_ON 蓝牙开启中
 E/HLQ: ----> bluetoothLog STATE_ON 蓝牙开启
 E/HLQ: ----> ACTION_ACL_CONNECTED 蓝牙已连接 
 E/HLQ: ----> 蓝牙已打开且已连接
 E/HLQ: ----> 输出已配对成功蓝牙列表
 E/HLQ: ----> name Earbuds X1 address E0:9D:FA:CA:4C:DF bondState 12 type 1 3
 E/HLQ: ----> kotlin.Unit
 E/HLQ: ----> bluetoothLog ----> 当前连接蓝牙名称:Earbuds X1

THK


贺biubiu
148 声望751 粉丝

Just do it.