连接到 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 会与名为 BluetoothLeService 的 Service 通信,后者会使用 BLE API 与 BLE 设备交互。绑定服务可让 activity 连接到 BluetoothLeService 并调用函数来连接到设备,从而处理此通信。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

activity 绑定到服务后,服务需要访问 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 需要将设备地址发送到服务,以便服务可以发起连接。该服务首先在 BluetoothAdapter 上调用 getRemoteDevice() 以访问设备。如果适配器无法找到具有该地址的设备,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 数据。

当与设备的 GATT 服务器的连接发生变化时,系统会触发 onConnectionStateChange() 函数。在以下示例中,回调在 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 服务器或与其断开连接时,需要向 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 需要实现 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
        }
    }
}