连接到 GATT 服务器

与 BLE 设备交互的第一步是连接到该设备。更具体地说,是连接到设备上的 GATT 服务器。如需连接到 BLE 设备上的 GATT 服务器,请使用 connectGatt() 方法。此方法接受三个参数:一个 Context对象、autoConnect(一个布尔值 ,用于指明是否在 BLE 设备可用后立即自动连接到该设备)以及对一个 BluetoothGattCallback的引用:

var bluetoothGatt: BluetoothGatt? = null
// ...
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)

此方法会连接到 BLE 设备托管的 GATT 服务器,并返回一个 BluetoothGatt实例,然后 您可以使用该实例执行 GATT 客户端操作。调用方(Android 应用)是 GATT 客户端。BluetoothGattCallback 用于向客户端提供结果(例如连接状态)以及任何进一步的 GATT 客户端操作。

设置绑定服务

在以下示例中,BLE 应用提供了一个 activity (DeviceControlActivity),用于连接到蓝牙设备、显示设备数据以及显示设备支持的 GATT 服务和特征。根据用户输入,此 activity 会与名为ServiceBluetoothLeService进行通信,后者通过 BLE API 与 BLE 设备进行交互。通信是 使用绑定服务执行的,该服务允许 activity 连接到BluetoothLeService 并调用函数以 连接到设备。The BluetoothLeService 需要一个 Binder 实现,以便为 activity 提供对服务的访问权限。

class BluetoothLeService : Service() {

    private val binder = LocalBinder()

    override fun onBind(intent: Intent): IBinder? {
        return binder
    }

    inner class LocalBinder : Binder() {
        fun getService(): BluetoothLeService {
            return this@BluetoothLeService
        }
    }
}

activity 可以使用 bindService()启动服务, 传入一个 Intent 以启动 服务、一个 ServiceConnection 实现以监听连接和断开连接事件,以及一个标志 以指定其他连接选项。

class DeviceControlActivity : AppCompatActivity() {

    private var bluetoothService: BluetoothLeService? = null
    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                // call functions on service to check connection and connect to devices
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.gatt_services_characteristics)

        val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
        bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
}

设置 BluetoothAdapter

服务绑定到后,需要访问 BluetoothAdapter。它应检查设备上是否有适配器。如需详细了解 BluetoothAdapter,请参阅设置 蓝牙。以下示例将此设置代码封装在一个 initialize() 函数中,该函数会返回一个 Boolean 值,以指明是否成功。

private const val TAG = "BluetoothLeService"

class BluetoothLeService : Service() {

    private var bluetoothAdapter: BluetoothAdapter? = null

    fun initialize(): Boolean {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        if (bluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.")
            return false
        }
        return true
    }

    // ...
}

activity 会在其 ServiceConnection 实现中调用此函数。 如何处理 initialize() 函数返回的 false 值取决于您的应用。您可以向用户显示一条错误消息,指明当前设备不支持蓝牙操作,或者停用任何需要蓝牙才能正常运行的功能。在以下示例中,系统会对 activity 调用 finish(),以将用户返回到上一个界面。

class DeviceControlActivity : AppCompatActivity() {

    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                if (!bluetooth.initialize()) {
                    Log.e(TAG, "Unable to initialize Bluetooth")
                    finish()
                }
                // perform device connection
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    // ...
}

连接到设备

BluetoothLeService 实例初始化后,即可连接到 BLE 设备。activity 需要将设备地址发送到服务,以便服务可以启动连接。服务首先会对 getRemoteDevice() 调用 BluetoothAdapter 以访问设备。如果适配器无法找到 具有该地址的设备,getRemoteDevice()会抛出 IllegalArgumentException

fun connect(address: String): Boolean {
    bluetoothAdapter?.let { adapter ->
        try {
            val device = adapter.getRemoteDevice(address)
        } catch (exception: IllegalArgumentException) {
            Log.w(TAG, "Device not found with provided address.")
            return false
        }
        // connect to the GATT server on the device
        return true
    } ?: run {
        Log.w(TAG, "BluetoothAdapter not initialized")
        return false
    }
}

DeviceControlActivity 会在服务初始化后调用此 connect() 函数。activity 需要传入 BLE 设备的地址。在以下示例中,设备地址作为 intent extra 传递给 activity。

// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(
        componentName: ComponentName,
        service: IBinder
    ) {
        bluetoothService = (service as LocalBinder).getService()
        bluetoothService?.let { bluetooth ->
            if (!bluetooth.initialize()) {
                Log.e(TAG, "Unable to initialize Bluetooth")
                finish()
            }
            // perform device connection
            deviceAddress?.let { bluetooth.connect(it) }
        }
    }

    override fun onServiceDisconnected(componentName: ComponentName) {
        bluetoothService = null
    }
}

