Android系统编程入门系列之硬件交互——通信硬件Bluetooth

通信硬件NFC的文章,虽然可以在Android系统中通过非直接接触的形式与支持NFC硬件的设备通信,但是也只能交互一些简短的标签内容,对大量的持续性数据,却并不能很好的支持。因此针对这个弊端,可以考虑使用支持Bluetooth技术的硬件。
Android系统支持传统的Bluetooth技术,其实现功能不仅可以传输数据,还可以传输并执行远程控制指令。在Android4.3 即API 18 及以后的版本中,低功耗的Bluetooth技术(简称为BLE)取自传统Bluetooth的核心功能,可以更省功耗并支持数据传输功能。在传统蓝牙技术中,应用程序所持有的蓝牙设备可以作为蓝牙服务端,开启蓝牙等待处理其他蓝牙设备的接入请求;也可以作为蓝牙客户端,开启蓝牙查找附近的蓝牙设备并请求接入。而在BLE技术中,应用程序所持有的蓝牙设备只能作为GATT客户端,连接其他类似蓝牙耳机等BLE设备。下面应用程序将会以这三种身份分别说明。

权限声明

使用Bluetooth技术,需要在清单文件中声明蓝牙相关权限。申请权限使用<uses-permission />标签,并设置其android:name属性为对应的权限名。蓝牙相关权限中主要有三种权限,申请后可分别对应执行不同功能。首先是可以执行蓝牙通信的基础权限,其值为Manifest.permission.BLUETOOTH="android.permission.BLUETOOTH";在需要通过蓝牙获取位置信息的应用程序中,还需要获取位置权限,其值为Manifest.permission.ACCESS_FINE_LOCATION="android.permission.ACCESS_FINE_LOCATION";另外如果需要使用蓝牙的远程控制功能,需要蓝牙管理员权限,其值为Manifest.permission.BLUETOOTH_ADMIN="android.permission.BLUETOOTH_ADMIN"

使用流程

由于一个Android系统的设备上只能使用唯一的一个蓝牙硬件,因此应用程序中可以通过android.bluetooth.BluetoothAdapter蓝牙适配器操作底层的蓝牙硬件。

设置蓝牙

调用BluetoothAdapter中的静态方法getDefaultAdapter()可以获取到BluetoothAdapter蓝牙适配器对象。但是,从Android 12即API 31开始就废弃了上述方法,取而代之的是借助android.bluetooth.BluetoothManager蓝牙管理类。在能获取到Context上下文环境类对象的位置,调用其getSystemService(String name)方法,传入参数 name 值为Context.BLUETOOTH_SERVICE="bluetooth",可以获取到BluetoothManager蓝牙管理类的实例化对象,进而调用该对象的getAdapter()方法获取已经实例化的BluetoothAdapter蓝牙适配器对象。

对于不支持蓝牙硬件的设备,得到的蓝牙适配器对象为null,因此记得在应用程序中做非空判断的相关操作!

在支持蓝牙的设备上得到BluetoothAdapter对象后,还要确保设备已经开启了蓝牙功能。调用该对象的isEnabled()方法,根据返回的boolean类型结果,判断该设备是否已经正常开启了蓝牙功能;如果蓝牙处于未启用状态,还要通过调用Context上下文环境对象的startActivityForResult(Intent intent, int requestCode)方法跳转到蓝牙功能开启界面,而这里传入的参数 intent 则是标记为蓝牙界面的Intent意图对象,在该对象中设置其 action 值为BluetoothAdapter.ACTION_REQUEST_ENABLE ="android.bluetooth.adapter.action.REQUEST_ENABLE"。在跳转到蓝牙功能界面后,需要由用户手动选择开启蓝牙,并返回当前界面后,才能继续执行后续操作。

蓝牙客户端查找其他蓝牙设备

