最近在搞蓝牙串口开发,由于此前对蓝牙这块接触较少,所以在写项目时碰壁不少。在查阅了不少大神写的Android蓝牙项目技术文章后,于是写下本篇文章,算是自己这段时间对蓝牙些许了解的一个总结吧。
注:本篇章只介绍经典蓝牙,想了解低功耗蓝牙的请出门左转,哈哈~~~
蓝牙开发的基本流程为:开启-》搜索-》配对-》连接-》数据交换。其中配对成功之后系统会自动执行连接操作。
![3778bf18aeb5bc15562e692ade3b784e.gif](https://i-blog.csdnimg.cn/blog_migrate/39867aa92088a47c778393b15a9040d3.gif)
权限
为了后面工作的不必要麻烦,首先在AndroidManifest.xml配置蓝牙权限
如果是6.0以上的系统,还需要增加定位相关的权限,还会延申到动态申请权限和GPS的检测
开启蓝牙
首先获取本地蓝牙适配器BluetoothAdapter 如果为空,说明设备不支持蓝牙。
public BluetoothAdapter getBtAdapter() { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null){ Log.i(TAG, "getBtAdapter: 该设备不支持蓝牙"); } return bluetoothAdapter;}
开启蓝牙的方式有两种,第一种简单粗暴,直接调用enable()方法即可,没有如何提示
bluetoothAdapter.enable();
第二种方式:Intent隐式打开,然后在onActivityResult方法中接收结果
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH); ... ...@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_ENABLE_BLUETOOTH) { if (resultCode == Activity.RESULT_OK) { Toast.makeText(BluetoothActivity.this, "打开蓝牙成功", Toast.LENGTH_SHORT).show(); tvStatu.setText("蓝牙已开启"); searchBtDevice(); } else { Toast.makeText(BluetoothActivity.this, "打开蓝牙失败", Toast.LENGTH_SHORT).show(); } }}
![e0bdd0bc40d95b1670fd5f17a120f9e9.png](https://i-blog.csdnimg.cn/blog_migrate/13d16f828ac8b0ccb9b198744d96210b.jpeg)
搜索蓝牙
扫描附近的蓝牙,开始扫描前前取消正在执行的搜索
//取消当前正在搜索设备... if (getBtAdapter().isDiscovering()) { getBtAdapter().cancelDiscovery(); } getBtAdapter().startDiscovery();
startDiscovery()是一个一步操作,一般会搜索12秒左右
广播接收扫描结果
注册广播
IntentFilter filter = new IntentFilter();filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); //开始扫描filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//扫描结束filter.addAction(BluetoothDevice.ACTION_FOUND);//搜索到设备filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); //配对状态监听
接收广播
自定义一个广播接收类,监听设备扫描状态
/** * 搜索蓝牙广播接收器 */private class SearchReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { mOnSearchDeviceListener.onStartDiscovery(); //开始搜索 } else if (BluetoothDevice.ACTION_FOUND.equals(action)) { //发现设备 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (mOnSearchDeviceListener != null) { mOnSearchDeviceListener.onNewDeviceFound(device); } if (device.getBondState() == BluetoothDevice.BOND_NONE) { mNewList.add(device); } else if (device.getBondState() == BluetoothDevice.BOND_BONDED) { mBondedList.add(device); } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { if (getBtAdapter() != null) { getBtAdapter().cancelDiscovery(); } //停止搜索 if (mOnSearchDeviceListener != null) { mOnSearchDeviceListener.onSearchCompleted(mBondedList, mNewList); } } }}
连接设备
我手上有个蓝牙转串口USB工具,所以就不写蓝牙服务端了。同时使用了SSCOM软件模拟数据接收和发送
![51000a4c52163c319540566ed4ebbf77.png](https://i-blog.csdnimg.cn/blog_migrate/5bafae419de7d824cb5d5a064b02d1b3.jpeg)
建立连接
因为设备的连接时耗时的,所以我们需要另起一个线程ConnectThread,通过bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(BLUETOOTH_UUID));拿到BluetoothSocket,然后就可以connect了
需要注意的是 调用connect(),最好先释放BluetoothSocket
如果没配对过,会弹出配对请求,配对成功后系统会自动连接,设备连接成功后我们就可以处理数据的收发了
![64ad90af0c7d373db61bb18930820995.png](https://i-blog.csdnimg.cn/blog_migrate/7dddcb91474db1b233afe7a29350d4c0.jpeg)
public class ConnectThread extends Thread { private static final String TAG = "ConnectThread"; private final BluetoothAdapter mBluetoothAdapter; private BluetoothSocket mmSocket; private final String BLUETOOTH_UUID = "00001101-0000-1000-8000-00805F9B34FB"; public ConnectThread(BluetoothAdapter bluetoothAdapter, BluetoothDevice bluetoothDevice) { this.mBluetoothAdapter = bluetoothAdapter; BluetoothSocket tmpBluetoothSocket = null; if (mmSocket != null) { Log.i(TAG, "ConnectThread-->mmSocket != null先去释放"); try { mmSocket.close(); } catch (IOException e) { e.printStackTrace(); } } Log.i(TAG, "ConnectThread-->mmSocket != null已释放"); //1、获取BluetoothSocket try { //建立安全的蓝牙连接,会弹出配对框 tmpBluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(BLUETOOTH_UUID)); } catch (IOException e) { Log.i(TAG, "ConnectThread-->获取BluetoothSocket异常!" + e.getMessage()); } mmSocket = tmpBluetoothSocket; if (mmSocket != null) { Log.i(TAG, "ConnectThread-->已获取BluetoothSocket"); } } @Override public void run() { //连接之前先取消发现设备,否则会大幅降低连接尝试的速度,并增加连接失败的可能性 if (mBluetoothAdapter == null) { Log.i(TAG, "ConnectThread:run-->mBluetoothAdapter == null"); return; } //取消发现设备 if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } if (mmSocket == null) { Log.i(TAG, "ConnectThread:run-->mmSocket == null"); return; } //2、通过socket去连接设备 try { Log.i(TAG, "ConnectThread:run-->去连接..."); if (onBluetoothConnectListener != null) { onBluetoothConnectListener.onStartConn(); //开始去连接回调 } mmSocket.connect(); //connect()为阻塞调用,连接失败或 connect() 方法超时(大约 12 秒之后),它将会引发异常 if (onBluetoothConnectListener != null) { onBluetoothConnectListener.onConnSuccess(mmSocket); //连接成功回调 Log.i(TAG, "ConnectThread:run-->连接成功"); } } catch (IOException e) { Log.i(TAG, "ConnectThread:run-->连接异常!" + e.getMessage()); if (onBluetoothConnectListener != null) { onBluetoothConnectListener.onConnFailure("连接异常:" + e.getMessage()); } //释放 cancel(); } } /** * 释放 */ public void cancel() { try { if (mmSocket != null && mmSocket.isConnected()) { Log.i(TAG, "ConnectThread:cancel-->mmSocket.isConnected() = " + mmSocket.isConnected()); mmSocket.close(); mmSocket = null; return; } if (mmSocket != null) { mmSocket.close(); mmSocket = null; } Log.i(TAG, "ConnectThread:cancel-->关闭已连接的套接字释放资源"); } catch (IOException e) { Log.i(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); //连接失败 } }
数据交换
设备连接成功后就可以进行数据交换了。我们可以写个ConnectedThread线程来处理数据的接收read()和发送write()。
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; private OnReceiveListener onReceiveListener; private Handler handler = new Handler(Looper.getMainLooper()); public void terminalClose(ConnectThread connectThread) { isStop = true; this.connectThread = connectThread; } public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; //使用临时对象获取输入和输出流,因为成员流是静态类型 try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.i(TAG, "ConnectedThread-->获取InputStream 和 OutputStream异常!"); } mmInStream = tmpIn; mmOutStream = tmpOut; if (mmInStream != null) { Log.i(TAG, "ConnectedThread-->已获取InputStream"); } if (mmOutStream != null) { Log.i(TAG, "ConnectedThread-->已获取OutputStream"); } } @Override public void run() { //最大缓存区 存放流 byte[] buffer = new byte[1024 * 2]; //持续监听输入流直到发生异常 while (!isStop) { try { if (mmInStream == null) { Log.i(TAG, "ConnectedThread:run-->输入流mmInStream == null"); break; } //先判断是否有数据,有数据再读取 if (mmInStream.available() != 0) { //2、接收数据 /* 为了demo正常运行,假设接收的数据长度为10; 实际上我们可以自定义数据格式,如:STX + LENGTH(2hex bytes)+ CONTENT + ETX + LRC */ int msgLen = 10; int readTotal = 0; int readCount; while (readTotal < msgLen) { readCount = mmInStream.read(buffer, readTotal, msgLen - readTotal); readTotal += readCount; } final byte[] respData = new byte[readTotal]; System.arraycopy(buffer, 0, respData, 0, readTotal); if (onReceiveListener != null) { handler.post(new Runnable() { @Override public void run() { onReceiveListener.onReceiveDataSuccess(respData); //成功收到消息 } }); } } } catch (final IOException e) { Log.i(TAG, "ConnectedThread:run-->接收消息异常!" + e.getMessage()); if (onReceiveListener != null) { handler.post(new Runnable() { @Override public void run() { onReceiveListener.onReceiveDataError("接收消息异常:" + e.getMessage()); //接收消息异常 } }); } //关闭流和socket boolean isClose = cancel(); if (isClose) { Log.i(TAG, "ConnectedThread:run-->接收消息异常,成功断开连接!"); } break; } } //关闭流和socket boolean isClose = cancel(); if (isClose) { Log.i(TAG, "ConnectedThread:run-->接收消息结束,断开连接!"); } } public void receive(OnReceiveListener onReceiveListener) { this.onReceiveListener = onReceiveListener; } //发送数据 public void write(final byte[] bytes, final OnSendListener onSendListener) { try { if (mmOutStream == null) { Log.i(TAG, "mmOutStream == null"); return; } mmOutStream.write(bytes); Log.i(TAG, "写入成功:" + new String(bytes)); if (onSendListener != null) { handler.post(new Runnable() { @Override public void run() { onSendListener.onSendSuccess(bytes); //发送数据成功回调 } }); } } catch (IOException e) { Log.i(TAG, "写入失败:" + new String(bytes)); if (onSendListener != null) { handler.post(new Runnable() { @Override public void run() { onSendListener.onSendError(bytes, "写入失败"); //发送数据失败回调 } }); } } } /** * 释放 * * @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.i(TAG, "ConnectedThread:cancel-->成功断开连接"); return true; } catch (IOException e) { // 任何一部分报错,都将强制关闭socket连接 mmInStream = null; mmOutStream = null; mmSocket = null; Log.i(TAG, "ConnectedThread:cancel-->断开连接异常!" + e.getMessage()); return false; } }}
以上就是经典蓝牙的基本操作流程了,剩下的就是需要注意一些细节了,比如设备搜索时长的控制、连接设备超时的处理等等。源码传送门