概述
工作需要用到Android蓝牙开发,所以在这里对Android蓝牙开发做一个整理。
先了解下Android蓝牙开发的基础知识:官方文档戳这里
我们需要知道,现在的蓝牙分为经典蓝牙和BLE(低功耗蓝牙)两种,经典蓝牙和低功耗蓝牙的区别戳这里 ,经典蓝牙适合传输数据量比较大的传输,比如图像、视频、音乐等,耗电也大;BLE适合耗电小,实时性要求高和数据量小的传输,比如智能穿戴设备、遥控类、鼠标、键盘、传感设备如心跳带,血压计,温度传感器等。
对于Android开发者来说,我们要知道 Android 4.3 及以上版本才支持BLE,常说的蓝牙单模和双模指的是仅支持BLE和同时支持BLE和经典蓝牙,经典蓝牙和BLE之间不能通信,Android手机同时支持经典蓝牙和BLE,但是扫描蓝牙的时候,只能扫描其中一种,如果是Android手机跟其他设备通过蓝牙通信,首先要确认设备支持的蓝牙协议。下面记录的是经典蓝牙开发步骤。
蓝牙开发步骤
通常Android蓝牙开发包含以下5个步骤:
- 开启
- 扫描
- 配对
- 连接
- 通信
开启蓝牙
- 权限(需要蓝牙和GPS权限,Android 6.0以上要加上运行时权限授权)
在 AndroidManifest 中声明权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
获取定位授权:
//要模糊定位权限才能搜索到蓝牙
PermissionUtil.requestEach(this, new PermissionUtil.OnPermissionListener() {
@Override
public void onSucceed() {
//授权成功后打开蓝牙
openBlueTooth();
}
@Override
public void onFailed(boolean showAgain) {
}
}, PermissionUtil.LOCATION);
- 判断设备是否支持蓝牙
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
//如果mBluetoothAdapter为null,该设备不支持蓝牙,不过基本上都支持的
}
- 判断蓝牙是否开启,没有就开启
if (!mBluetoothAdapter.isEnabled()) {
//若没打开则打开蓝牙
mBluetoothAdapter.enable();
}
扫描蓝牙
- 可以扫描有指定 UUID 服务的蓝牙设备,UUID 不是设备的标识,而是某个服务的标识, 什么是UUID戳这里
- 可以扫描全部蓝牙设备
- 注意:蓝牙设备被某设备(包括当前的设备)配对/连接后,可能不会再被扫描到
//扫描:经典蓝牙
mBluetoothAdapter.startDiscovery();
//扫描:低功耗蓝牙,需要加上停止扫描规则,扫描到指定设备或者一段时间后,这里设置10秒后停止扫描
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mBluetoothAdapter.stopLeScan(MainActivity.this);
Log.i(TAG, "=== 停止扫描了 === ");
}
}, SCAN_PERIOD);
mBluetoothAdapter.startLeScan(this);
通过广播来监听扫描结果:
//广播接收器
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
//扫描开始
break;
case BluetoothDevice.ACTION_FOUND:
//发现蓝牙
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
//扫描结束
break;
}
}
};
配对蓝牙
- 配对与连接之间的区别:配对意味着两个设备之间知道彼此的存在,通过配对密钥,建立一个加密的连接。而连接意味着设备之间共享一个通信通道(UUID),能够彼此传输数据
/**
* 蓝牙配对,配对结果通过广播返回
* @param device
*/
public void pin(BluetoothDevice device) {
if (device == null || !mBluetoothAdapter.isEnabled()) {
return;
}
//配对之前把扫描关闭
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
//判断设备是否配对,没有就进行配对
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
try {
Method createBondMethod = device.getClass().getMethod("createBond");
Boolean returnValue = (Boolean) createBondMethod.invoke(device);
returnValue.booleanValue();
} catch (Exception e) {
e.printStackTrace();
}
}
}
广播监听配对结果:
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
//配对状态变化广播
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice
.EXTRA_DEVICE);
switch (device.getBondState()) {
case BluetoothDevice.BOND_NONE:
Log.i(TAG, "--- 配对失败 ---");
break;
case BluetoothDevice.BOND_BONDING:
Log.i(TAG, "--- 配对中... ---");
break;
case BluetoothDevice.BOND_BONDED:
Log.i(TAG, "--- 配对成功 ---");
break;
}
break;
连接蓝牙
- 配对成功之后,两台设备之间建立连接,一台充当client的角色,另一台充当server的角色,由client发起连接;
- 通过 UUID 创建 BluetoothSocket 进行连接,两个端的UUID要一致,client和server都自己开发的话,可以由服务端创建一个UUID
- 发起连接和监听连接都要在子线程中执行
在client端的子线程中发起连接:
/**
*
* 发起蓝牙连接的线程
* 作者: 代码来自于Google官方 -> API指南 -> 蓝牙模块
* 日期: 18/12/14
*/
public class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private final BluetoothAdapter mBluetoothAdapter;
private ConnectCallBack callBack;
public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, ConnectCallBack callBack) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
mBluetoothAdapter = bluetoothAdapter;
this.callBack = callBack;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(ServerActivity.uuidStr));
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
// manageConnectedSocket(mmSocket); //启动数据传输的线程
if(callBack != null) {
callBack.onConnectSucceed(mmSocket);
}
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
public interface ConnectCallBack {
public void onConnectSucceed(BluetoothSocket serverSocket);
}
}
在server端的子线程中监听连接:
/**
*
* 监听蓝牙连接线程
* 作者: 代码来自于Google官方 -> API指南 -> 蓝牙模块
* 日期: 18/12/14
*/
public class AcceptThread extends Thread {
private static final String TAG = "BluetoothDemo";
private final BluetoothServerSocket mmServerSocket;
private AcceptCallBack callBack;
public AcceptThread(BluetoothAdapter bluetoothAdapter, AcceptCallBack callBack) {
this.callBack = callBack;
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code
tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord("bluetoothdemo", UUID
.fromString(ServerActivity.uuidStr));
} catch (IOException e) {
}
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
Log.i(TAG, "AcceptThread监听中...");
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
try {
// Do work to manage the connection (in a separate thread)
// manageConnectedSocket(socket); //启动数据传输的线程
if(callBack != null) {
callBack.onAcceptSucceed(socket);
}
Log.i(TAG, "AcceptThread连接成功");
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) {
}
}
public interface AcceptCallBack {
public void onAcceptSucceed(BluetoothSocket serverSocket);
}
}
通信
- 连接成功后,通过socket得到I/O流,读取数据和写入数据,在两个设备之间传输数据。
/**
* 发送和接收数据
* 作者: 代码来自于Google官方 -> API指南 -> 蓝牙模块
* 日期: 18/12/14
*/
public class ConnectedThread extends Thread {
private static final String TAG = "ConnectedThread";
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private Handler mHandler;
public ConnectedThread(BluetoothSocket socket, Handler handler) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
mHandler = handler;
// 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;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
if(mHandler != null) {
mHandler.obtainMessage(ServerActivity.MESSAGE_READ, bytes, -1, buffer)
.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) { }
}
}
ConnectThread、AcceptThread 和 ConnectedThread 都是Google官方文档里面的示例,我自己加了些回调方法而已,如果觉得比较乱,可以直接去看官方文档。官方文档戳这里
代码上传到了GitHub, 功能比较简单,两台手机一个充当client,一个充当server,严格按照 开启 >> 扫描 >> 配对 >> 连接 >> 通信
5个步骤走才能实现通信。仅供参考。