在设置蓝牙等操作之后,就是查找该设备附近范围内其他开启蓝牙功能的设备了。可以直接调用BluetoothAdapter对象的startDiscovery()方法来开启查找附近设备的功能,但是在发现设备后,系统会通过广播的形式发送已发现的设备信息给应用程序。所以还需要在某个自定义界面Activity或者服务Service中,注册BroadcastReceiver以接收该广播。

注册广播的方式可以回顾BroadcastReceiver动态注册部分内容,这里只是需要创建的IntentFilter意图过滤对象中的参数 action 值为BluetoothDevice.ACTION_FOUND="android.bluetooth.device.action.FOUND"。同时在自定义的BroadcastReceiver中处理收到的onReceive(Context context, Intent intent)方法中,对参数 intentgetParcelableExtra(String name)方法,并设置 name 值为BluetoothDevice.EXTRA_DEVICE="android.bluetooth.device.extra.DEVICE",可以获得android.bluetooth.BluetoothDevice蓝牙硬件设备类对象,在该对象中可以获取查找到的蓝牙硬件相关信息。

在查找到设备后,要及时调用BluetoothAdapter对象的cancelDiscovery()方法关闭查找功能。否则设备的蓝牙硬件会一直占用cpu资源并消耗电量,而且在后续的建立连接及数据传输时,也会由于蓝牙发现功能的开启占用大量带宽而影响后续操作效率。

对于之前已经连接配对过的设备,就不需要通过上述查找蓝牙的方式发现设备了,可以通过调用BluetoothAdapter对象的getBondedDevices()方法,直接获取已配对过的蓝牙设备,返回BluetoothDevice对象组成的Set集合。

蓝牙服务端等待其他设备连接请求

在设置蓝牙操作后,如果想将该设备作为被发现的设备等待连接,需要启动蓝牙的可检测功能。与开启蓝牙功能界面的方法类似,调用Context上下文环境对象的startActivityForResult(Intent intent, int requestCode)方法跳转到启动蓝牙可检测性功能界面,传入的参数 intent 意图对象中,设置的 action 值为BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE="android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";另外可以通过putExtra(String name, int value)设置可检测时间,其参数 name 值为BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION="android.bluetooth.adapter.extra.DISCOVERABLE_DURATION",而参数 value 值为int类型的数值,单位为秒。

同样在跳转到蓝牙可检测性功能界面后,需要由用户手动确认开启,才能等待被其他蓝牙设备查找发现。同时不管用户确认开启或取消开启功能,都会将结果回调到Context上下文环境所代表的原启动界面Activity中的onActivityResuslt(int requestCode, int resultCode, Intent data)方法中。

蓝牙Gatt客户端查找其他BLE设备

在设置蓝牙操作后,如果只是想查找附近的BLE设备并建立连接,则需要先拿到android.bluetooth.le.BluetoothLeScannerBLE扫描类对象。通过BluetoothAdapter对象的getBluetoothLeScanner()方法,可以得到BluetoothLeScannerBLE扫描类对象。之后借助该对象的startScan(ScanCallback callback)等系列方法,开始扫描查找附近的BLE设备,在查找到符合条件的设备后,会回调android.bluetooth.le.ScanCallback类型的参数 callback 对象中的onScanResult(int callbackType, ScanResult result)方法,并将结果保存在android.bluetooth.le.ScanResult扫描结果类型的参数 result 对象中。在ScanResult对象中,可以获得连接的BLE设备BlutoothDevice对象及连接所使用的协议功能等信息。

扫描查找到BLE设备后,及时调用BluetoothLeScanner对象的stopScan(ScanCallback callback)方法,参数 callback 与上文开始查找附近BLE设备方法中的参数相同,以停止当前蓝牙扫描查找功能。

连接蓝牙

在上述查找蓝牙或蓝牙可检测功能开启后被其他设备连接后,都会得到对方的BlutetoothDevice蓝牙设备类对象。通常,应用程序所在设备查找到其他蓝牙设备后,可以主动向设备建立连接请求;而当应用程序所在设备开启蓝牙可检测功能等待其他设备查找到时,只能被动的开启连接服务,等待处理其他设备的连接请求。

蓝牙客户端发起连接请求

对于主动发起蓝牙连接请求的方式,由得到的BluetoothDevice对象调用createRfcommSocketToServiceRecord(UUID uuid)方法,可以创建远程socket连接。参数 uuid 是自定义的java.util.UUID唯一索引类对象,该值必须与后文提到的被动等待连接的蓝牙设备中的监听服务中的唯一索引值一致。该方法会返回android.bluetooth.BluetoothSocket类用以保存创建的远程蓝牙socket连接。

在得到BluetoothSocket对象后,直接调用该对象的connect()方法发起连接请求。该方法调用后会一直等待连接的建立,只有建立连接后才会正常返回并执行后续操作,否则,如果请求超时或连接失败,该方法会抛出java.io.IOException异常。

蓝牙服务端等待处理连接

对于被动等待建立连接的方式,由得到的BluetoothDevice对象调用listenUsingRfcommWithServiceRecord(String name, UUID uuid)方法,建立持续监听服务。参数 name 是为该服务定义的名字;参数 uuid 是监听的唯一索引值,其值与上文中主动发起蓝牙连接请求是创建远程socket连接所传入的参数一致。该方法返回android.bluetooth.BluetoothServerSocket蓝牙连接的服务端socket类,用以保存在等待建立连接时的socket对象。

在得到BluetoothServerSocket对象后,直接调用该对象的accept()方法监听连接请求。该方法调用后也会一致等待监听状态,只有监听到匹配的连接请求后,才会返回BluetoothSocket对象,否则,如果连接失败,该方法同样会抛出java.io.IOException异常。

对于上述两种建立连接方式执行之后,便可以通过BluetoothSocket对象在蓝牙客户端和服务端之间传输数据。该对象的getInputStream()getOutputStream()方法可以分别获取socket连接的InputStream数据输入流对象和OutputStream数据输出流对象。可以借助InputStream数据输入流的read()系列方法,从socket连接中读取数据到应用程序中,反之借助OutputStream数据输出流的write()系列方法,将应用程序中的数据写入到socket连接中。同样地,这里的数据输入流的读取系列方法会一直处于等待读取状态,直到读到指定长度数据后才会返回结果,而数据输出流的写入系列方法,会在对方数据输入流读取慢导致写入缓存满时处于一直等待写入状态,直到指定数据完全写入才会返回结果。

BluetoothServerSocketBluetoothSocket对象传输数据结束后,需要分别调用他们的close()方法关闭连接,防止远程socket连接一直处于占用资源状态。

Gatt客户端连接BLE设备

对于Gatt客户端连接方式,由得到的BluetoothDevice对象调用connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)系列方法连接BLE设备的Gatt服务端,参数 context 是当前应用程序所在的上下文环境对象,参数 autoConnect 标识是否在连接该设备后自动建立Gatt服务连接,参数 callback 是建立Gatt服务连接后的回调android.bluetooth.BluetoothGattCallback抽象类。

在初次连接其他BLE设备的Gatt服务端时,会回调参数 callback 中的onServicesDiscovered(BluetoothGatt gatt, int status) 方法。而当已建立的Gatt连接状态发生改变时,会回调参数 callback 中的onConnectionStateChange(BluetoothGatt gatt, int status, int newState)方法。其他连接状态也会对应BluetoothGattCallback类中的相关方法。

BluetoothGattCallback抽象类的回调方法中,Gatt服务连接信息是通过android.bluetooth.BluetoothGatt类的参数 gatt 来传递的。通过BluetoothGatt对象的相关方法,可以执行Gatt客户端的连接及协议控制等操作,具体可需根据应用程序需求实现。只是记得在执行完相关操作后,调用参数 gattclose()方法关闭连接。

————————————————————————————————————————————————————————

上一篇:The-ith-Element


下一篇:一文读懂为何利尔达低功耗蓝牙方案在研讨会上引发热议