目录
一、前言
二、经典蓝牙的介绍
三、经典蓝牙项目实战
四、Demo案例源码地址:
一、前言
二、经典蓝牙的介绍
经典蓝牙的使用过程大致可分为以下几个步骤:
1、开启扫描,搜索周围蓝牙设备
2、扫描到设备后,与设备配对、建立连接
3、与设备成功连接后,实现数据通讯即收发数据
4、与设备通讯结束后,关闭与蓝牙的连接
三、经典蓝牙项目实战
1、在开始使用蓝牙之前,我们必须要声明两个权限,第一个是蓝牙权限,第二个是位置权限。
(1)蓝牙权限
<!-- 应用使用蓝牙的权限 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!--启动设备发现或操作蓝牙设置的权限-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
(2)位置权限(注意:Android 6.0以上版本还需要动态申请位置权限!)
<!--位置权限-->
<!--Android 10以上系统,需要ACCESS_FINE_LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--Android 9以及以下系统,需要ACCESS_FINE_LOCATION-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
2、创建发起蓝牙连接的线程ConnectThread.java 和 管理蓝牙连接、收发数据的线程ConnectedThread.java。
(1)ConnectThread.java,代码中已经有详细的注释。
package yc.bluetooth.androidbt;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import java.io.IOException;
import java.util.UUID;
/**
* 发起蓝牙连接
*/
public class ConnectThread extends Thread {
private static final String TAG = "ConnectThread";
private final BluetoothAdapter mBluetoothAdapter;
private BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothAdapter bluetoothAdapter,BluetoothDevice bluetoothDevice,String uuid) {
this.mBluetoothAdapter = bluetoothAdapter;
this.mmDevice = bluetoothDevice;
//使用一个临时变量,等会赋值给mmSocket
//因为mmSocket是静态的
BluetoothSocket tmp = null ;
if(mmSocket != null){
Log.e(TAG,"ConnectThread-->mmSocket != null先去释放");
try {
mmSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.d(TAG,"ConnectThread-->mmSocket != null已释放");
//1、获取BluetoothSocket
try {
//建立安全的蓝牙连接,会弹出配对框
tmp = mmDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
} catch (IOException e) {
Log.e(TAG,"ConnectThread-->获取BluetoothSocket异常!" + e.getMessage());
}
mmSocket = tmp;
if(mmSocket != null){
Log.w(TAG,"ConnectThread-->已获取BluetoothSocket");
}
}
@Override
public void run(){
//连接之前先取消发现设备,否则会大幅降低连接尝试的速度,并增加连接失败的可能性
if(mBluetoothAdapter == null){
Log.e(TAG,"ConnectThread:run-->mBluetoothAdapter == null");
return;
}
//取消发现设备
if(mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
if(mmSocket == null){
Log.e(TAG,"ConnectThread:run-->mmSocket == null");
return;
}
//2、通过socket去连接设备
try {
Log.d(TAG,"ConnectThread:run-->去连接...");
if(onBluetoothConnectListener != null){
onBluetoothConnectListener.onStartConn(); //开始去连接回调
}
mmSocket.connect(); //connect()为阻塞调用,连接失败或 connect() 方法超时(大约 12 秒之后),它将会引发异常
if(onBluetoothConnectListener != null){
onBluetoothConnectListener.onConnSuccess(mmSocket); //连接成功回调
Log.w(TAG,"ConnectThread:run-->连接成功");
}
} catch (IOException e) {
Log.e(TAG,"ConnectThread:run-->连接异常!" + e.getMessage());
if(onBluetoothConnectListener != null){
onBluetoothConnectListener.onConnFailure("连接异常:" + e.getMessage());
}
//释放
cancel();
}
}
/**
* 释放
*/
public void cancel() {
try {
if (mmSocket != null && mmSocket.isConnected()) {
Log.d(TAG,"ConnectThread:cancel-->mmSocket.isConnected() = " + mmSocket.isConnected());
mmSocket.close();
mmSocket = null;
return;
}
if (mmSocket != null) {
mmSocket.close();
mmSocket = null;
}
Log.d(TAG,"ConnectThread:cancel-->关闭已连接的套接字释放资源");
} catch (IOException e) {
Log.e(TAG,"ConnectThread:cancel-->关闭已连接的套接字释放资源异常!" + e.getMessage());
}
}
private OnBluetoothConnectListener onBluetoothConnectListener;
public void setOnBluetoothConnectListener(OnBluetoothConnectListener onBluetoothConnectListener) {
this.onBluetoothConnectListener = onBluetoothConnectListener;
}
//连接状态监听者
public interface OnBluetoothConnectListener{
void onStartConn(); //开始连接
void onConnSuccess(BluetoothSocket bluetoothSocket); //连接成功
void onConnFailure(String errorMsg); //连接失败
}
}
(2)ConnectedThread.java,代码中已经有详细的注释。
package yc.bluetooth.androidbt;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
/**
* 管理连接
* 1、发送数据
* 2、接收数据
*/
public class ConnectedThread extends Thread{
private static final String TAG = "ConnectedThread";
private BluetoothSocket mmSocket;
private InputStream mmInStream;
private OutputStream mmOutStream;
//是否是主动断开
private boolean isStop = false;
//发起蓝牙连接的线程
private ConnectThread connectThread;
public void terminalClose(ConnectThread connectThread){
isStop = true;
this.connectThread = connectThread;
}
public ConnectedThread(BluetoothSocket socket){
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
//使用临时对象获取输入和输出流,因为成员流是静态类型
//1、获取 InputStream 和 OutputStream
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG,"ConnectedThread-->获取InputStream 和 OutputStream异常!");
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
if(mmInStream != null){
Log.d(TAG,"ConnectedThread-->已获取InputStream");
}
if(mmOutStream != null){
Log.d(TAG,"ConnectedThread-->已获取OutputStream");
}
}
public void run(){
//最大缓存区 存放流
byte[] buffer = new byte[1024 * 2]; //buffer store for the stream
//从流的read()方法中读取的字节数
int bytes = 0; //bytes returned from read()
//持续监听输入流直到发生异常
while(!isStop){
try {
if(mmInStream == null){
Log.e(TAG,"ConnectedThread:run-->输入流mmInStream == null");
break;
}
//先判断是否有数据,有数据再读取
if(mmInStream.available() != 0){
//2、接收数据
bytes = mmInStream.read(buffer); //从(mmInStream)输入流中(读取内容)读取的一定数量字节数,并将它们存储到缓冲区buffer数组中,bytes为实际读取的字节数
byte[] b = Arrays.copyOf(buffer,bytes); //存放实际读取的数据内容
Log.w(TAG,"ConnectedThread:run-->收到消息,长度" + b.length + "->" + bytes2HexString(b, b.length)); //有空格的16进制字符串
if(onSendReceiveDataListener != null){
onSendReceiveDataListener.onReceiveDataSuccess(b); //成功收到消息
}
}
} catch (IOException e) {
Log.e(TAG,"ConnectedThread:run-->接收消息异常!" + e.getMessage());
if(onSendReceiveDataListener != null){
onSendReceiveDataListener.onReceiveDataError("接收消息异常:" + e.getMessage()); //接收消息异常
}
//关闭流和socket
boolean isClose = cancel();
if(isClose){
Log.e(TAG,"ConnectedThread:run-->接收消息异常,成功断开连接!");
}
break;
}
}
//关闭流和socket
boolean isClose = cancel();
if(isClose){
Log.d(TAG,"ConnectedThread:run-->接收消息结束,断开连接!");
}
}
//发送数据
public boolean write(byte[] bytes){
try {
if(mmOutStream == null){
Log.e(TAG, "mmOutStream == null");
return false;
}
//发送数据
mmOutStream.write(bytes);
Log.d(TAG, "写入成功:"+ bytes2HexString(bytes, bytes.length));
if(onSendReceiveDataListener != null){
onSendReceiveDataListener.onSendDataSuccess(bytes); //发送数据成功回调
}
return true;
} catch (IOException e) {
Log.e(TAG, "写入失败:"+ bytes2HexString(bytes, bytes.length));
if(onSendReceiveDataListener != null){
onSendReceiveDataListener.onSendDataError(bytes,"写入失败"); //发送数据失败回调
}
return false;
}
}
/**
* 释放
* @return true 断开成功 false 断开失败
*/
public boolean cancel(){
try {
if(mmInStream != null){
mmInStream.close(); //关闭输入流
}
if(mmOutStream != null){
mmOutStream.close(); //关闭输出流
}
if(mmSocket != null){
mmSocket.close(); //关闭socket
}
if(connectThread != null){
connectThread.cancel();
}
connectThread = null;
mmInStream = null;
mmOutStream = null;
mmSocket = null;
Log.w(TAG,"ConnectedThread:cancel-->成功断开连接");
return true;
} catch (IOException e) {
// 任何一部分报错,都将强制关闭socket连接
mmInStream = null;
mmOutStream = null;
mmSocket = null;
Log.e(TAG, "ConnectedThread:cancel-->断开连接异常!" + e.getMessage());
return false;
}
}
/**
* 字节数组-->16进制字符串
* @param b 字节数组
* @param length 字节数组长度
* @return 16进制字符串 有空格类似“0A D5 CD 8F BD E5 F8”
*/
public static String bytes2HexString(byte[] b, int length) {
StringBuffer result = new StringBuffer();
String hex;
for (int i = 0; i < length; i++) {
hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
result.append(hex.toUpperCase()).append(" ");
}
return result.toString();
}
private OnSendReceiveDataListener onSendReceiveDataListener;
public void setOnSendReceiveDataListener(OnSendReceiveDataListener onSendReceiveDataListener) {
this.onSendReceiveDataListener = onSendReceiveDataListener;
}
//收发数据监听者
public interface OnSendReceiveDataListener{
void onSendDataSuccess(byte[] data); //发送数据结束
void onSendDataError(byte[] data, String errorMsg); //发送数据出错
void onReceiveDataSuccess(byte[] buffer); //接收到数据
void onReceiveDataError(String errorMsg); //接收数据出错
}
}
3、使用蓝牙之前,首先要检查当前手机是否支持蓝牙。如果支持蓝牙,检查手机蓝牙是否已开启。如果没有开启,则需要先打开蓝牙。打开手机蓝牙,有两种方式,推荐使用第二种打开方式。
private void initBluetooth() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter == null){
Toast.makeText(this, "当前手机设备不支持蓝牙", Toast.LENGTH_SHORT).show();
}else{
//手机设备支持蓝牙,判断蓝牙是否已开启
if(bluetoothAdapter.isEnabled()){
Toast.makeText(this, "手机蓝牙已开启", Toast.LENGTH_SHORT).show();
}else{
//蓝牙没有打开,去打开蓝牙。推荐使用第二种打开蓝牙方式
//第一种方式:直接打开手机蓝牙,没有任何提示
// bluetoothAdapter.enable(); //BLUETOOTH_ADMIN权限
//第二种方式:友好提示用户打开蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
}
}
}
4、确保手机蓝牙已打开,就可以开始搜索设备。搜索设备只需调用startDiscovery()方法,但搜索的结果是通过广播来获取的,所以,还需要定义广播来获取搜索到的设备。
(1)搜索设备
private void searchBtDevice() { if(bluetoothAdapter.isDiscovering()){ //当前正在搜索设备... return; } //开始搜索 bluetoothAdapter.startDiscovery(); }
(2)自定义广播接收器,接收搜索到的设备。
/**
* 蓝牙广播接收器
*/
private static class BtBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, BluetoothAdapter.ACTION_DISCOVERY_STARTED)) { //开启搜索
if (onDeviceSearchListener != null) {
onDeviceSearchListener.onDiscoveryStart(); //开启搜索回调
}
} else if (TextUtils.equals(action, BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {//完成搜素
if (onDeviceSearchListener != null) {
onDeviceSearchListener.onDiscoveryStop(); //完成搜素回调
}
} else if (TextUtils.equals(action, BluetoothDevice.ACTION_FOUND)) { //3.0搜索到设备
//蓝牙设备
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//信号强度
int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
Log.d(TAG, "扫描到设备:" + bluetoothDevice.getName() + "-->" + bluetoothDevice.getAddress());
if (onDeviceSearchListener != null) {
onDeviceSearchListener.onDeviceFound(bluetoothDevice,rssi); //3.0搜素到设备回调
}
}
}
/**
* 蓝牙设备搜索监听者
* 1、开启搜索
* 2、完成搜索
* 3、搜索到设备
*/
public interface OnDeviceSearchListener {
void onDiscoveryStart(); //开启搜索
void onDiscoveryStop(); //完成搜索
void onDeviceFound(BluetoothDevice bluetoothDevice, int rssi); //搜索到设备
}
private OnDeviceSearchListener onDeviceSearchListener;
public void setOnDeviceSearchListener(OnDeviceSearchListener onDeviceSearchListener) {
this.onDeviceSearchListener = onDeviceSearchListener;
}
}
(3)注册广播接收器
private void initBtBroadcast() {
//注册广播接收
btBroadcastReceiver = new BtBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); //开始扫描
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//扫描结束
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);//搜索到设备
registerReceiver(btBroadcastReceiver,intentFilter);
}
(4)注册过广播之后,要记得在onDestroy()中注销广播
@Override
protected void onDestroy() {
super.onDestroy();
//注销广播接收
unregisterReceiver(btBroadcastReceiver);
}
5、搜索到目标设备之后,就可以与蓝牙设备建立连接。
(1)发起连接
/**
* 开始连接设备
* @param bluetoothDevice 蓝牙设备
* @param uuid 发起连接的UUID
* @param conOutTime 连接超时时间
*/
public void startConnectDevice(final BluetoothDevice bluetoothDevice, String uuid, long conOutTime){
if(bluetoothDevice == null){
Log.e(TAG,"startConnectDevice-->bluetoothDevice == null");
return;
}
if(bluetoothAdapter == null){
Log.e(TAG,"startConnectDevice-->bluetooth3Adapter == null");
return;
}
//发起连接
connectThread = new ConnectThread(bluetoothAdapter,curBluetoothDevice,uuid);
connectThread.setOnBluetoothConnectListener(new ConnectThread.OnBluetoothConnectListener() {
@Override
public void onStartConn() {
Log.d(TAG,"startConnectDevice-->开始连接..." + bluetoothDevice.getName() + "-->" + bluetoothDevice.getAddress());
}
@Override
public void onConnSuccess(BluetoothSocket bluetoothSocket) {
//移除连接超时
mHandler.removeCallbacks(connectOuttimeRunnable);
Log.d(TAG,"startConnectDevice-->移除连接超时");
Log.w(TAG,"startConnectDevice-->连接成功");
Message message = new Message();
message.what = CONNECT_SUCCESS;
mHandler.sendMessage(message);
//标记当前连接状态为true
curConnState = true;
//管理连接,收发数据
managerConnectSendReceiveData(bluetoothSocket);
}
@Override
public void onConnFailure(String errorMsg) {
Log.e(TAG,"startConnectDevice-->" + errorMsg);
Message message = new Message();
message.what = CONNECT_FAILURE;
mHandler.sendMessage(message);
//标记当前连接状态为false
curConnState = false;
//断开管理连接
clearConnectedThread();
}
});
connectThread.start();
//设置连接超时时间
mHandler.postDelayed(connectOuttimeRunnable,conOutTime);
}
//连接超时
private Runnable connectOuttimeRunnable = new Runnable() {
@Override
public void run() {
Log.e(TAG,"startConnectDevice-->连接超时" );
Message message = new Message();
message.what = CONNECT_FAILURE;
mHandler.sendMessage(message);
//标记当前连接状态为false
curConnState = false;
//断开管理连接
clearConnectedThread();
}
};
(2)管理连接,数据收发
managerConnectSendReceiveData()方法中,connectedThread对象进行数据发送结果、接收结果监听。
/**
* 管理已建立的连接,收发数据
* @param bluetoothSocket 已建立的连接
*/
public void managerConnectSendReceiveData(BluetoothSocket bluetoothSocket){
//管理已有连接
connectedThread = new ConnectedThread(bluetoothSocket);
connectedThread.start();
connectedThread.setOnSendReceiveDataListener(new ConnectedThread.OnSendReceiveDataListener() {
@Override
public void onSendDataSuccess(byte[] data) {
Log.w(TAG,"发送数据成功,长度" + data.length + "->" + bytes2HexString(data,data.length));
Message message = new Message();
message.what = SEND_SUCCESS;
message.obj = "发送数据成功,长度" + data.length + "->" + bytes2HexString(data,data.length);
mHandler.sendMessage(message);
}
@Override
public void onSendDataError(byte[] data,String errorMsg) {
Log.e(TAG,"发送数据出错,长度" + data.length + "->" + bytes2HexString(data,data.length));
Message message = new Message();
message.what = SEND_FAILURE;
message.obj = "发送数据出错,长度" + data.length + "->" + bytes2HexString(data,data.length);
mHandler.sendMessage(message);
}
@Override
public void onReceiveDataSuccess(byte[] buffer) {
Log.w(TAG,"成功接收数据,长度" + buffer.length + "->" + bytes2HexString(buffer,buffer.length));
Message message = new Message();
message.what = RECEIVE_SUCCESS;
message.obj = "成功接收数据,长度" + buffer.length + "->" + bytes2HexString(buffer,buffer.length);
mHandler.sendMessage(message);
}
@Override
public void onReceiveDataError(String errorMsg) {
Log.e(TAG,"接收数据出错:" + errorMsg);
Message message = new Message();
message.what = RECEIVE_FAILURE;
message.obj = "接收数据出错:" + errorMsg;
mHandler.sendMessage(message);
}
});
}
sendData()方法中,connectedThread对象发送数据。
/**
* 发送数据
* @param data 要发送的数据 字符串
* @param isHex 是否是16进制字符串
* @return true 发送成功 false 发送失败
*/
public boolean sendData(String data,boolean isHex){
if(connectedThread == null){
Log.e(TAG,"sendData:string -->connectedThread == null");
return false;
}
if(data == null || data.length() == 0){
Log.e(TAG,"sendData:string-->要发送的数据为空");
return false;
}
if(isHex){ //是16进制字符串
data.replace(" ",""); //取消空格
//检查16进制数据是否合法
if(data.length() % 2 != 0){
//不合法,最后一位自动填充0
String lasts = "0" + data.charAt(data.length() - 1);
data = data.substring(0,data.length() - 2) + lasts;
}
Log.d(TAG,"sendData:string -->准备写入:" + data); //加空格显示
return connectedThread.write(hexString2Bytes(data));
}
//普通字符串
Log.d(TAG,"sendData:string -->准备写入:" + data);
return connectedThread.write(data.getBytes());
}
6、与蓝牙设备通讯结束之后,可与蓝牙设备断开连接。
/**
* 断开已有的连接
*/
public void clearConnectedThread(){
Log.d(TAG,"clearConnectedThread-->即将断开");
//connectedThread断开已有连接
if(connectedThread == null){
Log.e(TAG,"clearConnectedThread-->connectedThread == null");
return;
}
connectedThread.terminalClose(connectThread);
//等待线程运行完后再断开
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
connectedThread.cancel(); //释放连接
connectedThread = null;
}
},10);
Log.w(TAG,"clearConnectedThread-->成功断开连接");
Message message = new Message();
message.what = DISCONNECT_SUCCESS;
mHandler.sendMessage(message);
}
7、项目演示
(1)扫描到设备,点击“连接”按钮, 会在“搜索”按钮下方显示连接结果 。注意经典蓝牙连接是,第一次连接时会有弹出一个配对框,这个具体配对方式是蓝牙设备开发人员设置的。
(2)手机给蓝牙设备(设备名为:BTyqy)发送数据成功之后,蓝牙设备把接收到的数据再回发送给手机。
(3)断开连接。点击“断开”按钮, 会在“搜索”按钮下方显示断开结果 。