Android蓝牙通用数据传输之一(SPP)
一、引言Android蓝牙中涉及通用数据传输协议的有两种:SPP协议BLE(Bluetooth low energy)协议SPP协议是Android 2.0引入的API,是通过Socket的形式来实现数据传输及交互,有分客户端和服务端,手机一般以客户端的角色主动连接SPP协议设备。BLE协议是Android 4.3引入的API,但手机厂商大部份在Android 4.4上才...
一、引言
Android蓝牙中涉及通用数据传输协议的有两种:
- SPP协议
- BLE(Bluetooth low energy)协议
SPP协议是Android 2.0引入的API,是通过Socket的形式来实现数据传输及交互,有分客户端和服务端,手机一般以客户端的角色主动连接SPP协议设备。
BLE协议是Android 4.3引入的API,但手机厂商大部份在Android 4.4上才支持BLE,即低功耗蓝牙,一般我们开发的话是使用中央(BluetoothGatt)或者外围(BluetoothGattServer)来进行开发的,手机正常情况下当作中央设备来接收信息,而蓝牙模块当作是外围设备发送数据。
二、SPP协议
SPP协议的连接流程:
- 使用registerReceiver注册BroadcastReceiver来获取蓝牙状态、搜索设备等消息
- 在BroadcastReceiver的onReceive()里取得搜索所得的蓝牙设备信息(如名称,MAC,RSSI)
- 通过设备的MAC地址来建立一个BluetoothDevice对象
- 由BluetoothDevice衍生出BluetoothSocket,准备SOCKET来读写设备
- 通过BluetoothSocket的createRfcommSocketToServiceRecord()方法来选择连接的协议/服务,默认是SPP(UUID:00001101-0000-1000-8000-00805F9B34FB)
- Connect之后(如果还没配对则系统自动提示),使用BluetoothSocket的getInputStream()和getOutputStream()来读写蓝牙设备
- 关闭数据接口
SPP协议涉及4个类:
- BluetoothAdapter:代表本地蓝牙适配器,是所有蓝牙交互的入口。使用这个你可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信
- BluetoothDevice:代表一个远程蓝牙设备,使用这个来请求一个与远程设备的BluetoothSocket连接,或者查询关于设备名称、地址、类和连接状态等设备信息
- BluetoothSocket:代表一个蓝牙socket的接口(和TCP Socket类似)。这是一个连接点,它允许一个应用与其他蓝牙设备通过InputStream和OutputStream交换数据
- BluetoothServerSocket:代表一个开放的服务器socket,它监听接受的请求(与TCP ServerSocket类似)。为了连接两台Android设备,一个设备必须使用这个类开启一个服务器socket。当一个远程蓝牙设备开始一个和该设备的连接请求,BluetoothServerSocket将会返回一个已连接的BluetoothSocket,接受该连接
获取本地蓝牙适配器:
public static BluetoothAdapter getBtAdapter() {
Context mContext = CSmartApplication.getInstance().getApplicationContext();
BluetoothManager bluetoothManager = (BluetoothManager) mContext
.getSystemService(Context.BLUETOOTH_SERVICE);
return bluetoothManager.getAdapter();
}
注册监听:
private void requestProfileConnectionState() {
//检测连接状态:
int a2dp = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.A2DP);
int gatt = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.GATT);
int sap = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.SAP);
//据是否有连接获取已连接的设备
int flag = -1;
if (a2dp == BluetoothProfile.STATE_CONNECTED) {
flag = a2dp;
} else if (gatt == BluetoothProfile.STATE_CONNECTED) {
flag = gatt;
} else if (sap == BluetoothProfile.STATE_CONNECTED) {
flag = sap;
}
if (flag != -1) {
ProxyListener mProxyListener = new ProxyListener();
mBluetoothAdapter.getProfileProxy(this, mProxyListener, flag);
}
}
private void listenBlueState() {
//检查当前是否存在蓝牙连接
requestProfileConnectionState();
if (mFondDevieReceiver == null) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mFondDevieReceiver = new FoundDeviceReceiver();
registerReceiver(mFondDevieReceiver, intentFilter);
}
}
获取远程蓝牙设备:
private class ProxyListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (proxy != null) {
List<BluetoothDevice> mDevices = proxy.getConnectedDevices();
if (mDevices.size() > 0) {
for (int i = 0; i < mDevices.size(); i++) {
//获取BluetoothDevice
mDevice = mDevices.get(0);
logShow("mDevice: " + mDevices.get(0).getAddress());
//调用创建Socket连接
connectDevice();
}
}
mBluetoothAdapter.closeProfileProxy(profile, proxy);
}
}
@Override
public void onServiceDisconnected(int profile) {
}
}
private class FoundDeviceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)
|| BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
int a2dpState = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
int adapterState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.ERROR);
if (BluetoothA2dp.STATE_CONNECTED == a2dpState || BluetoothAdapter.STATE_CONNECTED == adapterState) {//连接成功
//获取BluetoothDevice
mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
logShow("STATE_CONNECTED: " + mDevice.getAddress());
//调用创建Socket连接
connectDevice();
} else if (BluetoothA2dp.STATE_CONNECTING == a2dpState) {//正在连接
} else if (BluetoothA2dp.STATE_DISCONNECTED == a2dpState) {//取消连接
logShow("STATE_DISCONNECTED");
......
}
}
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_OFF:
break;
case BluetoothAdapter.STATE_TURNING_OFF:
break;
case BluetoothAdapter.STATE_ON:
break;
case BluetoothAdapter.STATE_TURNING_ON:
break;
}
}
}
}
创建Socket:
创建RFCOMM的UUID需要与设备端约定,一般使用默认的就可以,但有时因项目特殊,也会另有定义。
private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");//默认UUID
/**
* Create the RFCOMM bluetooth socket.
*
* @param uuid UUID to create the socket with.
* @return BluetoothSocket object.
*/
@TargetApi(10)
private BluetoothSocket createSocket(UUID uuid) {
BluetoothSocket socket = null;
try {
if (VERSION.SDK_INT >= 10) {
socket = mBTDevice.createInsecureRfcommSocketToServiceRecord(uuid);
} else {
socket = mBTDevice.createRfcommSocketToServiceRecord(uuid);
}
} catch (IOException e) {
if (mDebug)
Log.e(TAG, "createSocket: " + e.toString());
try {
Method method = mBTDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
socket = (BluetoothSocket) method.invoke(mBTDevice, Integer.valueOf(1));
return socket;
} catch (Exception e1) {
if (mDebug)
handleException("createSocket", TypeException.CONNECTION_FAILED, e1);
}
}
return socket;
}
数据读写:
注:mInputStream.read(buffer)是I/O口堵塞式的
mBTSocket.connect();
mInputStream = mBTSocket.getInputStream();
//启动新线程去处理
while (this.going) {
if (mInputStream != null) {
try {
int bytes = mInputStream.read(buffer);
if (bytes < 0) {
this.going = false;
} else {
Log.e(TAG, "buffer: " + Arrays.toString(buffer));
//数据解析
mHandler.obtainMessage(Message.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
}
} catch (Exception e) {
Log.e(TAG, "mInputStream read is null");
this.going = false;
}
}
}
public void sendData(byte[] data) {
if (!this.mDfuBusy) {//判断连接是否有效
if (mBTSocket == null) {
Log.e(TAG, "sendCommandData: not connected.");
return;
}
try {
OutputStream outputStream = mBTSocket.getOutputStream();
if (outputStream != null) {
try {
outputStream.write(data);
} catch (Exception e) {
Log.e(TAG, "outputStream.write null");
}
} else {
Log.e(TAG, "outputStream is null");
}
} catch (IOException e) {
Log.e(TAG, "sendData: " + e);
}
}
}
接口关闭:
private void disconnectBluetooth() {
if (mDebug) {
Log.i(TAG, "disconnect BT");
}
if (mBTSocket != null) {
try {
if (mInputStream != null) {
mInputStream.close();
mInputStream = null;
}
if (mBTSocket != null) {
if (mBTSocket.getInputStream() != null) {
mBTSocket.getInputStream().close();
}
}
if (mBTSocket != null) {
if (mBTSocket.getOutputStream() != null) {
mBTSocket.getOutputStream().close();
}
}
if (mBTSocket != null) {
mBTSocket.close();
}
mBTSocket = null;
mBTDevice = null;
} catch (IOException e) {
if (mDebug) {
Log.e(TAG, e.toString());
}
}
}
}
服务端的Socket建立与客户端差不多,需注意以下两个步骤:
- 创建监听listen(蓝牙没有此监听,但需要通过whlie(true)死循环来一直监听的)
- 通过accept(),如果有客户端连接,会创建一个新的Socket,体现出并发性,可以同时与多个socket通讯)
总之:SPP的连接和操作相对比较简单,考虑的事情也少。需要注意的是频繁数据发送(间隔时间短)的情况下,APP收到的数据并不是按原先约定的TLV一包一包数据,可能会被拆包,应对的办法就是启动线程接收完数据,然后去一包一包取。
参考
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)