声明 GATT 回调

当 activity 告知服务要连接到哪个设备,并且服务连接到该设备后,服务需要连接到 BLE 设备上的 GATT 服务器。此连接需要一个 BluetoothGattCallback 来接收有关连接状态、服务发现、特征读取和特征通知的通知。

本主题重点介绍连接状态通知。如需了解如何执行 服务发现、特征读取和请求特征 通知,请参阅传输 BLE 数据

onConnectionStateChange() 函数在与设备的 GATT 服务器的连接发生更改时触发。 在以下示例中,回调是在 Service 类中定义的,因此它 一旦服务连接到 BluetoothDevice,即可使用该回调。

private val bluetoothGattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            // successfully connected to the GATT Server
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            // disconnected from the GATT Server
        }
    }
}

连接到 GATT 服务

声明 BluetoothGattCallback 后,服务可以使用 connect() 函数中的 BluetoothDevice 对象连接到设备上的 GATT 服务。

系统会使用 connectGatt() 函数。这需要一个 Context 对象、一个 autoConnect 布尔值标志和 BluetoothGattCallback。在此示例中,应用直接连接到 BLE 设备,因此为 autoConnect 传递了 false

此外,还添加了 BluetoothGatt 属性。这样,服务便可在不再 需要连接时关闭 连接

class BluetoothLeService : Service() {

    // ...
    private var bluetoothGatt: BluetoothGatt? = null

    // ...
    fun connect(address: String): Boolean {
        bluetoothAdapter?.let { adapter ->
            try {
                val device = adapter.getRemoteDevice(address)
                // connect to the GATT server on the device
                bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
                return true
            } catch (exception: IllegalArgumentException) {
                Log.w(TAG, "Device not found with provided address.  Unable to connect.")
                return false
            }
        } ?: run {
            Log.w(TAG, "BluetoothAdapter not initialized")
            return false
        }
    }
}

广播更新

当服务器连接到 GATT 服务器或与 GATT 服务器断开连接时,需要将新状态通知给 activity。有多种方法可以实现此目的。以下示例使用广播将服务中的信息发送到 activity。

服务声明了一个用于广播新状态的函数。此函数接受一个操作字符串,该字符串会传递给 Intent 对象,然后再广播给系统。

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

广播函数就位后,会在 BluetoothGattCallback 中使用该函数来发送有关与 GATT 服务器的连接状态的信息。常量和服务当前连接状态在服务中声明,表示 Intent 操作。

class BluetoothLeService : Service() {

    private var connectionState = STATE_DISCONNECTED

    private val bluetoothGattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // successfully connected to the GATT Server
                connectionState = STATE_CONNECTED
                broadcastUpdate(ACTION_GATT_CONNECTED)
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // disconnected from the GATT Server
                connectionState = STATE_DISCONNECTED
                broadcastUpdate(ACTION_GATT_DISCONNECTED)
            }
        }
    }

    // ...
    companion object {
        const val ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED"
        const val ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"

        private const val STATE_DISCONNECTED = 0
        private const val STATE_CONNECTED = 2
    }
}

监听 activity 中的更新

服务广播连接更新后,activity 需要 实现 BroadcastReceiver。 在设置 activity 时注册此接收器,并在 activity 离开屏幕时取消注册该接收器。通过监听来自服务的事件,activity 能够根据与 BLE 设备的当前连接状态更新界面。

class DeviceControlActivity : AppCompatActivity() {

    private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            when (intent.action) {
                BluetoothLeService.ACTION_GATT_CONNECTED -> {
                    connected = true
                    updateConnectionState(R.string.connected)
                }
                BluetoothLeService.ACTION_GATT_DISCONNECTED -> {
                    connected = false
                    updateConnectionState(R.string.disconnected)
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter())
        bluetoothService?.let { service ->
            deviceAddress?.let { address ->
                val result = service.connect(address)
                Log.d(TAG, "Connect request result=$result")
            }
        }
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(gattUpdateReceiver)
    }

    private fun makeGattUpdateIntentFilter(): IntentFilter {
        return IntentFilter().apply {
            addAction(BluetoothLeService.ACTION_GATT_CONNECTED)
            addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED)
        }
    }
}

传输 BLE 数据中, BroadcastReceiver 还用于传达服务发现以及 来自设备的特征数据。

关闭 GATT 连接

处理蓝牙连接时的一个重要步骤是在完成连接后关闭连接。为此,请对 BluetoothGatt 对象调用 close() 函数。在以下示例中,服务保留了对 BluetoothGatt 的引用。当 activity 与服务解除绑定时,连接会关闭,以避免耗尽设备电池电量。

class BluetoothLeService : Service() {

    // ...
    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }
}