本文主要记录下项目开发过程中的蓝牙功能
其中开发流程部分主要参考博文Android蓝牙开发—经典蓝牙详细开发流程
- 开启蓝牙
- 扫描蓝牙
- 配对蓝牙
- 连接蓝牙
- 状态监听
- 通信
- 获取BluetoothAdapter对象
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- 判断当前设备是否支持蓝牙
public boolean isSupportBlue() {
return mBluetoothAdapter != null;
}
- 判断蓝牙是否开启
public boolean isBlueEnable() {
return isSupportBlue() && mBluetoothAdapter.isEnabled();
}
- 开启蓝牙
- 异步自动开启蓝牙(需通过广播监听结果)
public void openBlueAsync() {
if (isSupportBlue()) {
mBluetoothAdapter.enable();
}
}
- 同步提示开启蓝牙
public void openBlueSync(Activity activity, int requestCode) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(intent, requestCode);
}
- 蓝牙权限
-
Android6.0以下权限处理
在AndroidManifest里面添加以下权限
-
Android6.0以上权限处理
在AndroidManifest里面添加以下权限
-
备注
Android6.0(API 级别 23)以上没有定位权限会导致无法扫描到周边设备,如果您的应用适配 Android 9.0(API 级别 28)或更低版本,则您可以声明 ACCESS_COARSE_LOCATION 权限而非 ACCESS_FINE_LOCATION 权限。Android12.0(API 级别 31)以后扫描设备不再需要定位权限。(定位权限需动态申请)
- 获取设备已经配对的蓝牙对象(在执行设备发现之前,您必须查询已配对的设备集,以了解所需的设备是否处于已检测到状态。)
public ListgetBondedDevices() { if (!isBlueEnable()) { return null; } return new ArrayList<>(mBluetoothAdapter.getBondedDevices()); }
- 扫描周围蓝牙设备(配对上的设备有可能扫描不出来)
public boolean scanBlue() {
if (!isBlueEnable()) {
Log.e(TAG, "Bluetooth not enable!");
return false;
}
//当前是否在扫描,如果是就取消当前的扫描,重新扫描
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
//这个方法是异步操作,一般耗时12秒
return mBluetoothAdapter.startDiscovery();
}
- 取消扫描蓝牙
public boolean cancelScanBlue() {
if (isBlueEnable()) {
return mBluetoothAdapter.cancelDiscovery();
}
return true;
}
- 通过广播方式得到扫描结果
- 创建扫描结果接口
public interface ScanBlueCallBack {
void onScanStarted();
void onScanFinished();
void onScanning(BluetoothDevice bluetoothDevice);
}
- 创建扫描广播接收类
public class ScanBlueReceiver extends BroadcastReceiver {
private static final String TAG = ScanBlueReceiver.class.getSimpleName();
private ScanBlueCallBack callBack;
public ScanBlueReceiver(ScanBlueCallBack callBack) {
this.callBack = callBack;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d(TAG, "action:" + action);
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (action) {
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
Log.d(TAG, "开始扫描...");
callBack.onScanStarted();
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
Log.d(TAG, "结束扫描...");
callBack.onScanFinished();
break;
case BluetoothDevice.ACTION_FOUND:
Log.d(TAG, "发现设备...");
callBack.onScanning(device);
break;
}
}
}
- 注册广播(界面结束时需解注册)
IntentFilter intentFilter = new IntentFilter(); //开始扫描设备 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); //结束扫描设备 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); //发现蓝牙设备 intentFilter.addAction(BluetoothDevice.ACTION_FOUND); registerReceiver(mScanBlueReceiver, intentFilter);配对蓝牙
- 开始配对
public void pin(BluetoothDevice device) {
if (device == null) {
Log.e(TAG, "bond device null!");
return;
}
if (!isBlueEnable()) {
Log.e(TAG, "Bluetooth not enable!");
return;
}
//配对前先将扫描关闭
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
//判断设备是否配对,没有配对再配,配对了就不需做操作了
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
Log.d(TAG, "attempts to bond:" + device.getName());
try {
Method createBondMethod = device.getClass().getMethod("createBond");
Boolean returnValue = (Boolean) createBondMethod.invoke(device);
returnValue.booleanValue();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "attempts to bond fail!");
}
}
}
- 取消配对
public void cancelPinBlue(BluetoothDevice device) {
if (device == null) {
Log.e(TAG, "cancel bond device null!");
return;
}
if (!isBlueEnable()) {
Log.e(TAG, "Bluetooth not enable!");
return;
}
//判断设备是否配对,没有配对就不操作
if (device.getBondState() != BluetoothDevice.BOND_NONE) {
Log.e(TAG, "attempts to cancel bond:" + device.getName());
try {
Method removeBondMethod = device.getClass().getMethod("removeBond");
Boolean returnValue = (Boolean) removeBondMethod.invoke(device);
returnValue.booleanValue();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "attempts to cancel bond fail!");
}
}
}
- 通过广播方式得到配对结果
- 创建配对结果接口
public interface PinBlueCallBack {
default void onBondRequest() {
}
void onBondFail(BluetoothDevice device);
default void onBonding(BluetoothDevice device) {
}
void onBondSuccess(BluetoothDevice device);
}
- 创建配对广播接收类
public class PinBlueReceiver extends BroadcastReceiver {
private static final String TAG = PinBlueReceiver.class.getSimpleName();
private final String pin = "1234";
private PinBlueCallBack callBack;
public PinBlueReceiver(PinBlueCallBack callBack) {
this.callBack = callBack;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e(TAG, "action:" + action);
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) {
try {
callBack.onBondRequest();
//1.确认配对
Method setPairingConfirmation = device.getClass().getDeclaredMethod("setPairingConfirmation", boolean.class);
setPairingConfirmation.invoke(device, true);
//2.终止有序广播
abortBroadcast();//如果没有将广播终止,则会出现一个一闪而过的配对框。
//3.调----用setPin方法进行配对...
Method removeBondMethod = device.getClass().getDeclaredMethod("setPin", new Class[]{byte[].class});
Boolean returnValue = (Boolean) removeBondMethod.invoke(device, new Object[]{pin.getBytes()});
} catch (Exception e) {
e.printStackTrace();
}
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
switch (device.getBondState()) {
case BluetoothDevice.BOND_BONDING:
Log.d(TAG, "配对中!");
callBack.onBonding(device);
break;
case BluetoothDevice.BOND_BONDED:
Log.d(TAG, "配对成功!");
callBack.onBondSuccess(device);
break;
case BluetoothDevice.BOND_NONE:
Log.d(TAG, "配对失败!");
callBack.onBondFail(device);
break;
}
}
}
}
- 注册广播(界面结束时需解注册)
IntentFilter pinFilter = new IntentFilter(); //配对请求 pinFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); //配对状态变化 pinFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); registerReceiver(mPinBlueReceiver, pinFilter);连接蓝牙
经典蓝牙连接相当于socket连接,是个非常耗时的操作,所以应在Activity(界面)以外的线程中执行连接步骤。
- 创建蓝牙连接接口
public interface ConnectBlueCallBack {
default void onStartConnect() {
}
void onConnectSuccess(BluetoothDevice device, BluetoothSocket socket);
void onConnectFail(BluetoothDevice device, String hint);
}
- 创建连接线程
public class ConnectBlueTask extends AsyncTask{ private static final String TAG = ConnectBlueTask.class.getSimpleName(); private BluetoothDevice bluetoothDevice; private final ConnectBlueCallBack callBack; public ConnectBlueTask(ConnectBlueCallBack callBack) { this.callBack = callBack; } @Override protected BluetoothSocket doInBackground(BluetoothDevice... bluetoothDevices) { bluetoothDevice = bluetoothDevices[0]; BluetoothSocket socket = null; try { Log.e(TAG, "开始连接!"); socket = bluetoothDevice.createRfcommSocketToServiceRecord(BluetoothUtils.MY_UUID_SECURE); if (socket != null && !socket.isConnected()) { socket.connect(); } } catch (IOException e) { try { if (socket != null) { socket.close(); } } catch (IOException e1) { e1.printStackTrace(); } } return socket; } @Override protected void onPreExecute() { Log.e(TAG, "开始连接!"); if (callBack != null) { callBack.onStartConnect(); } } @Override protected void onPostExecute(BluetoothSocket socket) { if (socket != null && socket.isConnected()) { Log.e(TAG, "连接成功!"); if (callBack != null) { callBack.onConnectSuccess(bluetoothDevice, socket); } } else { Log.e(TAG, "连接失败!"); if (callBack != null) { callBack.onConnectFail(bluetoothDevice, "连接失败"); } } } }
- 启动连接线程
public void connect(BluetoothDevice device, ConnectBlueCallBack callBack) {
if (device == null) {
Log.e(TAG, "bond device null!");
return;
}
if (!isBlueEnable()) {
Log.e(TAG, "Bluetooth not enable!");
return;
}
//连接之前把扫描关闭
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
new ConnectBlueTask(callBack).execute(device);
}
- 判断是否连接成功
public boolean isConnectBlue(BluetoothSocket socket) {
return socket != null && socket.isConnected();
}
- 断开连接
public boolean cancelConnect(BluetoothSocket socket) {
if (socket != null && socket.isConnected()) {
try {
socket.getInputStream().close();
socket.getOutputStream().close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return true;
}
- 根据设备MAC地址进行连接
public void connectMAC(String address, ConnectBlueCallBack callBack) {
if (!isBlueEnable()) {
return;
}
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
connect(device, callBack);
}
状态监听
在蓝牙设备建立连接之后,因操作、距离、设备原因需要监听蓝牙间连接状态,以便进行相应的操作处理。
- 创建蓝牙状态监测接口
public interface BluetoothMonitorCallBack {
default void onOpening() {
}
default void onOpened() {
}
default void onClosing() {
}
default void onClosed() {
}
default void onConnected() {
}
default void onDisconnected() {
}
}
- 创建蓝牙状态监测广播
public class BluetoothMonitorReceiver extends BroadcastReceiver {
private final String TAG = BluetoothMonitorReceiver.class.getSimpleName();
private BluetoothMonitorCallBack callBack;
public BluetoothMonitorReceiver(BluetoothMonitorCallBack callBack) {
this.callBack = callBack;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (null != action) {
switch (action) {
case BluetoothAdapter.ACTION_STATE_CHANGED:
int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
switch (blueState) {
case BluetoothAdapter.STATE_TURNING_ON:
Log.e(TAG, "蓝牙正在打开!");
callBack.onOpening();
break;
case BluetoothAdapter.STATE_ON:
Log.e(TAG, "蓝牙已打开!");
callBack.onOpened();
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.e(TAG, "蓝牙正在关闭!");
callBack.onClosing();
break;
case BluetoothAdapter.STATE_OFF:
Log.e(TAG, "蓝牙已关闭!");
callBack.onClosed();
break;
}
break;
case BluetoothDevice.ACTION_ACL_CONNECTED:
Log.e(TAG, "蓝牙设备已连接!");
callBack.onConnected();
break;
case BluetoothDevice.ACTION_ACL_DISCONNECTED:
Log.e(TAG, "蓝牙设备已断开!");
callBack.onDisconnected();
break;
}
}
}
}
- 注册广播(界面结束时需要解注册)
IntentFilter intentFilter = new IntentFilter(); // 监视蓝牙关闭和打开的状态 intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); // 监视蓝牙设备与APP连接的状态 intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); registerReceiver(this.mBluetoothMonitorReceiver, intentFilter);通信
- 方式一
- 创建读取数据接口
public interface ReadCallBack {
default void onStarted() {
}
void onFinished(boolean isSuccess, String content);
}
- 创建读取数据线程
public class ReadTask extends AsyncTask{ private static final String TAG = ReadTask.class.getSimpleName(); private ReadCallBack callBack; private BluetoothSocket socket; public ReadTask(ReadCallBack callBack, BluetoothSocket socket) { this.callBack = callBack; this.socket = socket; } @Override protected String doInBackground(String... strings) { BufferedInputStream inputStream = null; try { StringBuilder stringBuffer = new StringBuilder(); inputStream = new BufferedInputStream(socket.getInputStream()); int length = 0; byte[] buf = new byte[1024]; while ((length = inputStream.read()) != -1) { stringBuffer.append(new String(buf, 0, length)); } return stringBuffer.toString(); } catch (IOException e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return "读取失败!"; } @Override protected void onPreExecute() { Log.e(TAG, "开始读取数据!"); if (callBack != null) { callBack.onStarted(); } } @Override protected void onPostExecute(String s) { Log.e(TAG, "完成读取数据!"); if ("读取失败!".equals(s)) { callBack.onFinished(false, s); } else { callBack.onFinished(true, s); } } }
- 创建写入数据接口
public interface WriteCallBack {
default void onStarted() {
}
void onFinished(boolean isSuccess, String hint);
}
- 创建写入数据线程
public class WriteTask extends AsyncTask{ private static final String TAG = WriteTask.class.getSimpleName(); private WriteCallBack callBack; private BluetoothSocket socket; public WriteTask(WriteCallBack callBack, BluetoothSocket socket) { this.callBack = callBack; this.socket = socket; } @Override protected String doInBackground(String... strings) { String string = strings[0]; OutputStream outputStream = null; try { outputStream = socket.getOutputStream(); outputStream.write(string.getBytes()); } catch (IOException e) { e.printStackTrace(); return "发送失败!"; } finally { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return "发送成功!"; } @Override protected void onPreExecute() { Log.e(TAG, "开始写入数据!"); if (callBack != null) { callBack.onStarted(); } } @Override protected void onPostExecute(String s) { Log.e(TAG, "完成写入数据!"); if (callBack != null) { if ("发送成功!".equals(s)) { callBack.onFinished(true, s); } else { callBack.onFinished(false, s); } } } }
- 方式二(因项目功能需要而编写)
- 创建连接操作接口
public interface ConnectedOperationCallBack {
default void onReadSuccess(String content) {
}
default void onReadFile() {
}
default void onWriteSuccess() {
}
default void onWriteFile() {
}
}
- 创建连接线程
public class ConnectedThread extends Thread {
private final BluetoothSocket mBluetoothSocket;
private final InputStream mInputStream;
private final OutputStream mOutputStream;
private ConnectedOperationCallBack mOperationCallBack;
private boolean isRead = true;
public ConnectedThread(BluetoothSocket bluetoothSocket, ConnectedOperationCallBack callBack) {
this.mBluetoothSocket = bluetoothSocket;
this.mOperationCallBack = callBack;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
tmpIn = mBluetoothSocket.getInputStream();
tmpOut = mBluetoothSocket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
mInputStream = tmpIn;
mOutputStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024];
int bytes = 0;
//监听输入流以备获取数据
while (isRead) {
try {
bytes = mInputStream.read(buffer);
if (bytes != -1) {
String string = new String(buffer, 0, bytes, "utf-8");
if (null != mOperationCallBack) {
mOperationCallBack.onReadSuccess(string.substring(0, 7));
}
}
} catch (IOException e) {
e.printStackTrace();
if (null != mOperationCallBack) {
mOperationCallBack.onReadFile();
}
}
try {
//线程睡眠20ms以避免过于频繁工作 50ms->20ms 2017.12.2
//导致UI处理发回的数据不及时而阻塞
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void write(byte[] buffer) {
try {
mOutputStream.write(buffer);
if (null != mOperationCallBack) {
mOperationCallBack.onWriteSuccess();
}
} catch (IOException e) {
e.printStackTrace();
if (null != mOperationCallBack) {
mOperationCallBack.onWriteFile();
}
}
}
public void cancel() {
isRead = false;
try {
mInputStream.close();
mOutputStream.close();
mBluetoothSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BluetoothSocket对象
问题
在项目中需要连接蓝牙电子秤进行重量获取,通过在“我的”模块进行蓝牙扫描、配对与连接,之后在订单模块结算时进行重量获取。此时就需要通过BluetoothSocket的getInputStream方法与getOutputStream进行数据获取处理。
分析不同模块的Activity下都需要BluetoothSocket对象,就需要BluetoothSocket在不同模块中传递。
在Activity中传输数据有几种方法:
- Intent.putExtra()
- 实现Serializable
- 实现Parcelable
但是上述方法都不适合
因为BluetoothSocket类被final关键字修饰,使得该类无法被继承,同时也没有实现Serializable与Parcelable。所以无法在Activity之间传递BluetoothSocket对象。
既然BluetoothSocket无法被传递,同时在设备连接后BluetoothSocket就需要一直存在(除主动断开与关闭APP),那就将BluetoothSocket设置为全局对象。在App的Application中声明BluetoothSocket,使App下的Activity都能使用。
- 创建一个Application对象
public class BaseApplication extends Application {
private BluetoothSocket mBluetoothSocket;
@Override
public void onCreate() {
super.onCreate();
}
public BluetoothSocket getBluetoothSocket() {
return mBluetoothSocket;
}
public void setBluetoothSocket(BluetoothSocket bluetoothSocket) {
this.mBluetoothSocket = bluetoothSocket;
}
}
- 在AndroidManidest中进行注册
- 连接成功后将BluetoothSocket传入到Application中
mApplication= (BaseApplication) getApplication(); mApplication.setBluetoothSocket(socket);
- 在使用时获取Application的mBluetoothSocket
mApplication= (BaseApplication) getApplication(); mBluetoothSocket= mApplication.getBluetoothSocket());总结
通过全局变量可以实现多模块/多Activity之间对BluetoothSocket的赋值、获取与使用,保证了对象的唯一性,同时因为Application生命周期较长,对象占用资源的时间也长,所以应当根据功能需求来决定具体实现方案。
拓展 获取设备类型通过扫描我们可以得到BluetoothDevice对象,其中BluetoothDevice.getType()方法,可以获取到远程设备的蓝牙设备类型,其中包括:
- DEVICE_TYPE_UNKNOWN(未知类型)
- DEVICE_TYPE_CLASSIC(传统类型)
- DEVICE_TYPE_LE(BLE类型)
- DEVICE_TYPE_DUAL(双模式类型传统类型与BLE类型)
上述类型并不满足项目需求,我们需要的是更加详细的设备类型,如游戏手柄、打印机、蓝牙耳机等,具体实现效果需要与系统蓝牙一致。
想要获取到详细的设备类型可以通过BluetoothDevice的getBluetoothClass方法来获取到蓝牙类,通过BluetoothClass的getMajorDeviceClass方法获得主要设备类型,通过BluetoothClass的getDeviceClass方法获得主要和次要设备类型。
//获得蓝牙主要设备类型 BluetoothDevice.getBluetoothClass().getMajorDeviceClass(); //获得蓝牙主要和次要设备类型 BluetoothDevice.getBluetoothClass().getDeviceClass();
其中主要类型如下类型(需要次要设备类型请参考BluetoothClass类)
public static class Major {
private static final int BITMASK = 0x1F00;
public static final int MISC = 0x0000;
public static final int COMPUTER = 0x0100;
public static final int PHONE = 0x0200;
public static final int NETWORKING = 0x0300;
public static final int AUDIO_VIDEO = 0x0400;
public static final int PERIPHERAL = 0x0500;
public static final int IMAGING = 0x0600;
public static final int WEARABLE = 0x0700;
public static final int TOY = 0x0800;
public static final int HEALTH = 0x0900;
public static final int UNCATEGORIZED = 0x1F00;
}
创建蓝牙类型工具类,根据类型返回对应设备的图标(如果需要更详细的划分可以自己根据需求实现)
public class BluetoothTypeUtils {
public static int getDeviceType(BluetoothClass bluetoothClass) {
if (null == bluetoothClass) {
return R.mipmap.icon_bluetooth;
}
switch (bluetoothClass.getMajorDeviceClass()) {
case BluetoothClass.Device.Major.PHONE:
return R.mipmap.icon_phone;
case BluetoothClass.Device.Major.COMPUTER:
return R.mipmap.icon_computer;
case BluetoothClass.Device.Major.PERIPHERAL:
return R.mipmap.icon_printer;
case BluetoothClass.Device.Major.AUDIO_VIDEO:
return R.mipmap.icon_earphone;
default:
return R.mipmap.icon_bluetooth;
}
}
}
实现效果
后续将会提供Demo连接,如有错误,欢迎指正!



