蓝牙通讯:本质是通过socket做虚拟串口通讯。
关键字:
- RFCOMM(串口电缆仿真协议)
- socket通讯
- 可被发现
- 扫描设备
- 配对
- UUID (客户端与服务端UUID要相同)
关键步骤点:
- 确定终端支持蓝牙
- 确认并请求开启蓝牙
- 扫描发现已配对设备
- 扫描发现可配对设备
- 连接设备(发起配对)
- 配对连接后,Socket通讯
以上关键步骤有些可选,我们扫描及配对等步骤通过终端系统先操作,编码先实现连接设备及socket通讯。其他功能根据需要添加,后面也会一一介绍。
实现说明:
服务端:
- 获取蓝牙设备对象
- 建立服务端监听socket对象
- 启动socket对象的accept()方法监听客户端的连接
- 管理socket输入输出流进行通讯
package com.bluetooth.sevice;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private static final String NAME_SECURE = "BluetoothChatSecure";
private static final UUID SPP_MY_UUID_SECURE = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private BluetoothAdapter mBluetoothAdapter;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MESSAGE_READ:
Toast.makeText(MainActivity.this,"数据接收成功",Toast.LENGTH_LONG).show();
parseReceiveData((byte[]) msg.obj);
break;
}
super.handleMessage(msg);
}
};
private ConnectedThread connectedThread;
private AcceptThread acceptThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1、获取蓝牙设备对象,mBluetoothAdapter如果为空表示终端不存在蓝牙设备
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//isEnabled判断蓝牙开关是否开启
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 99);
} else {
acceptThread = new AcceptThread();
acceptThread.start();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
acceptThread = new AcceptThread();
acceptThread.start();
}
}
private void parseReceiveData(byte[] obj) {
Log.i("接收到数据:", "长度:"+obj.length+"数据:"+HexUtils.bcd2str(obj));
}
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
//2.建立服务端监听socket对象
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE,SPP_MY_UUID_SECURE);
} catch (IOException e) { }
mmServerSocket = tmp;
}
@Override
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
//3.启动socket对象的accept()方法监听客户端的连接
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
//4.管理socket输入输出流进行通讯
connectedThread = new ConnectedThread(socket);
connectedThread.start();
try {
mmServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
@Override
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
while (true) {
try {
bytes = mmInStream.read(buffer);
byte[] outBuf = new byte[bytes];
System.arraycopy(buffer,0,outBuf,0,bytes);
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, outBuf)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != connectedThread) {
connectedThread.cancel();
}
if (null != acceptThread) {
acceptThread.cancel();
}
}
}
客户端:
- 获取蓝牙设备对象
- 通过服务端mac发起配对
- 建立客户端socket对象
- 通过socket的connect()方法与服务端建立连接
- 管理socket输入输出流进行通讯
package com.bluetooth.client;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private final String macAddress = "48:E6:C0:2D:CA:C5";
private ConnectThread connectThread;
private ConnectedThread connectedThread;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MESSAGE_READ:
Toast.makeText(MainActivity.this,"数据接收成功",Toast.LENGTH_LONG).show();
parseReceiveData((byte[]) msg.obj);
break;
}
super.handleMessage(msg);
}
};
private BluetoothAdapter mBluetoothAdapter;
private void parseReceiveData(byte[] obj) {
Log.i("接收到数据:", "长度:"+obj.length+"数据:"+HexUtils.bcd2str(obj));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
JSONObject req = new JSONObject();
byte[] bytes = null;
try {
req.put("type", "face");
bytes = req.toString().getBytes("GBK");
} catch (JSONException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (null != bytes && null != connectedThread) {
connectedThread.write(bytes);
}
}
});
//1、获取蓝牙设备对象mBluetoothAdapter如果为空表示终端不存在蓝牙设备
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (null != mBluetoothAdapter) {
//isEnabled判断蓝牙开关是否开启
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 99);
} else {
//2.通过服务端mac发起配对
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddress);
connectThread = new ConnectThread(device);
connectThread.start();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddress);
connectThread = new ConnectThread(device);
connectThread.start();
}
}
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private final UUID MY_UUID_SECURE = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
public ConnectThread(BluetoothDevice device) {
BluetoothSocket tmp = null;
mmDevice = device;
try {
//3.建立客户端socket对象
tmp = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
} catch (IOException e) { }
mmSocket = tmp;
}
@Override
public void run() {
try {
//4.通过socket的connect()方法与服务端建立连接
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
//5.管理socket输入输出流进行通讯
connectedThread = new ConnectedThread(mmSocket);
connectedThread.start();
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
@Override
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
while (true) {
try {
bytes = mmInStream.read(buffer);
byte[] outBuf = new byte[bytes];
System.arraycopy(buffer,0,outBuf,0,bytes);
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, outBuf)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
@Override
protected void onDestroy() {
super.onDestroy();
connectedThread.cancel();
connectThread.cancel();
}
}
以上代码已经可以完成双方数据的收发,接着可以学习其他实用功能:
- 扫描发现已配对设备
//获取已经配对的设备
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
Log.i("已经配对的设备:",device.getName() + "|" + device.getAddress());
}
}
- 扫描发现可配对设备(主要就是保存可配对设备的mac值,通过mac值进行配对连接)
调用方法mBluetoothAdapter.startDiscovery();开始扫描设备。这里需要声明权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
扫描获取到设备是异步的,所以用广播的方式。扫描的结果需要用广播接收:
//扫描获取设备的广播接收器
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.i("扫描到新设备:",device.getName() + "|" + device.getAddress());
}
}
};
//广播过滤并注册
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
扫描并配对连接的动作通常由客户端操作,优化客户端的代码,最终的效果如图: