1、前言
SPP是蓝牙2.0版本时主要采用的通信协议,需要通过设备MAC地址建立socket连接,然后通过inputStream和outputStream进行通信。
BLE是蓝牙4.0版本开始使用的通信协议,是基于Gatt(通用属性配置文件)的通信方式。BLE支持扫描周边设备并建立连接,而SPP需要提前建立蓝牙连接。BLE扫描周边设备并获取想要的设备,建立gatt连接,通过双方设备定义好的服务特性获取BluetoothGattService并进行相关定义;通过发送数据特性从service获取BluetoothGattCharacteristic并进行相关定义;通过接收数据特性从characteristic获取BluetoothGattDescriptor并进行相关定义。之后便可以通过gatt进行通信。
private static final UUID SERVICE_CHARACTERISTIC_UUID = UUID.fromString("0000FFE0-0000-1000-8000-00805F9b34FB");
private static final UUID SEND_CHARACTERISTIC_UUID = UUID.fromString("0000FFE1-0000-1000-8000-00805F9b34FB");
private static final UUID RECEIVE_CHARACTERISTIC_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
2、实现过程
(1)在配置文件AndroidManifest.xml中<manifest></manifest>引入权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
(2)在入口文件MainActivity的入口函数onCreate方法中获取蓝牙适配器mBluetoothManager和扫描控制器mBluetoothLeScanner;判断是否支持BLE协议;判断是否打开蓝牙,若无则请求用户打开,用户处理结果在onActivityResult方法中响应;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
if (mBluetoothAdapter == null
//系统是否支持BLE
&& !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.e(TAG, "not support bluetooth");
} else {
Log.e(TAG, " support bluetooth");
}
if (!mBluetoothAdapter.isEnabled()) {
// Request user to enable Bluetooth if not enabled
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
} else {
// Start device scanning and connect to the device
Log.i(TAG, "开始扫描:");
requestPermissions();
}
mConnectButton = findViewById(R.id.connect_button);
mConnectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startScan();
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
// Bluetooth is enabled, start scanning for BLE devices
requestPermissions();
} else {
// User denied the request to enable Bluetooth
Toast.makeText(this, "Bluetooth is required for this app to work.", Toast.LENGTH_SHORT).show();
}
}
}
(3)若具备BLE通信能力,且已打开蓝牙,则请求授权定位信息,授权结果在onRequestPermissionsResult方法中响应。这里需要注意Android版本不同使用的方法不同,要注意兼容不同版本号:1)Android4.3以下使用ScanLEStart方法,引入的权限为Corse,需要Activity文件中动态引入;2)Android4.3-12版本使用ScanStart方法,引入权限为FINE,需要Activity文件中动态引入,本文即采用此种方法;3)Android12版本使用ScanStart方法,引入权限为Scan,不用在文件中动态引入。权限引入见第(1)小节。
private void requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
Toast.makeText(this, "使用蓝牙需要授权定位信息", Toast.LENGTH_LONG).show();
}
//请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_ACCESS_FINE_LOCATION_PERMISSION);
}else{
startScan();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_ACCESS_FINE_LOCATION_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户授权
startScan();
} else {
Log.i(TAG, "用户未授权!");
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
(4)开始扫描(设置过滤条件,扫描类型,回调函数):
private void startScan() {
if (!mScanning) {
// 设置过滤条件
List<ScanFilter> bleScanFilters = new ArrayList<>();
ScanFilter filter = new ScanFilter.Builder().setDeviceAddress(MAC_ADDRESS).build();
bleScanFilters.add(filter);
// 设置扫描设置
ScanSettings scanSettings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES).setMatchMode(ScanSettings.MATCH_MODE_STICKY).build();
// 开始扫描
mBluetoothLeScanner.startScan(bleScanFilters,scanSettings, mScanCallback);
mScanning = true;
// 扫描 10 秒后停止扫描
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
stopScan();
}
}, 10000);
}
}
private void stopScan() {
if (mScanning) {
// 停止扫描
mBluetoothLeScanner.stopScan(mScanCallback);
mScanning = false;
}
Log.d(TAG, "Scan stopped.");
}
(5)一旦扫描到设备,就会调用回调函数中的onScanResult,若扫描出错,则会调用onScanFailed函数。扫描的回调函数如下:
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
// 当扫描到蓝牙设备时调用
BluetoothDevice device = result.getDevice();
String deviceName = device.getName();
String deviceAddress = device.getAddress();
Log.d(TAG, "Found device: " + deviceName + " (" + deviceAddress + ")");
if(deviceName != null && deviceAddress.equals(MAC_ADDRESS)){
mBluetoothDevice = device;
connectToDevice(device);
}
}
@Override
public void onScanFailed(int errorCode) {
// 扫描失败时调用
Log.e(TAG, "Scan failed with error code: " + errorCode);
}
};
(6)扫描到设备后自动调用onScanResult方法,在该方法中根据Mac地址或名称等获取想要连接的设备,并调用设备的connectGatt(上下文,当前Activity类,是否自动连接,回调函数mGattCallback)方法连接设备。
private void connectToDevice(BluetoothDevice device) {
// 连接到蓝牙设备
mBluetoothGatt = device.connectGatt(this, true, mGattCallback);
}
(7)mGattCallback回调中的方法响应时机如下:
1)onConnectionStateChange:一旦监听到连接状态变化则会调用;
2)onServicesDiscovered:onConnectionStateChange方法中连接成功时调用discoverServices方法。该方法中获取Gatt所有服务和特性,根据设备厂家提供的发送数据特性UUID,找到匹配的服务特性characteristic;根据厂家提供的接收数据特性UUID,获取相应descriptor;将Gatt与characteristic和descriptor绑定;
3)onCharacteristicChanged:以下设置为true时,设备返回数据时的响应;
mBluetoothGatt.setCharacteristicNotification(mCharacteristic, true);
4)onDescriptorWrite:3)中设置为true时,写入数据后的响应;
5)onCharacteristicRead:3)中设置为false时,设备返回数据时的响应;
6)onCharacteristicWrite:3)中设置为false时,写入数据后的响应;
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
// 连接成功
Log.d(TAG, "Connected to GATT server.");
// 发现设备的服务
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 连接断开
Log.d(TAG, "Disconnected from GATT server.");
connectToDevice(mBluetoothDevice);
} else if (newState == BluetoothGatt.STATE_CONNECTING) {
Log.d(TAG, "Connecting to GATT server.");
// 连接中
} else if (newState == BluetoothGatt.STATE_DISCONNECTING) {
Log.d(TAG, "Disconnecting from GATT server.");
// 断开连接中
}
// 打印 status 错误码
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "Connection error: " + status);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 服务发现成功
// 获取设备支持的所有服务和特性
List<BluetoothGattService> services = gatt.getServices();
// 根据特性的 UUID 查找需要发送数据的特性
for (BluetoothGattService service : services) {
//查看服务UUID
// Log.i(TAG, "服务UUID:"+service.getUuid());
//查看服务所有特性的UUID
// List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
// for (int i=0;i<characteristics.size();i++){
// BluetoothGattCharacteristic characteristic = characteristics.get(i);
// UUID uuid = characteristic.getUuid();
// Log.i(TAG, "服务第"+i+"个特性UUID是: "+uuid.toString());
// }
//获取与设备服务UUID匹配的服务特性,其中设备服务UUID与厂家联系
mCharacteristic = service.getCharacteristic(SEND_CHARACTERISTIC_UUID);
if (mCharacteristic != null){
mBluetoothGatt.setCharacteristicNotification(mCharacteristic, true);
// getDescriptor是一个异步操作,因此存在执行到writeDescriptor时descriptpr为空的情况,所以设置一个延时操作,否则无法接收设备回复的数据
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//必须设置描述才能接收设备发来的数据,其中描述UUID与厂家联系
BluetoothGattDescriptor descriptor = mCharacteristic.getDescriptor( RECEIVE_CHARACTERISTIC_UUID);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}, 10000);
// 将十六进制数组转换为字节数组
String hex = ERS_MSG;
byte[] byteArray = new byte[hex.length()/2];
for( int i=0; i<byteArray.length; i++ ) {
//grab the hex in pairs
String output = hex.substring(2*i, (2*i + 2));
//convert hex to decimal
int decimal = Integer.parseInt(output, 16);
byteArray[i] = (byte) decimal;
}
// 发送数据
sendDataToDevice(byteArray);
stopScan();
break;
}
}
} else {
// 服务发现失败
Log.e(TAG, "onServicesDiscovered: Failed");
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// 特性发生改变时调用,设备主动发送了数据,在此处处理接收到的数据
byte[] receivedData = characteristic.getValue();
Log.d(TAG, "Received data: " + Arrays.toString(receivedData));
if(mBluetoothGatt!=null){
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
// 读取特性成功时调用,你主动向设备发出了读取请求,并设备回复了数据
if (status == BluetoothGatt.GATT_SUCCESS) {
// 在此处处理接收到的数据
byte[] receivedData = characteristic.getValue();
Log.d(TAG, "Received data: " + Arrays.toString(receivedData));
} else {
// 读取特性失败
Log.e(TAG, "Failed to read characteristic.");
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// 写入成功,可以读取回复消息
Log.i(TAG, "写入成功");
gatt.readCharacteristic(characteristic);
}else{
Log.i(TAG, "写入失败: "+status);
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Descriptor写入成功,你可以在这里读取回应的数据
// 读取数据的逻辑,可以使用gatt.readCharacteristic(characteristic)来读取特征值
if (status == BluetoothGatt.GATT_SUCCESS) {
// 写入成功,可以读取回复消息
Log.i(TAG, "descriptor写入成功");
BluetoothGattService service = gatt.getService(SERVICE_CHARACTERISTIC_UUID);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(SEND_CHARACTERISTIC_UUID);
gatt.readCharacteristic(characteristic);
}else{
Log.i(TAG, "descriptor写入失败: "+status);
}
}
}
};
(8)发送数据:
private void sendDataToDevice(byte[] data) {
if (mCharacteristic != null && mBluetoothGatt != null) {
// 这里是将要发送的数据
byte[] dataToSend = data;
mCharacteristic.setValue(dataToSend);
mCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
Log.i(TAG, "sendDataToDevice: "+mCharacteristic.getValue());
mBluetoothGatt.writeCharacteristic(mCharacteristic);
}
}
(9)连接真机,进行测试,在日志打印窗口能够看到返回的数据。