Android 平台包含蓝牙网络堆栈支持,此支持能让设备以无线方式与其他蓝牙设备交换数据。应用框架提供通过 Android Bluetooth API 访问蓝牙功能的权限。这些 API 允许应用以无线方式连接到其他蓝牙设备,从而实现点到点和多点无线功能。
使用蓝牙进行通信的四大必需任务:设置蓝牙、查找局部区域内的配对设备或可用设备、连接设备,以及在设备之间传输数据。
为了让支持蓝牙的设备能够在彼此之间传输数据,它们必须先通过配对过程形成通信通道。其中一台设备(可检测到的设备)需将自身设置为可接收传入的连接请求。另一台设备会使用服务发现过程找到此可检测到的设备。在可检测到的设备接受配对请求后,这两台设备会完成绑定过程,并在此期间交换安全密钥。二者会缓存这些密钥,以供日后使用。完成配对和绑定过程后,两台设备会交换信息。当会话完成时,发起配对请求的设备会发布已将其链接到可检测设备的通道。但是,这两台设备仍保持绑定状态,因此在未来的会话期间,只要二者在彼此的范围内且均未移除绑定,便可自动重新连接。
Android 应用可通过 Bluetooth API 执行以下操作:
1、扫描其他蓝牙设备
2、查询本地蓝牙适配器的配对蓝牙设备
3、建立 RFCOMM 通道
4、通过服务发现连接到其他设备
5、与其他设备进行双向数据传输
6、管理多个连接
关键类和接口:
android.bluetooth 包中提供所有 Bluetooth API。以下概要列出了创建蓝牙连接所需的类和接口:
BluetoothAdapter
表示本地蓝牙适配器(蓝牙无线装置)。BluetoothAdapter 是所有蓝牙交互的入口点。借助该类,您可以发现其他蓝牙设备、查询已(已配对)设备的列表、使用已知的 MAC 地址实例化 BluetoothDevice,以及通过创建 BluetoothServerSocket 侦听来自其他设备的通信。
BluetoothDevice
表示远程蓝牙设备。借助该类,您可以通过 BluetoothSocket 请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。
BluetoothSocket
表示蓝牙套接字接口(类似于 TCP Socket)。这是允许应用使用 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。
BluetoothServerSocket
表示用于侦听传入请求的开放服务器套接字(类似于 TCP ServerSocket)。如要连接两台 Android 设备,其中一台设备必须使用此类开放一个服务器套接字。当远程蓝牙设备向此设备发出连接请求时,该设备接受连接,然后返回已连接的 BluetoothSocket。
BluetoothClass
描述蓝牙设备的一般特征和功能。这是一组只读属性,用于定义设备的类和服务。虽然这些信息会提供关于设备类型的有用提示,但该类的属性未必描述设备支持的所有蓝牙配置文件和服务。
BluetoothProfile
表示蓝牙配置文件的接口。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。举个例子:免提配置文件。如需了解有关配置文件的详细讨论,请参阅使用配置文件。
BluetoothHeadset
提供蓝牙耳机支持,以便与手机配合使用。这包括蓝牙耳机配置文件和免提 (v1.5) 配置文件。
BluetoothA2dp
定义如何使用蓝牙立体声音频传输配置文件 (A2DP),通过蓝牙连接将高质量音频从一个设备流式传输至另一个设备。
BluetoothHealth
表示用于控制蓝牙服务的健康设备配置文件代理。
BluetoothHealthCallback
用于实现 BluetoothHealth 回调的抽象类。您必须扩展此类并实现回调方法,以接收关于应用注册状态和蓝牙通道状态变化的更新内容。
BluetoothHealthAppConfiguration
表示第三方蓝牙健康应用注册的应用配置,该配置旨在实现与远程蓝牙健康设备的通信。
BluetoothProfile.ServiceListener
当 BluetoothProfile 进程间通信 (IPC) 客户端连接到运行特定配置文件的内部服务或断开该服务连接时,向该客户端发送通知的接口。
基本操作代码:
1、设置蓝牙:
获取 BluetoothAdapter:
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
// Device doesn’t support Bluetooth
}
启用蓝牙:
if (!bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
2、查询已配对设备:
Set pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
// There are paired devices. Get the name and address of each paired device.
for (BluetoothDevice device : pairedDevices) {
String deviceName = device.getName();
String deviceHardwareAddress = device.getAddress(); // MAC address
}
}
3、发现设备:
@Override
protected void onCreate(Bundle savedInstanceState) {
…
// Register for broadcasts when a device is discovered.
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
}
// Create a BroadcastReceiver for ACTION_FOUND.
private final BroadcastReceiver receiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Discovery has found a device. Get the BluetoothDevice
// object and its info from the Intent.
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String deviceName = device.getName();
String deviceHardwareAddress = device.getAddress(); // MAC address
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
…
// Don't forget to unregister the ACTION_FOUND receiver.
unregisterReceiver(receiver);
}
4、连接设备:
作为服务器连接:
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket
// because mmServerSocket is final.
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code.
tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) {
Log.e(TAG, "Socket's listen() method failed", e);
}
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned.
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "Socket's accept() method failed", e);
break;
}
if (socket != null) {
// A connection was accepted. Perform work associated with
// the connection in a separate thread.
manageMyConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
// Closes the connect socket and causes the thread to finish.
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close the connect socket", e);
}
}
}
作为客户端连接:
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket
// because mmSocket is final.
BluetoothSocket tmp = null;
mmDevice = device;
try {
// Get a BluetoothSocket to connect with the given BluetoothDevice.
// MY_UUID is the app's UUID string, also used in the server code.
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, "Socket's create() method failed", e);
}
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it otherwise slows down the connection.
bluetoothAdapter.cancelDiscovery();
try {
// Connect to the remote device through the socket. This call blocks
// until it succeeds or throws an exception.
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and return.
try {
mmSocket.close();
} catch (IOException closeException) {
Log.e(TAG, "Could not close the client socket", closeException);
}
return;
}
// The connection attempt succeeded. Perform work associated with
// the connection in a separate thread.
manageMyConnectedSocket(mmSocket);
}
// Closes the client socket and causes the thread to finish.
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close the client socket", e);
}
}
}
5、管理连接:
public class MyBluetoothService {
private static final String TAG = “MY_APP_DEBUG_TAG”;
private Handler handler; // handler that gets info from Bluetooth service
// Defines several constants used when transmitting messages between the
// service and the UI.
private interface MessageConstants {
public static final int MESSAGE_READ = 0;
public static final int MESSAGE_WRITE = 1;
public static final int MESSAGE_TOAST = 2;
// ... (Add other message types here as needed.)
}
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private byte[] mmBuffer; // mmBuffer store for the stream
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams; using temp objects because
// member streams are final.
try {
tmpIn = socket.getInputStream();
} catch (IOException e) {
Log.e(TAG, "Error occurred when creating input stream", e);
}
try {
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "Error occurred when creating output stream", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
mmBuffer = new byte[1024];
int numBytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs.
while (true) {
try {
// Read from the InputStream.
numBytes = mmInStream.read(mmBuffer);
// Send the obtained bytes to the UI activity.
Message readMsg = handler.obtainMessage(
MessageConstants.MESSAGE_READ, numBytes, -1,
mmBuffer);
readMsg.sendToTarget();
} catch (IOException e) {
Log.d(TAG, "Input stream was disconnected", e);
break;
}
}
}
// Call this from the main activity to send data to the remote device.
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
// Share the sent message with the UI activity.
Message writtenMsg = handler.obtainMessage(
MessageConstants.MESSAGE_WRITE, -1, -1, mmBuffer);
writtenMsg.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Error occurred when sending data", e);
// Send a failure message back to the activity.
Message writeErrorMsg =
handler.obtainMessage(MessageConstants.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString("toast",
"Couldn't send data to the other device");
writeErrorMsg.setData(bundle);
handler.sendMessage(writeErrorMsg);
}
}
// Call this method from the main activity to shut down the connection.
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close the connect socket", e);
}
}
}
}
通信原理:
蓝牙通信和socket通信原理基本上是一致的,socket通信原理如下:
蓝牙客户端Socket流程如下:
1、创建客户端蓝牙Sokcet
2、创建连接
3、读写数据
4、关闭
服务端socket:
1、创建服务端蓝牙Socket
2、绑定端口号(蓝牙忽略)
3、创建监听listen(蓝牙忽略, 蓝牙没有此监听,而是通过whlie(true)死循环来一直监听的)
4、通过accept(),如果有客户端连接,会创建一个新的Socket,体现出并发性,可以同时与多个socket通讯)
5、读写数据
6、关闭
权限设置:
<manifest … >
客户端:
public class ConnectThread extends Thread{
private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
/** 客户端socket*/
private final BluetoothSocket mmSoket;
/** 要连接的设备*/
private final BluetoothDevice mmDevice;
private BluetoothAdapter mBluetoothAdapter;
/** 主线程通信的Handler*/
private final Handler mHandler;
/** 发送和接收数据的处理类*/
private ConnectedThread mConnectedThread;
public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, Handler mUIhandler) {
mmDevice = device;
mBluetoothAdapter = bluetoothAdapter;
mHandler = mUIhandler;
BluetoothSocket tmp = null;
try {
// 创建客户端Socket
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
e.printStackTrace();
}
mmSoket = tmp;
}
@Override
public void run() {
super.run();
// 关闭正在发现设备.(如果此时又在查找设备,又在发送数据,会有冲突,影响传输效率)
mBluetoothAdapter.cancelDiscovery();
try {
// 连接服务器
mmSoket.connect();
} catch (IOException e) {
// 连接异常就关闭
try {
mmSoket.close();
} catch (IOException e1) {
}
return;
}
manageConnectedSocket(mmSoket);
}
private void manageConnectedSocket(BluetoothSocket mmSoket) {
// 通知主线程连接上了服务端socket,更新UI
mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
// 新建一个线程进行通讯,不然会发现线程堵塞
mConnectedThread = new ConnectedThread(mmSoket,mHandler);
mConnectedThread.start();
}
/**
* 关闭当前客户端
*/
public void cancle() {
try {
mmSoket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/** * 发送数据 * @paramdata */
public void sendData(byte[] data) {
if(mConnectedThread != null) {
mConnectedThread.write(data);
}
}
}
服务器端:
public class AccepThread extendsThread{
/** 连接的名称*/
private static final String NAME = "BluetoothClass";
/** UUID*/
private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
/** 服务端蓝牙Sokcet*/
private final BluetoothServerSocket mmServerSocket;
private final BluetoothAdapter mBluetoothAdapter;
/** 线程中通信的更新UI的Handler*/
private final Handler mHandler;
/** 监听到有客户端连接,新建一个线程单独处理,不然在此线程中会堵塞*/
private ConnectedThread mConnectedThread;
public AccepThread(BluetoothAdapter adapter, Handler handler) throws IOException {
mBluetoothAdapter = adapter;
this.mHandler = handler;
// 获取服务端蓝牙socket
mmServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
}
@Override
public void run() {
super.run();
// 连接的客户端soacket
BluetoothSocket socket = null;
// 服务端是不退出的,要一直监听连接进来的客户端,所以是死循环
while (true){
// 通知主线程更新UI,客户端开始监听
mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
try {
// 获取连接的客户端socket
socket = mmServerSocket.accept();
} catch (IOException e) {
// 通知主线程更新UI, 获取异常
mHandler.sendEmptyMessage(Constant.MSG_ERROR);
e.printStackTrace();
// 服务端退出一直监听线程
break;
}
if(socket != null) {
// 管理连接的客户端socket
manageConnectSocket(socket);
// 这里应该是手动断开,案例应该是只保证连接一个客户端,所以连接完以后,关闭了服务端socket
try {
mmServerSocket.close();
mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/** * 管理连接的客户端socket
-
@paramsocket */
private void manageConnectSocket(BluetoothSocket socket) {
// 只支持同时处理一个连接
// mConnectedThread不为空,踢掉之前的客户端
if(mConnectedThread != null) {
mConnectedThread.cancle();
}
// 主线程更新UI,连接到了一个客户端
mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
// 新建一个线程,处理客户端发来的数据
mConnectedThread = new ConnectedThread(socket, mHandler);
mConnectedThread.start();
}/*断开服务端,结束监听 /
public void cancle() {
try {
mmServerSocket.close();
mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
} catch (IOException e) {
e.printStackTrace();
}
}
/** * 发送数据- @paramdata */
public void sendData(byte[] data){
if(mConnectedThread != null) {
mConnectedThread.write(data);
}
}
}
- @paramdata */
共同通讯处理类ConnectedThread:
public class ConnectedThread extends Thread{
/** 当前连接的客户端BluetoothSocket*/
private final BluetoothSocket mmSokcet;
/** 读取数据流*/
private final InputStream mmInputStream;
/** 发送数据流*/
private final OutputStream mmOutputStream;
/** 与主线程通信Handler*/
private Handler mHandler;
private String TAG ="ConnectedThread";
public ConnectedThread(BluetoothSocket socket,Handler handler) {
mmSokcet = socket; mHandler = handler;
InputStream tmpIn =null;
OutputStream tmpOut =null;
try{
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
}catch(IOException e) {
e.printStackTrace();
}
mmInputStream = tmpIn;
mmOutputStream = tmpOut;
}
@Override
public void run() {
super.run();
byte[] buffer =new byte[1024];
while(true) {
try{
// 读取数据
int bytes = mmInputStream.read(buffer);
if(bytes >0) {
String data =new String(buffer,0,bytes,"utf-8");
// 把数据发送到主线程, 此处还可以用广播
Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA,data);
mHandler.sendMessage(message);
}
Log.d(TAG,"messge size :"+ bytes);
}catch(IOException e){
e.printStackTrace();
}
}
}
// 踢掉当前客户端
public void cancle() {
try{
mmSokcet.close();
}catch(IOException e) {
e.printStackTrace();
}
}
/** * 服务端发送数据 *
@paramdata */
public void write(byte[] data) {
try{
mmOutputStream.write(data);
}catch(IOException e) {
e.printStackTrace();
}
}
}
链接:
https://developer.android.google.cn/guide/topics/connectivity/bluetooth
http://www.android-doc.com/guide/topics/connectivity/bluetooth.html
https://www.jianshu.com/p/8fbbc6723a7c
https://blog.csdn.net/weixin_39079048/article/details/78923669