Android 蓝牙的一些使用心得
最近在搞蓝牙串口开发,由于此前对蓝牙这块接触较少,所以在写项目时碰壁不少。在查阅了不少大神写的Android蓝牙项目技术文章后,于是写下本篇文章,算是自己这段时间对蓝牙些许了解的一个总结吧。
注:本篇章只介绍经典蓝牙,想了解低功耗蓝牙的请出门左转,哈哈~~~
蓝牙开发的基本流程为:开启-》搜索-》配对-》连接-》数据交换。其中配对成功之后系统会自动执行连接操作。
权限
为了后面工作的不必要麻烦,首先在AndroidManifest.xml配置蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
如果是6.0以上的系统,还需要增加定位相关的权限,还会延申到动态申请权限和GPS的检测
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
开启蓝牙
首先获取本地蓝牙适配器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);
... ...
@Override
protected 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();
}
}
}
搜索蓝牙
扫描附近的蓝牙,开始扫描前前取消正在执行的搜索
//取消当前正在搜索设备...
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软件模拟数据接收和发送
建立连接
因为设备的连接时耗时的,所以我们需要另起一个线程ConnectThread,通过bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(BLUETOOTH_UUID));拿到BluetoothSocket,然后就可以connect了
需要注意的是 调用connect(),最好先释放BluetoothSocket
如果没配对过,会弹出配对请求,配对成功后系统会自动连接,设备连接成功后我们就可以处理数据的收发了
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;
}
}
}
以上就是经典蓝牙的基本操作流程了,剩下的就是需要注意一些细节了,比如设备搜索时长的控制、连接设备超时的处理等等。源码传送门
欢迎关注我的个人微信公众号,【优了个秀】和你每天进步一点点
上一篇: JNative的一些使用心得
推荐阅读
-
Android Handler的使用详解
-
Android bindService的使用与Service生命周期案例详解
-
Android-轮播图banner的使用步骤
-
一些使用频率比较高的php函数_php技巧
-
Android使用AudioRecord和AudioTrack完成音频的采集和播放以及使用MediaCodec完成硬编和硬解
-
Android获取当前应用分配的最大内存和目前使用内存的方法
-
Eclipse中关于mybatis插件geneartor的使用和遇到的一些问题
-
Android持久化存储(4)greenDAO的使用
-
Python标准库urllib2的一些使用细节总结
-
一些使用频率较高的非常实用的PHP函数