备注:后面几节讲解的都是以BLE为例子
这就是开始使用蓝牙开发所需了解的基础知识。
我们继续下一节。
第二节:启用蓝牙和检查设备支持
在这一节中,我们将学习如何在你的应用中启用蓝牙功能,并检查用户的设备是否支持蓝牙。
1. 获取蓝牙适配器
首先,你需要获取BluetoothAdapter的实例,它是所有蓝牙操作的入口点。
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
如果getDefaultAdapter()返回null,则表示设备不支持蓝牙。
2. 检查蓝牙是否启用
接下来,检查蓝牙是否已经启用。使用BluetoothAdapter的isEnabled**()**方法可以检查蓝牙是否启用:
if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled()) {
// 蓝牙未启用
}
bluetoothAdapter != null 说明设备支持蓝牙
!bluetoothAdapter.isEnabled() 说明蓝牙没启用
3. 请求用户启用蓝牙
如果蓝牙未启用,你可以请求用户启用它。
这可以通过启动一个Intent来请求用户启用蓝牙,而不是直接调用enable()方法,因为**enable()**方法无需用户同意即可启用蓝牙,这可能不是最佳用户体验。
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
在这里,REQUEST_ENABLE_BT是应用定义的整数请求码,用于在你的Activity的onActivityResult回调中接收结果。
4. onActivityResult回调
当用户响应启用蓝牙的请求时,系统会调用你的Activity的onActivityResult方法。
你可以在这里检查请求的结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
// 用户启用了蓝牙
} else {
// 用户未启用蓝牙
}
}
}
这就是如何在你的应用中启用蓝牙并检查设备是否支持蓝牙的基本步骤,理解这些步骤是进行蓝牙开发的基础。
第三节:发现设备和获取已配对设备
在这一节中,我们将学习如何发现附近的蓝牙设备以及如何获取已经与你的设备配对的蓝牙设备列表。
1. 获取已配对设备
你的Android设备可能已经与一些蓝牙设备配对过了。
要获取这些已配对的设备列表,你可以使用BluetoothAdapter的**getBondedDevices()**方法:
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
// 至少有一个已配对设备
for (BluetoothDevice device : pairedDevices) {
String deviceName = device.getName();
String deviceHardwareAddress = device.getAddress(); // MAC地址
Log.d("TAG", "蓝牙设备名称:"+deviceName);
Log.d("TAG", "蓝牙设备地址:"+deviceHardwareAddress);
}
}
2. 发现新设备
要发现附近的蓝牙设备,你需要调用BluetoothAdapter的startDiscovery()方法。这个方法是异步的,发现过程通常会持续12秒。
你需要注册一个BroadcastReceiver来监听BluetoothDevice.ACTION_FOUND广播,这个广播会在发现新设备时发送。
// 注册广播接收器以监听发现的设备
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
// 发现设备
if (bluetoothAdapter.startDiscovery()) {
// 发现过程成功启动
}
疑问:
BluetoothDevice.ACTION_FOUND 是一个什么值?
解答:
- BluetoothDevice.ACTION_FOUND 是一个字符串常量,用于在广播中标识已找到一个蓝牙设备,值是"android.bluetooth.device.action.FOUND"。
- 当你的应用调用BluetoothAdapter的startDiscovery()方法开始扫描附近的蓝牙设备时,每发现一个设备,系统就会发送这个ACTION_FOUND的广播。
- 你的应用可以通过注册一个BroadcastReceiver来监听这个广播,以便获取每个发现的蓝牙设备的信息。
在你的BroadcastReceiver中,你可以获取发现的设备信息:
private final BroadcastReceiver receiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 从Intent中获取发现的BluetoothDevice
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String deviceName = device.getName();
String deviceHardwareAddress = device.getAddress(); // MAC地址
}
}
};
疑问:
action有几种?
解答:
- BluetoothDevice.ACTION_ACL_CONNECTED:当与远程设备建立低级别(ACL)连接时发送的广播。
- BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED:当系统要断开与远程设备的低级别(ACL)连接时发送的广播。
- BluetoothDevice.ACTION_ACL_DISCONNECTED:当与远程设备的低级别(ACL)连接断开时发送的广播。
- BluetoothDevice.ACTION_BOND_STATE_CHANGED:当远程设备的配对状态发生变化时发送的广播。
- BluetoothDevice.ACTION_NAME_CHANGED:当远程设备的名称发生变化时发送的广播。
- BluetoothDevice.ACTION_PAIRING_REQUEST:在需要配对时发送的广播,通常是因为配对过程需要输入PIN码或确认配对。
- BluetoothAdapter.ACTION_DISCOVERY_STARTED:当设备发现开始时发送的广播。
- BluetoothAdapter.ACTION_DISCOVERY_FINISHED:当设备发现结束时发送的广播。
- BluetoothAdapter.ACTION_STATE_CHANGED:当蓝牙适配器的状态发生变化时发送的广播,例如蓝牙被开启或关闭。
监听这些action可以让你的应用更好地与蓝牙设备交互。
例如,
通过监听ACTION_BOND_STATE_CHANGED,你的应用可以知道何时一个设备已经成功配对,或者配对失败。
通过监听ACTION_ACL_CONNECTED和ACTION_ACL_DISCONNECTED,你的应用可以知道何时设备连接或断开连接。
可以在这些节点做一些处理。
3. 停止发现
由于发现过程会消耗大量资源,一旦你找到了你感兴趣的设备,或者你想停止扫描,你应该使用**cancelDiscovery()**方法来停止发现过程:
bluetoothAdapter.cancelDiscovery();
4. 清理
记得在不再需要时,**unregisterReceiver(receiver)**注销你的BroadcastReceiver,例如,在你的Activity或Fragment的onDestroy()方法中:
@Override
protected void onDestroy() {
super.onDestroy();
// 确保我们不再监听广播
unregisterReceiver(receiver);
}
这就是如何发现新设备以及获取已配对设备的基本步骤。
理解这些步骤对于开发能够与其他蓝牙设备交互的应用是非常重要的。
当你准备好了,请告诉我,以便我们继续下一节。
第四节:连接到蓝牙设备
在这一节中,我们将学习如何使用BluetoothSocket来连接到一个蓝牙设备。连接到设备是进行数据交换的前提。
1. 创建BluetoothSocket
要与远程蓝牙设备建立连接,首先需要创建一个BluetoothSocket。
BluetoothSocket代表了蓝牙套接字的接口,它是两个设备之间通信的通道。
对于大多数用途,你将使用**createRfcommSocketToServiceRecord(UUID)**方法来创建BluetoothSocket。
这个方法需要一个UUID参数,这个UUID必须是你想要连接的远程设备上的蓝牙服务的UUID。UUID代表通用唯一标识符,用于唯一标识你的应用的蓝牙服务。
BluetoothDevice device = ... // 获取一个BluetoothDevice对象
UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); // 示例UUID
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(MY_UUID);
有几个概念:
- 服务UUID
- 特征UUID
- 描述UUID
- 公共标准UUID
2. 连接到远程设备
创建BluetoothSocket后,你可以调用它的**connect()**方法来尝试与远程设备建立连接。
这个方法是**阻塞调用****(**直到连接成功或抛出异常)。
try {
socket.connect();
// 连接成功,可以开始进行数据交换
} catch (IOException e) {
// 连接失败
e.printStackTrace();
}
3. 数据交换
连接成功后,你可以通过BluetoothSocket的输入输出流来进行数据的读写。
try {
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 从InputStream读取数据
byte[] buffer = new byte[1024];
int bytes;
bytes = inputStream.read(buffer);
String receivedData = new String(buffer, 0, bytes);
// 向OutputStream写入数据
String dataToSend = "Hello, Bluetooth!";
outputStream.write(dataToSend.getBytes());
} catch (IOException e) {
// 处理异常
}
4. 断开连接
完成数据交换后,应该关闭BluetoothSocket来释放资源。
try {
socket.close();
} catch (IOException e) {
// 处理异常
}
注意事项
- 连接过程可能会花费一些时间,建议在一个单独的线程中进行连接操作,以避免阻塞UI线程。
- 确保你的UUID与远程设备提供的服务UUID匹配。
- 在连接过程中和之后,要妥善处理异常。
疑问:
我听同事说发送数据还有片段发送,是怎么样的
解答:
在蓝牙通信中,由于蓝牙协议本身的限制以及不同设备的性能差异,发送大量数据时直接发送整个数据包可能会导致数据传输失败或者效率低下。
因此,将大数据分割成小片段(fragments)进行发送是一种常见的做法,这就是所谓的“片段发送”或“数据分片”。
1.数据分片的基本思想
数据分片的基本思想,是将大的数据包分割成多个较小的数据片段,然后逐个片段地发送这些数据。接收方收到所有片段后,再将它们重新组合成原始的数据包。
2.如何实现数据分片?
实现数据分片的一个简单方法是:
- (1)确定片段大小:
- 首先,你需要确定每个数据片段的大小。
- 这个大小需要根据你的应用需求和蓝牙硬件的能力来决定。一个常见的做法是使用512字节作为每个片段的大小。
- (2)分割数据:
- 将你的数据分割成多个片段。
- 如果数据大小不是片段大小的整数倍,最后一个片段可能会更小。
- (3)发送数据片段:
- 逐个发送每个数据片段。
- 在每个片段发送之后,可能需要等待接收方的确认,再发送下一个片段。
- (4)接收方重组数据:
- 接收方需要按照发送顺序接收并存储每个数据片段。
- 一旦所有片段都被接收,接收方就可以将它们组合成原始的数据包。
示例代码
以下是一个简化的示例,展示了如何将一个大的字节数组分割成多个片段并发送:
OutputStream outputStream = bluetoothSocket.getOutputStream();
byte[] largeData = ...; // 假设这是要发送的大数据
int chunkSize = 512; // 片段大小
int offset = 0;
while (offset < largeData.length) {
// 计算剩余数据大小
int remaining = largeData.length - offset;
int byteCount = Math.min(remaining, chunkSize); // 这次发送的字节数
// 发送数据片段
outputStream.write(largeData, offset, byteCount);
outputStream.flush(); // 确保数据被发送
offset += byteCount; // 更新偏移量
// 可选:等待接收方的确认
}
// 数据发送完毕
注意事项
- 数据分片会增加通信的复杂度,特别是在需要确保数据完整性和顺序的情况下。
- 接收方需要有相应的逻辑来处理接收到的数据片段,包括片段的重组和可能的错误处理。
- 在实际应用中,可能还需要考虑如何处理数据传输中的错误,例如重传丢失的片段。
通过数据分片,你可以更可靠地通过蓝牙发送大量数据,同时还可以提高数据传输的效率。
示例代码
获取蓝牙设备电量
public void getBluetoothDeviceBattery() {
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
//获取BluetoothAdapter的Class对象
Class<BluetoothAdapter> bluetoothAdapterClass = BluetoothAdapter.class;
try {
//反射获取蓝牙连接状态的方法
Method method = bluetoothAdapterClass.getDeclaredMethod("getConnectionState", (Class[]) null);
//打开使用这个方法的权限
method.setAccessible(true);
int state = (int) method.invoke(btAdapter, (Object[]) null);
if (state == BluetoothAdapter.STATE_CONNECTED) {
//获取在系统蓝牙的配对列表中的设备--!已连接设备包含在其中
if (ActivityCompat.checkSelfPermission(this,
android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "没有权限");
return;
}
Set<BluetoothDevice> devices = btAdapter.getBondedDevices();
for (BluetoothDevice device : devices) {
Method batteryMethod = BluetoothDevice.class.getDeclaredMethod("getBatteryLevel", (Class[]) null);
batteryMethod.setAccessible(true);
Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected", (Class[]) null);
isConnectedMethod.setAccessible(true);
boolean isConnected = (boolean) isConnectedMethod.invoke(device, (Object[]) null);
int level = (int) batteryMethod.invoke(device, (Object[]) null);
if (device != null && level > 0 && isConnected) {
String deviceName = device .getName();
// LogUtils.d(deviceName + " 电量: " + level);
Log.e(TAG, deviceName + " 电量: " + level);
}
}
} else {
// ToastUtils.showLong("No Connected Bluetooth Devices Found");
Log.e(TAG, "No Connected Bluetooth Devices Found");
}
} catch (Exception e) {
Log.e(TAG, "Exception"+e.toString());
e.printStackTrace();
}
}
总之,这就是如何连接到蓝牙设备并进行数据交换的基本步骤。理解这些步骤对于开发能够与其他蓝牙设备交互的应用是非常重要的。
当你准备好了,请告诉我,以便我们继续下一节。
第五节:管理蓝牙连接
在这一节中,我们将讨论如何管理蓝牙连接,包括:数据的接收处理、连接的维护和断开连接的正确方式。
1. 接收数据
在前一节中,我们讨论了如何发送数据。接收数据同样重要,通常涉及到从BluetoothSocket的输入流中读取数据。
由于数据接收是阻塞操作,推荐在单独的线程中进行读取操作,以避免阻塞UI线程。
class ConnectedThread extends Thread {
private final BluetoothSocket socket;
private final InputStream inputStream;
private final OutputStream outputStream;
public ConnectedThread(BluetoothSocket socket) {
this.socket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
inputStream = tmpIn;
outputStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // 缓冲区
int bytes; // 读取的字节数
while (true) {
try {
// 从InputStream读取数据
bytes = inputStream.read(buffer);
String receivedData = new String(buffer, 0, bytes);
// 处理接收到的数据
} catch (IOException e) {
// 连接丢失时的处理
break;
}
}
}
// 调用此方法从外部发送数据
public void write(byte[] bytes) {
try {
outputStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
// 调用此方法关闭连接
public void cancel() {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 维护连接
维护蓝牙连接的关键,在于处理各种可能导致连接中断的情况。
例如,设备的距离过远、干扰或设备关闭。
在ConnectedThread的run方法中,如果**inputStream.read(buffer)**抛出IOException,通常意味着连接已经丢失,此时应当关闭socket并尝试重新连接或更新UI。
3. 断开连接
当不再需要蓝牙连接时,正确的断开连接是很重要的。这包括关闭BluetoothSocket,以及与之关联的输入输出流。
在ConnectedThread类中,我们提供了一个cancel方法来做这件事。调用cancel可以安全地关闭socket并终止线程。
public void cancel() {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
4. 重连策略
在实际应用中,可能需要实现自动重连的逻辑。这通常涉及到在连接丢失后等待一段时间,然后尝试重新连接到设备。
重连策略的实现会根据具体的应用需求而有所不同,但基本思路是在捕获到连接丢失的异常后,延迟一段时间后再次尝试连接。
这就是关于管理蓝牙连接的基本知识。通过正确地管理连接,你的应用可以更加稳定,为用户提供更好的体验。
当你准备好了,请告诉我,以便我们继续下一节。
第六节:蓝牙服务和特征
在这一节中,我们将探讨使用蓝牙低功耗(BLE)时的高级概念,特别是蓝牙服务和特征的概念。这些概念对于理解和开发BLE应用至关重要。
1. BLE简介
蓝牙低功耗(BLE),也称为蓝牙4.0 或 蓝牙Smart,是蓝牙技术的一个版本,专为低功耗设备设计,适用于需要长期运行在电池供电下的设备。与经典蓝牙相比,BLE在保持通信能力的同时,大大降低了能耗。
2. 服务和特征
在BLE中,数据交换是通过服务(Services)和特征(Characteristics)的概念来组织的。
- 服务(Service):
服务是一组功能相关的特征的集合。每个服务都由一个唯一的UUID来标识。
- 特征(Characteristic):
特征表示服务中的一个数据点,例如一个传感器的值或者一个配置参数。特征包含一个值以及0到多个描述符(Descriptors),用于描述特征的属性。特征也由UUID标识。
3. 发现服务和特征