一.概述和配置
1.概述
Android 平台包含蓝牙网络堆栈支持,此支持能让设备以无线方式与其他蓝牙设备交换数据。应用框架提供通过 Android Bluetooth API 访问蓝牙功能的权限。这些 API 允许应用以无线方式连接到其他蓝牙设备,从而实现点到点和多点无线功能。
Android 应用可通过 Bluetooth API 执行以下操作:
- 扫描其他蓝牙设备
- 查询本地蓝牙适配器的配对蓝牙设备
- 建立 RFCOMM 通道
- 通过服务发现连接到其他设备
- 与其他设备进行双向数据传输
- 管理多个连接
为了让支持蓝牙的设备能够在彼此之间传输数据,它们必须先通过配对过程形成通信通道。其中一台设备(可检测到的设备)需将自身设置为可接收传入的连接请求。另一台设备会使用服务发现过程找到此可检测到的设备。在可检测到的设备接受配对请求后,这两台设备会完成绑定过程,并在此期间交换安全密钥。二者会缓存这些密钥,以供日后使用。完成配对和绑定过程后,两台设备会交换信息。当会话完成时,发起配对请求的设备会发布已将其链接到可检测设备的通道。但是,这两台设备仍保持绑定状态,因此在未来的会话期间,只要二者在彼此的范围内且均未移除绑定,便可自动重新连接。
2.配置
如要在应用中使用蓝牙功能,您必须声明两个权限。第一个是 BLUETOOTH
。您需要此权限才能执行任何蓝牙通信,例如请求连接、接受连接和传输数据等。
第二个必须声明的权限是 ACCESS_FINE_LOCATION
。您的应用需要此权限,因为蓝牙扫描可用于收集用户的位置信息。此类信息可能来自用户自己的设备,以及在商店和交通设施等位置使用的蓝牙信标。
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- If your app targets Android 9 or lower, you can declare
ACCESS_COARSE_LOCATION instead. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>
二.Java代码
首先介绍一下蓝牙的两个广播Receiver。
第一个:蓝牙状态的改变是通过广播接收到的。
// 注册蓝牙状态接收广播
IntentFilter intentFilter = new ntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mReceiverBluetoothStatus,intentFilter);
/**
* 定义接收蓝牙状态广播receiver
*
* @param savedInstanceState
*/
private BroadcastReceiver mReceiverBluetoothStatus = new BroadcastReceiver() {
@Override
public void onReceive(Context context,Intent intent) {
int status = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,-1);
switch (status) {
case BluetoothAdapter.STATE_OFF:
Log.d(TAG,"蓝牙已关闭");
break;
case BluetoothAdapter.STATE_ON:
Log.d(TAG,"蓝牙已打开");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.d(TAG,"蓝牙关闭中...");
break;
case BluetoothAdapter.STATE_TURNING_ON:
Log.d(TAG,"蓝牙打开中...");
break;
default:
break;
}
}
};
第二个:蓝牙搜索到设备、绑定设备(配对)也是通过广播接收的。(搜索到设备系统会自动发一个广播)
// 注册蓝牙device接收广播
IntentFilter intentFilterDevice = new IntentFilter();
// 开始查找
intentFilterDevice.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
// 结束查找
intentFilterDevice.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
// 查找设备(查找到设备)
intentFilterDevice.addAction(BluetoothDevice.ACTION_FOUND);
// 设备扫描模式改变 (自己状态的改变action,当设置可见或者不见时都会发送此广播)
intentFilterDevice.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
// 绑定状态
intentFilterDevice.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
registerReceiver(mReceiverDeceiver,intentFilterDevice);
客户端代码:
/**
* <p>Title: ConnectThread</p >
* <p>Description: 客户端逻辑: 客户端的线程,处理客户端socket</p >
* <p>Company: ihaveu</p >
*
* @author MaWei
* @date 2017/12/26
*/
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();
}
}
/**
* 发送数据
* @param data
*/
public void sendData(byte[] data) {
if(mConnectedThread != null) {
mConnectedThread.write(data);
}
}
}
服务器端代码:
/**
* <p>Title: AccepThread</p >
* <p>Description: 服务端Socket通过accept()一直监听客户端连接的线程</p >
* <p>Company: ihaveu</p >
*
* @author MaWei
* @date 2017/12/26
*/
public class AccepThread extends Thread {
/** 连接的名称*/
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
* @param socket
*/
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();
}
}
/**
* 发送数据
* @param data
*/
public void sendData(byte[] data){
if(mConnectedThread != null) {
mConnectedThread.write(data);
}
}
}
综合代码:
/**
* <p>Title: ChatController</p >
* <p>Description: 聊天控制器</p >
* <p>Company: ihaveu</p >
*
* @author MaWei
* @date 2017/12/26
*/
public class ChatController {
/** 客户端的线程*/
private ConnectThread mConnectThread;
/** 服务端的线程*/
private AccepThread mAccepThread;
private ChatProtocol mProtocol = new ChatProtocol();
/**
* 网络协议的处理函数
*/
private class ChatProtocol implements ProtocoHandler<String>{
private static final String CHARSET_NAME = "utf-8";
/**
* 封包(发送数据)
* 把发送的数据变成 数组 2进制流
*/
@Override
public byte[] encodePackge(String data) {
if(data == null) {
return new byte[0];
}else {
try {
return data.getBytes(CHARSET_NAME);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return new byte[0];
}
}
}
/**
* 解包(接收处理数据)
* 把网络上数据变成自己想要的数据体
*/
@Override
public String decodePackage(byte[] netData) {
if(netData == null) {
return "";
}else {
try {
return new String(netData, CHARSET_NAME);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "";
}
}
}
}
/**
* 与服务器连接进行聊天
*/
public void startChatWith(BluetoothDevice device,BluetoothAdapter adapter,Handler handler){
mConnectThread = new ConnectThread(device, adapter, handler);
mConnectThread.start();
}
/**
* 等待客户端来连接
* handler : 用来跟主线程通信,更新UI用的
*/
public void waitingForFriends(BluetoothAdapter adapter, Handler handler) {
try {
mAccepThread = new AccepThread(adapter,handler);
mAccepThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发出消息
*/
public void sendMessage(String msg){
// 封包
byte[] data = mProtocol.encodePackge(msg);
if(mConnectThread != null) {
mConnectThread.sendData(data);
}else if(mAccepThread != null) {
mAccepThread.sendData(data);
}
}
/**
* 网络数据解码
*/
public String decodeMessage(byte[] data){
return mProtocol.decodePackage(data);
}
/**
* 停止聊天
*/
public void stopChart(){
if(mConnectThread != null) {
mConnectThread.cancle();
}
if(mAccepThread != null) {
mAccepThread.cancle();
}
}
/**
* 以下是单例写法
*/
private static class ChatControlHolder{
private static ChatController mInstance = new ChatController();
}
public static ChatController getInstance(){
return ChatControlHolder.mInstance;
}
}