Android 蓝牙监听与扫描

基础知识

蓝牙操作主要有四项任务:设置蓝牙、查找局部区域内的配对设备或可用设备、连接设备,以及在设备间传输数据。

蓝牙的分类

传统蓝牙(Classic Bluetooth)
  • 电池使用强度大
  • 可用于数据量较大的传输,如语音,音乐,较高数据量传输等
  • 广泛用于音箱,耳机,汽车电子及传统数传行业
低功耗蓝牙(Bluetooth LE)
  • 功耗低
  • 不支持音频协议,传输速率较低
  • 主要用于移动互联和健康医疗,如鼠标,键盘,遥控鼠标(Air Mouse),传感设备的数据发送,如心跳带,血压计,温度传感器,体重秤,健康手环等。
双模蓝牙
  • 同时支持传统蓝牙和低功耗蓝牙模组

使用方法可以参考以下官方文档:

传统蓝牙

低功耗蓝牙

以及简书:

android蓝牙BLE扫描实现方法

对于蓝牙扫描的说明

Android中两种蓝牙API的选择

传统蓝牙的电池使用强度较大,Android 4.3(API 18)中引入了面向低功耗蓝牙(BLe)的API支持。但这并不是说,4.3以上的设备就一定搭载了低功耗蓝牙。反而是更多地搭载“经典蓝牙”或“双模蓝牙”,毕竟要传输音频。

从测试结果来看,传统蓝牙API 可以同时扫描出 传统蓝牙低功耗蓝牙 ,而低功耗蓝牙API 则只能用于扫描 低功耗蓝牙

所以,千万别以为4.3以上的设备就应该用BLE API开发蓝牙功能,除非你的业务需求是针对BLE设备的,如果你需要扫描车载蓝牙或各种使用蓝牙连接的外设,那么建议使用传统蓝牙API,不然基本上扫不到设备。

功能讲解

查找已配对设备列表

权限
  • BLUETOOTH(普通权限)
  • ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION(危险权限)

注:对于定位权限的依赖区分系统版本,较老版本(大概是6.0以前)中不需要定位权限,新版本(大概是6.0至9.0)需要任意一个定位权限,而从Q开始,必须拥有精确定位权限(具体的版本界限需要测试得出)。

其他要求
  • 必须开启蓝牙

注:如果设备已开启蓝牙,可以静默获取数据,但若是要通过代码开启蓝牙,则需要 BLUETOOTH_ADMIN 权限,系统会在执行 开启蓝牙 操作时,向用户显示一个弹框,等待用户授权。

可获取的数据
来自BluetoothDevice对象的数据
  • 蓝牙名称
  • 蓝牙硬件地址
  • 绑定状态
  • 蓝牙类型
  • uuids
  • 该蓝牙所属设备类型大分类(详见【蓝牙相关字段说明】)
  • 该蓝牙所属设备类型小分类(详见【蓝牙相关字段说明】)
获取方法

蓝牙开启的情况下,同步获取

核心代码
	public static BluetoothAdapter getBAdapter(Context context) {
		BluetoothAdapter mBluetoothAdapter = null;
		try {
			if (Build.VERSION.SDK_INT >= 18) {
				BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
				mBluetoothAdapter = manager.getAdapter();

			} else {
				mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
			}
		} catch (Throwable t) {
			t.printStackTrace();
		}
		return mBluetoothAdapter;
	}
	
	/**
	 * 查询已配对的蓝牙设备
	 *
	 * @param mBluetoothAdapter
	 */
	public static ArrayList<HashMap<String, Object>> getBondedDevice(BluetoothAdapter mBluetoothAdapter) {
		ArrayList<HashMap<String, Object>> result = new ArrayList<>();
		try {
			if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
				Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
				// If there are paired devices
				if (pairedDevices.size() > 0) {
					for (BluetoothDevice device : pairedDevices) {
						HashMap<String, Object> deviceInfo = parseBtDevice2Map(device);
						deviceInfo.put("__currConnected", (isConnectedBtDevice(device) ? 1 : 0));
						result.add(deviceInfo);
					}
				}
			}
		} catch (Throwable t) {
			t.printStackTrace();
		}
		return result;
	}

        @SuppressLint("MissingPermission")
	private static HashMap<String, Object> parseDevice2Map(BluetoothDevice device) {
		HashMap<String, Object> map = new HashMap<>();
		if (device != null) {
			try {
				map.put("name", device.getName());
				map.put("address", device.getAddress());
				map.put("bondState", device.getBondState());

				BluetoothClass btClass = device.getBluetoothClass();
				int majorClass = btClass.getMajorDeviceClass();
				int deviceClass = btClass.getDeviceClass();
				map.put("majorClass", majorClass);
				map.put("deviceClass", deviceClass);

				if (Build.VERSION.SDK_INT >= 18) {
					map.put("type", device.getType());
				}
				// 已配对的设备,同时获取其uuids
				if (Build.VERSION.SDK_INT >= 15 && device.getBondState() == 12) {
					ArrayList<String> uuids = new ArrayList<>();
					ParcelUuid[] parcelUuids = device.getUuids();
					if (parcelUuids != null && parcelUuids.length > 0) {
						for (ParcelUuid parcelUuid : parcelUuids) {
							if (parcelUuid != null && parcelUuid.getUuid() != null) {
								uuids.add(parcelUuid.getUuid().toString());
							}
						}
					}
					map.put("uuids", uuids);
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}
		return map;
	}

获取当前连接的设备

方法一

先获取已匹配设备列表,再利用API返回的BluetoothDevice对象,反射调用其中的 isConnected 实例方法。

核心代码:

	public static boolean isConnectedDevice(BluetoothDevice device) {
		boolean isConnected = false;
		if (device != null) {
			try {
				if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
                                        // isConnected方法只能反射调用
					Boolean result = ReflectUtils.invokeInstanceMethod(device, "isConnected");
					if (result != null) {
						isConnected = result.booleanValue();
					}
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}
		return isConnected;
	}
方法二

广播监听,向系统注册 BluetoothDevice.ACTION_ACL_CONNECTED 广播,可以监听蓝牙状态,每次有远程设备连接至本机时,会收到广播。不过该方式只能监听广播注册之后的蓝牙连接,注册之前已经连接的设备当然获取不到。

使用时需要注意:

  1. 用完后别忘了注销广播接收器。
  2. 广播接收器的 onReceive() 方法是在S 主线程 触发的,所以不要在其中处理耗时操作,如果使用了callback返回蓝牙操作的相关结果给外界,那么在callback中同样不能做耗时操作。

核心代码:

/**
 * 注册广播接收器,用于接收蓝牙相关操作的结果
 */
public static void registerBOperationReceiver() {
	if (btOperationReceiver == null) {
		try {
			btOperationReceiver = new BroadcastReceiver() {
				@Override
				public void onReceive(Context context, Intent intent) {
					try {
						String action = intent.getAction();
						// 蓝牙开关状态变化
						if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
							//获取蓝牙广播中的蓝牙新状态
							int blueNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
							//获取蓝牙广播中的蓝牙旧状态
							int blueOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, 0);
							switch (blueNewState) {
								//正在打开蓝牙
								case BluetoothAdapter.STATE_TURNING_ON: {
									Toast.makeText(context, "STATE_TURNING_ON", Toast.LENGTH_SHORT).show();
									break;
								}
								//蓝牙已打开
								case BluetoothAdapter.STATE_ON: {
									Toast.makeText(context, "STATE_ON", Toast.LENGTH_SHORT).show();
									break;
								}
								//正在关闭蓝牙
								case BluetoothAdapter.STATE_TURNING_OFF: {
									Toast.makeText(context, "STATE_TURNING_OFF", Toast.LENGTH_SHORT).show();
									break;
								}
								//蓝牙已关闭
								case BluetoothAdapter.STATE_OFF: {
									Toast.makeText(context, "STATE_OFF", Toast.LENGTH_SHORT).show();
									break;
								}
							}
						}
						/*
						 * 本机的蓝牙连接状态发生变化
						 *
						 * 特指“无任何连接”→“连接任意远程设备”,以及“连接任一或多个远程设备”→“无任何连接”的状态变化,
						 * 即“连接第一个远程设备”与“断开最后一个远程设备”时才会触发该Action
						 */
						else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
							//获取蓝牙广播中的蓝牙连接新状态
							int newConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0);
							//获取蓝牙广播中的蓝牙连接旧状态
							int oldConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, 0);
							// 当前远程蓝牙设备
							BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
							HashMap<String, Object> map = parseBtDevice2Map(device);
							switch (newConnState) {
								//蓝牙连接中
								case BluetoothAdapter.STATE_CONNECTING: {
									Log.d(TAG, "STATE_CONNECTING, " + map.get("name"));
									Toast.makeText(context, "STATE_CONNECTING", Toast.LENGTH_SHORT).show();
									break;
								}
								//蓝牙已连接
								case BluetoothAdapter.STATE_CONNECTED: {
									Log.d(TAG, "STATE_CONNECTED, " + map.get("name"));
									Toast.makeText(context, "STATE_CONNECTED", Toast.LENGTH_SHORT).show();
									break;
								}
								//蓝牙断开连接中
								case BluetoothAdapter.STATE_DISCONNECTING: {
									Log.d(TAG, "STATE_DISCONNECTING, " + map.get("name"));
									Toast.makeText(context, "STATE_DISCONNECTING", Toast.LENGTH_SHORT).show();
									break;
								}
								//蓝牙已断开连接
								case BluetoothAdapter.STATE_DISCONNECTED: {
									Log.d(TAG, "STATE_DISCONNECTED, " + map.get("name"));
									Toast.makeText(context, "STATE_DISCONNECTED", Toast.LENGTH_SHORT).show();
									break;
								}
							}
						}
						// 有远程设备成功连接至本机
						else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
							// 当前远程蓝牙设备
							BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
							HashMap<String, Object> map = parseBtDevice2Map(device);
							Log.d(TAG, "ACTION_ACL_CONNECTED, " + map.get("name"));
							Toast.makeText(context, "ACTION_ACL_CONNECTED", Toast.LENGTH_SHORT).show();
						}
						// 有远程设备断开连接
						else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
							// 当前远程蓝牙设备
							BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
							HashMap<String, Object> map = parseBtDevice2Map(device);
							Log.d(TAG, "ACTION_ACL_DISCONNECTED, " + map.get("name"));
							Toast.makeText(context, "ACTION_ACL_DISCONNECTED", Toast.LENGTH_SHORT).show();
						}
					} catch (Throwable t) {
						t.printStacktrace();
					}
				}
			};
		} catch (Throwable t) {
			t.printStacktrace();
		}
		IntentFilter filter = new IntentFilter();
		// 蓝牙开关状态
		filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
		// 本机的蓝牙连接状态发生变化(连接第一个远程设备与断开最后一个远程设备才触发)
		filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
		// 有远程设备成功连接至本机(每个远程设备都会触发)
		filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
		// 有远程设备断开连接(每个远程设备都会触发)
		filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
		try {
			context.registerReceiver(btOperationReceiver, filter);
		} catch (Throwable t) {}
	}
}

蓝牙状态广播可根据业务需求,选择使用以下ACTION:

  • BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED
    • 本机的蓝牙连接状态发生变化时触发。特指“无任何连接”→“连接任意远程设备”,以及“连接任一或多个远程设备”→“无任何连接”的状态变化,即“连接第一个远程设备”与“断开最后一个远程设备”时才会触发该Action。
  • BluetoothDevice.ACTION_ACL_CONNECTED / BluetoothDevice.ACTION_ACL_DISCONNECTED
    • 每个远程设备的连接与断开都会触发

发现设备

权限
  • BLUETOOTH(普通权限)
  • BLUETOOTH_ADMIN(用于扫描蓝牙,不需动态申请,不会弹框,除非执行“开启蓝牙”动作)
  • ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION(危险权限)

注:对于定位权限的依赖区分系统版本,较老版本(大概是6.0以前)中不需要定位权限,新版本(大概是6.0至9.0)需要任意一个定位权限,而从Q开始,必须拥有精确定位权限(具体的版本界限需要测试得出)。

其他要求
  • 必须开启蓝牙
  • 7.0 后不能在30秒内扫描和停止超过5次
    • 否则扫描不到结果,并收到 onScanFailed(int),返回错误码:ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app无法注册,无法开始扫描)。
可获取的数据
来自BluetoothDevice对象的数据
  • 蓝牙名称(传统设备、混合模式设备能拿到,低功耗设备基本拿不到)
  • 蓝牙硬件地址
  • 绑定状态
  • 蓝牙类型
  • uuids(通常拿不到,只有已配对的设备才能拿到)
  • 该蓝牙所属设备类型大分类(详见【蓝牙相关字段说明】)
  • 该蓝牙所属设备类型小分类(详见【蓝牙相关字段说明】)
蓝牙发现功能特有的数据
  • rssi:可理解成设备的信号值。该数值是一个负数,越大则信号越强(传统蓝牙API与BLE API均可获取)
  • scanRecord:远程设备提供的广播数据的内容,是一个二进制数组(BLE API特有)
获取方法
  • 异步获取
  • 扫描时长需要主动控制(不建议一次扫描太久,耗电)
  • 注意:已匹配的设备不会出现在“发现列表”中(测试发现匹配的iphone会出现在发现列表)
说明
  • 传统蓝牙API的扫描时长,系统默认是12秒左右,但可以主动停止,可以根据业务需求,设置一个扫描超时时间
传统蓝牙API实现扫描的核心代码
	/**
	 * 查找蓝牙,包括传统蓝牙和低功耗蓝牙
	 *
	 * 注:
	 * 1.该方式在查找低功耗蓝牙上效率较低
	 * 2.若只需要查找低功耗蓝牙,应该使用“低功耗蓝牙API”,即 findBluetoothLE() 方法
	 *
	 * @param scanInterval 扫描时长,单位:秒
	 * @param bluetoothAdapter
	 * @param btScanCallback 扫描结果回调
	 */
	public static void findBluetoothLEAndClassic(int scanInterval, final BluetoothAdapter bluetoothAdapter, final BtScanCallback btScanCallback) {
		try {
			if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")
			&& DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
				if (!bluetoothAdapter.isEnabled()) {
					// 若蓝牙未打开,直接返回
					btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				if (mScanning) {
					// 正在扫描中,直接返回
					btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				// 默认扫描6秒,若scanInterval不合法,则使用默认值
				final int defaultInterval = 6;
				if (scanInterval <= 0) {
					scanInterval = defaultInterval;
				}

				// 通过bluetoothAdapter.startDiscovery()实现的扫描,系统会在扫描结束(通常是12秒)后自动停止,
				// 而cancelDiscovery()可以提前终止扫描。 所以这里的控制逻辑,相当于设置一个最大时间,限制扫描不得超出这个时间,
				// 但是很可能提前完成扫描(比如scanInterval > 12秒)
				// 设置一段时间后停止扫描(以防系统未正常停止扫描)
				final Handler handler = HandlerThread.newHandler(new Handler.Callback() {
					@Override
					public boolean handleMessage(Message msg) {
						Log.d(TAG, "Cancel bluetooth scan");
						// 若已经停止扫描(系统扫描结束/通过cancelDiscovery取消扫描),则再次调用该方法不会触发ACTION_DISCOVERY_FINISHED
						bluetoothAdapter.cancelDiscovery();
						return false;
					}
				});
				handler.sendEmptyMessageDelayed(0, scanInterval * 1000);

				// 准备开始扫描
				final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<>();
				btScanReceiver = new BroadcastReceiver() {
					public void onReceive(Context context, Intent intent) {
						String action = intent.getAction();

						if (action.equals(BluetoothDevice.ACTION_FOUND)) { //found device
							BluetoothDevice device = intent
									.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
							HashMap<String, Object> map = parseBtDevice2Map(device);
							// 该extra取值与BluetoothDevice对象中getName()取值一致,因此不需要通过它获取name
//							String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
							short defaultValue = 0;
							short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, defaultValue);
							map.put("rssi", rssi);
							scanResult.add(map);
							Log.d(TAG, "onScanResult: " + device.getAddress() + ", " + device.getName());
						} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
							Log.d(TAG, "正在扫描");
						} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
							Log.d(TAG, "扫描完成");
							mScanning = false;
							btScanCallback.onScan(scanResult);
							// 若系统先扫描完,不需要再通过代码主动停止扫描
							handler.removeMessages(0);
							// 注销接收器
							unRegisterBtScanReceiver();
						}
					}
				};
				IntentFilter filter = new IntentFilter();
				// 用BroadcastReceiver来取得搜索结果
				filter.addAction(BluetoothDevice.ACTION_FOUND);
				filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
				// 两种情况会触发ACTION_DISCOVERY_FINISHED:1.系统结束扫描(约12秒);2.调用cancelDiscovery()方法主动结束扫描
				filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
				context.registerReceiver(btScanReceiver, filter);

				// 开始扫描
				mScanning = true;
				bluetoothAdapter.startDiscovery();
			} else {
				// 缺少权限,直接返回
				btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
			}
		} catch (Throwable t) {
			t.printStackTrace();
			btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
		}
	}

	public static void unRegisterBtScanReceiver() {
		if (btScanReceiver != null) {
			context.unregisterReceiver(btScanReceiver);
			btScanReceiver = null;
		}
	}
	
	public interface BtScanCallback {
		void onScan(ArrayList<HashMap<String, Object>> result);
	}
BLE API实现扫描的核心代码
	/**
	 * 查找低功耗蓝牙,该方法在4.3(API 18)以上,无法查找“传统蓝牙”
	 *
	 * @param scanInterval 扫描时长,单位:秒
	 * @param bluetoothAdapter
	 * @param btScanCallback 扫描结果回调
	 */
	public static void findBluetoothLE(int scanInterval, final BluetoothAdapter bluetoothAdapter, final BtScanCallback btScanCallback) {
		try {
			if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")
					&& DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
				if (!bluetoothAdapter.isEnabled()) {
					// 若蓝牙未打开,直接返回
					btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				if (mScanning) {
					// 正在扫描中,直接返回
					btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				// 默认扫描6秒,若scanInterval不合法,则使用默认值
				final int defaultInterval = 6;
				if (scanInterval <= 0) {
					scanInterval = defaultInterval;
				}
				// 4.3的低功耗蓝牙API
				if (Build.VERSION.SDK_INT >= 18) {
					final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<>();
					// 5.0又引入了新的蓝牙API(4.3版本的API仍然可用)
					if (Build.VERSION.SDK_INT < 21) {
						// 定义扫描结果回调
						final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
							/**
							 *
							 * @param device 扫描到的设备实例,可从实例中获取到相应的信息。如:名称,mac地址
							 * @param rssi 可理解成设备的信号值。该数值是一个负数,越大则信号越强
							 * @param scanRecord 远程设备提供的广播数据的内容
							 */
							@Override
							public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
								HashMap<String, Object> map = parseBtDevice2Map(device);
								map.put("rssi", rssi);
//						map.put("scanRecord", Data.byteToHex(scanRecord));
								scanResult.add(map);
							}
						};

						// 开始扫描
						mScanning = true;
						bluetoothAdapter.startLeScan(leScanCallback);

						// 设置一段时间后停止扫描
						Handler handler = HandlerThread.newHandler(new Handler.Callback() {
							@Override
							public boolean handleMessage(Message msg) {
								mScanning = false;
								bluetoothAdapter.stopLeScan(leScanCallback);
								btScanCallback.onScan(scanResult);
								return false;
							}
						});
						handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
					} else {
						// 定义扫描结果回调
						final ScanCallback mScanCallback = new ScanCallback() {
							//当一个蓝牙ble广播被发现时回调
							@Override
							public void onScanResult(int callbackType, ScanResult result) {
								Log.d(TAG, "onScanResult: " + result.getDevice().getAddress() + ", " + result.getDevice().getName());
								super.onScanResult(callbackType, result);
								//扫描类型有开始扫描时传入的ScanSettings相关
								//对扫描到的设备进行操作。如:获取设备信息。
								if (result != null) {
									HashMap<String, Object> map = new HashMap<>();
									BluetoothDevice device = result.getDevice();
									if (device != null) {
										map = parseBtDevice2Map(device);
									}
									map.put("rssi", result.getRssi());
									ScanRecord scanRecord = result.getScanRecord();
									scanResult.add(map);
								}
							}

							// 批量返回扫描结果。一般蓝牙设备对象都是通过onScanResult(int,ScanResult)返回,
							// 而不会在onBatchScanResults(List)方法中返回,除非手机支持批量扫描模式并且开启了批量扫描模式。
							// 批处理的开启请查看ScanSettings。
							//@param results 以前扫描到的扫描结果列表。
							@Override
							public void onBatchScanResults(List<ScanResult> results) {
								super.onBatchScanResults(results);
								Log.d(TAG, "onBatchScanResults");

							}

							//当扫描不能开启时回调
							@Override
							public void onScanFailed(int errorCode) {
								super.onScanFailed(errorCode);
								//扫描太频繁会返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app无法注册,无法开始扫描。
								Log.d(TAG, "onScanFailed. errorCode: " + errorCode);
							}
						};
						//开始扫描
						final BluetoothLeScanner mBLEScanner = bluetoothAdapter.getBluetoothLeScanner();
						mScanning = true;
/** 也可指定过滤条件和扫描配置
						 //创建ScanSettings的build对象用于设置参数
						 ScanSettings.Builder builder = new ScanSettings.Builder()
						 //设置高功耗模式
						 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
						 //android 6.0添加设置回调类型、匹配模式等
						 if(android.os.Build.VERSION.SDK_INT >= 23) {
						 //定义回调类型
						 builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
						 //设置蓝牙LE扫描滤波器硬件匹配的匹配模式
						 builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
						 }
						 // 若设备支持批处理扫描,可以选择使用批处理,但此时扫描结果仅触发onBatchScanResults()
						 //				if (bluetoothAdapter.isOffloadedScanBatchingSupported()) {
						 //					//设置蓝牙LE扫描的报告延迟的时间(以毫秒为单位)
						 //					//设置为0以立即通知结果
						 //					builder.setReportDelay(0L);
						 //				}
						 ScanSettings scanSettings = builder.build();
						 //可设置过滤条件,在第一个参数传入,但一般不设置过滤。
						 mBLEScanner.startScan(null, scanSettings, mScanCallback);
 */
						mBLEScanner.startScan(mScanCallback);
						// 设置一段时间后停止扫描
						Handler handler = HandlerThread.newHandler(new Handler.Callback() {
							@Override
							public boolean handleMessage(Message msg) {
								mScanning = false;
								mBLEScanner.stopScan((mScanCallback));
								btScanCallback.onScan(scanResult);
								return false;
							}
						});
						handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
					}
				} else {
					findBluetoothLEAndClassic(scanInterval, bluetoothAdapter, btScanCallback);
				}
			} else {
				// 缺少权限,直接返回
				btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
			}
		} catch (Throwable t) {
			t.printStackTrace();
			btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
		}
	}
Demo验证获取结果说明
  • 按照官方的集成指导,4.3以上设备使用了BLE API进行扫描,能够扫描到很多蓝牙,但是却并不能发现测试目标蓝牙,包括测试车载蓝牙的扫描也一样,另外,此方案下几乎获取不到蓝牙名称。
  • 反而使用传统蓝牙API,虽然扫描到的结果比低功耗蓝牙API少,但是能扫描到测试目标蓝牙,而且很多蓝牙名称是可以获取到的。

对于以上结果,怀疑的问题点:

  • 官方的低功耗蓝牙中提到过:你只能要么扫低功耗蓝牙(type=2),要么扫传统蓝牙(type=1),不能同时扫。(Note: You can only scan for Bluetooth LE devices or scan for Classic Bluetooth devices, as described in Bluetooth. You cannot scan for both Bluetooth LE and classic devices at the same time.)
  • 低功耗蓝牙API是不是只能扫低功耗蓝牙,不能扫传统蓝牙?
    • 测试用的两个设备正好都是传统蓝牙,
    • 使用低功耗蓝牙API,扫描列表中扫出的蓝牙只有“低功耗”和“未知”
    • 使用传统蓝牙API,扫描列表中同时有“低功耗”、“传统”和“混合模式”,另外“传统”一般都能拿到“蓝牙名称”,“低功耗”几乎拿不到。
    • 对于蓝牙扫描的说明 中,也确实提到了:低功耗蓝牙API只能扫描低功耗蓝牙,而传统蓝牙API,在大部分机型上,可以扫描“低功耗”和“传统”。局限在于,扫“低功耗”的效率低,不能返回“设备广播(ScanRecord)”。

蓝牙相关字段说明

绑定状态(bondState)
  • 10:未绑定,表示远程设备未绑定,没有共享链接密钥,因此通信(如果允许的话)将是未经身份验证和未加密的。
  • 11:绑定中,表示正在与远程设备进行绑定
  • 12:已绑定,表示远程设备已绑定,远程设备本地存储共享连接的密钥,因此可以对通信进行身份验证和加密。
蓝牙类型(type)(API 18开始)
  • 0:Unknown
  • 1:传统蓝牙(Classic - BR/EDR devices)
  • 2:低功耗蓝牙(Low Energy - LE-only)
  • 3:混合模式(Dual Mode - BR/EDR/LE)
远程设备支持的功能(uuids)(API 15开始)
  • the supported features (UUIDs) of the remote device
该蓝牙所属设备类型大分类(majorClass)
  • This value can be compared with the public constants in BluetoothClass.Device.Major to determine which major class is encoded in this Bluetooth class.
  • int型,取值如下:
    • 0:MISC
    • 256:COMPUTER
    • 512:PHONE
    • 768:NETWORKING
    • 1024:AUDIO_VIDEO
    • 1280:PERIPHERAL(外围设备)
    • 1536:IMAGING
    • 1792:WEARABLE
    • 2048:TOY
    • 2304:HEALTH
    • 7936:BITMASK / UNCATEGORIZED
该蓝牙所属设备类型小分类(deviceClass)
  • This value can be compared with the public constants in BluetoothClass.Device to determine which device class is encoded in this Bluetooth class.
  • int型,取值如下:
    • 8188:BITMASK
    • 256:COMPUTER_UNCATEGORIZED
    • 260:COMPUTER_DESKTOP
    • 264:COMPUTER_SERVER
    • 268:COMPUTER_LAPTOP
    • 272:COMPUTER_HANDHELD_PC_PDA
    • 276:COMPUTER_PALM_SIZE_PC_PDA
    • 280:COMPUTER_WEARABLE
    • 512:PHONE_UNCATEGORIZED
    • 516:PHONE_CELLULAR
    • 520:PHONE_CORDLESS
    • 524:PHONE_SMART
    • 528:PHONE_MODEM_OR_GATEWAY
    • 532:PHONE_ISDN
    • 1024:AUDIO_VIDEO_UNCATEGORIZED
    • 1028:AUDIO_VIDEO_WEARABLE_HEADSET
    • 1032:AUDIO_VIDEO_HANDSFREE
    • 1040:AUDIO_VIDEO_MICROPHONE
    • 1044:AUDIO_VIDEO_LOUDSPEAKER
    • 1048:AUDIO_VIDEO_HEADPHONES
    • 1052:AUDIO_VIDEO_PORTABLE_AUDIO
    • 1056:AUDIO_VIDEO_CAR_AUDIO
    • 1060:AUDIO_VIDEO_SET_TOP_BOX
    • 1064:AUDIO_VIDEO_HIFI_AUDIO
    • 1068:AUDIO_VIDEO_VCR
    • 1072:AUDIO_VIDEO_VIDEO_CAMERA
    • 1076:AUDIO_VIDEO_CAMCORDER
    • 1080:AUDIO_VIDEO_VIDEO_MONITOR
    • 1084:AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER
    • 1088:AUDIO_VIDEO_VIDEO_CONFERENCING
    • 1096:AUDIO_VIDEO_VIDEO_GAMING_TOY
    • 1792:WEARABLE_UNCATEGORIZED
    • 1796:WEARABLE_WRIST_WATCH
    • 1800:WEARABLE_PAGER
    • 1804:WEARABLE_JACKET
    • 1808:WEARABLE_HELMET
    • 1812:WEARABLE_GLASSES
    • 2048:TOY_UNCATEGORIZED
    • 2052:TOY_ROBOT
    • 2056:TOY_VEHICLE
    • 2060:TOY_DOLL_ACTION_FIGURE
    • 2064:TOY_CONTROLLER
    • 2068:TOY_GAME
    • 2304:HEALTH_UNCATEGORIZED
    • 2308:HEALTH_BLOOD_PRESSURE
    • 2312:HEALTH_THERMOMETER
    • 2316:HEALTH_WEIGHING
    • 2320:HEALTH_GLUCOSE
    • 2324:HEALTH_PULSE_OXIMETER
    • 2328:HEALTH_PULSE_RATE
    • 2332:HEALTH_DATA_DISPLAY
    • 1280:PERIPHERAL_NON_KEYBOARD_NON_POINTING(系统隐藏)
    • 1344:PERIPHERAL_KEYBOARD(系统隐藏)
    • 1408:PERIPHERAL_POINTING(系统隐藏)
    • 1472:PERIPHERAL_KEYBOARD_POINTING(系统隐藏)

实现一个蓝牙工具类

最后附上一个完整的蓝牙操作工具类:

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class BHelper {
	private static final String TAG = "BHelper";
	private static BHelper instance;
	private Context context;
	private boolean mScanning = false;
	// 蓝牙开关/连接接收器
	private BroadcastReceiver bOperationReceiver;
	private boolean bOperationRegistered = false;
	// 蓝牙扫描接收器
	private BroadcastReceiver bScanReceiver;
	private boolean bScanRegistered = false;
	private Map<String, BOperationCallback> bOperationCallbackMap;

	private BHelper(Context context) {
		this.context = context.getApplicationContext();
	}
	
	public static BHelper getInstance(Context context) {
		if (instance == null) {
			synchronized (BHelper.class) {
				if (instance == null) {
					instance = new BHelper(context);
				}
			}
		}
		return instance;
	}
	
	// 根据id注销指定的监听器
	public void unRegisterBOperationReceiver(String id) {
		try {
			if (bOperationCallbackMap != null && !bOperationCallbackMap.containsKey(id)) {
				bOperationCallbackMap.remove(id);
			}
			// 当前没有任何运行中的监听器时,才需要注销广播接收器
			if (bOperationCallbackMap.isEmpty()) {
				if (bOperationReceiver != null && bOperationRegistered) {
					context.unregisterReceiver(bOperationReceiver);
					bOperationRegistered = false;
					bOperationReceiver = null;
				}
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
		}
	}

	/**
	 * 注册广播接收器,用于接收蓝牙相关操作的结果
	 * 参数中增加id,目的是支持同时注册多个监听器,否则后注册的监听器会覆盖前面的监听器,导致同一时间只能有一个地方使用蓝牙工具类
	 */
	public void registerBOperationReceiver(String id, final BOperationCallback bOperationCallback) {
		if (bOperationCallback != null) {
			if (bOperationCallbackMap == null) {
				bOperationCallbackMap = new HashMap<String, BOperationCallback>();
			}
			bOperationCallbackMap.put(id, bOperationCallback);

			if (bOperationReceiver == null) {
				try {
					bOperationReceiver = new BroadcastReceiver() {
						@Override
						public void onReceive(Context context, Intent intent) {
							try {
								String action = intent.getAction();
								// 蓝牙开关状态变化
								if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
									//获取蓝牙广播中的蓝牙新状态
									int bNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
									//获取蓝牙广播中的蓝牙旧状态
									int bOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, 0);
									switch (bNewState) {
										//正在打开蓝牙
										case BluetoothAdapter.STATE_TURNING_ON: {
											// no need to monitor this action
											break;
										}
										//蓝牙已打开
										case BluetoothAdapter.STATE_ON: {
											if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
												for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
													BOperationCallback callback = entry.getValue();
													if (callback != null) {
														callback.onEnabled();
													}
												}
											}
											break;
										}
										//正在关闭蓝牙
										case BluetoothAdapter.STATE_TURNING_OFF: {
											// no need to monitor this action
											break;
										}
										//蓝牙已关闭
										case BluetoothAdapter.STATE_OFF: {
											if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
												for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
													BOperationCallback callback = entry.getValue();
													if (callback != null) {
														callback.onDisabled();
													}
												}
											}
											break;
										}
									}
								}
								/*
								 * 本机的蓝牙连接状态发生变化
								 *
								 * 特指“无任何连接”→“连接任意远程设备”,以及“连接任一或多个远程设备”→“无任何连接”的状态变化,
								 * 即“连接第一个远程设备”与“断开最后一个远程设备”时才会触发该Action
								 */
								else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
									//获取蓝牙广播中的蓝牙连接新状态
									int newConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0);
									//获取蓝牙广播中的蓝牙连接旧状态
									int oldConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, 0);
									// 当前远程蓝牙设备
									BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
									HashMap<String, Object> map = parseDevice2Map(device);
									switch (newConnState) {
										//蓝牙连接中
										case BluetoothAdapter.STATE_CONNECTING: {
											// no need to monitor this action
											break;
										}
										//蓝牙已连接
										case BluetoothAdapter.STATE_CONNECTED: {
											if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
												for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
													BOperationCallback callback = entry.getValue();
													if (callback != null) {
														callback.onConnectionChanged(true, map);
													}
												}
											}
											break;
										}
										//蓝牙断开连接中
										case BluetoothAdapter.STATE_DISCONNECTING: {
											// no need to monitor this action
											break;
										}
										//蓝牙已断开连接
										case BluetoothAdapter.STATE_DISCONNECTED: {
											if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
												for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
													BOperationCallback callback = entry.getValue();
													if (callback != null) {
														callback.onConnectionChanged(false, map);
													}
												}
											}
											break;
										}
									}
								}
								// 有远程设备成功连接至本机
								else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
									// 当前远程蓝牙设备
									BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
									HashMap<String, Object> map = parseDevice2Map(device);
									if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
										for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
											BOperationCallback callback = entry.getValue();
											if (callback != null) {
												callback.onDeviceConnected(map);
											}
										}
									}
								}
								// 有远程设备断开连接(连接至一个蓝牙设备时,若关闭蓝牙,则只会触发STATE_DISCONNECTED,不会触发ACTION_ACL_DISCONNECTED)
								else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
									// 当前远程蓝牙设备
									BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
									HashMap<String, Object> map = parseDevice2Map(device);
									if (bOperationCallbackMap != null && !bOperationCallbackMap.isEmpty()) {
										for (Map.Entry<String, BOperationCallback> entry : bOperationCallbackMap.entrySet()) {
											BOperationCallback callback = entry.getValue();
											if (callback != null) {
												callback.onDeviceDisconnected(map);
											}
										}
									}
								}
							} catch (Throwable t) {
								Log.d(TAG, t.getMessage() + "", t);
							}
						}
					};
					IntentFilter filter = new IntentFilter();
					// 蓝牙开关状态
					filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
					// 本机的蓝牙连接状态发生变化(连接第一个远程设备与断开最后一个远程设备才触发)
					filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
					// 有远程设备成功连接至本机(每个远程设备都会触发)
					filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
					// 有远程设备断开连接(每个远程设备都会触发)
					filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
					context.registerReceiver(bOperationReceiver, filter);
					bOperationRegistered = true;
				} catch (Throwable t) {
					Log.d(TAG, t.getMessage() + "", t);
				}
			}
		}
	}

	/**
	 * 打开蓝牙
	 *
	 */
	@SuppressLint("MissingPermission")
	public void open() {
		try {
			if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")
					&& DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
				//方式一:请求打开蓝牙
//				Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//				activity.startActivityForResult(intent, 1);
				//方式二:半静默打开蓝牙
				//低版本android会静默打开蓝牙,高版本android会请求打开蓝牙
				BluetoothAdapter adapter = getBAdapter();
				adapter.enable();
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
		}
	}

	/**
	 * 判断蓝牙是否已打开
	 *
	 * @return
	 */
	@SuppressLint("MissingPermission")
	public boolean isEnabled() {
		boolean enabled = false;
		try {
			if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
				BluetoothAdapter adapter = getBAdapter();
				if (adapter != null) {
					//判断蓝牙是否开启
					if (adapter.isEnabled()) {
						enabled = true;
					}
				} else {
					// Device does not support Bluetooth
				}
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
		}
		return enabled;
	}

	/**
	 * 查询已配对的蓝牙设备
	 */
	@SuppressLint("MissingPermission")
	public ArrayList<HashMap<String, Object>> getBondedDevice() {
		ArrayList<HashMap<String, Object>> result = new ArrayList<HashMap<String, Object>>();
		try {
			if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
				BluetoothAdapter adapter = getBAdapter();
				Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
				// If there are paired devices
				if (pairedDevices.size() > 0) {
					for (BluetoothDevice device : pairedDevices) {
						HashMap<String, Object> deviceInfo = parseDevice2Map(device);
						deviceInfo.put("__currConnected", (isConnectedDevice(device) ? 1 : 0));
						result.add(deviceInfo);
					}
				}
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
		}
		return result;
	}

	public boolean isConnectedDevice(BluetoothDevice device) {
		boolean isConnected = false;
		if (device != null) {
			try {
				if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
					//#if def{debuggable}
					Boolean result = ReflectHelper.invokeInstanceMethod(device, "isConnected");
					//#else
					//#=Boolean result = ReflectHelper.invokeInstanceMethod(device, Strings.getString(115));
					//#endif
					if (result != null) {
						isConnected = result.booleanValue();
					}
				}
			} catch (Throwable t) {
				Log.d(TAG, t.getMessage() + "", t);
			}
		}
		return isConnected;
	}

	/**
	 * 查找蓝牙,包括传统蓝牙和低功耗蓝牙
	 *
	 * 注:
	 * 1.该方式在查找低功耗蓝牙上效率较低
	 * 2.若只需要查找低功耗蓝牙,应该使用“低功耗蓝牙API”,即 findLE() 方法
	 * 3.为防止非正常终止扫描造成的内存泄漏,使用该方法后,需在适当的时机,主动调用一次unRegisterBtScanReceiver(),以注销接收器
	 *
	 * @param scanInterval 扫描时长,单位:秒,建议取值范围(0,12]
	 * @param bScanCallback 扫描结果回调
	 */
	@SuppressLint("MissingPermission")
	public void findLEAndClassic(int scanInterval, final BScanCallback bScanCallback) {
		try {
			if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")
			&& DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
				final BluetoothAdapter adapter = getBAdapter();
				if (!adapter.isEnabled()) {
					// 若蓝牙未打开,直接返回
					bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				if (mScanning) {
					// 正在扫描中,直接返回
					bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				// 默认扫描6秒,若scanInterval不合法,则使用默认值
				final int defaultInterval = 6;
				if (scanInterval <= 0) {
					scanInterval = defaultInterval;
				}

				// 通过bluetoothAdapter.startDiscovery()实现的扫描,系统会在扫描结束(通常是12秒)后自动停止,
				// 而cancelDiscovery()可以提前终止扫描。 所以这里的控制逻辑,相当于设置一个最大时间,限制扫描不得超出这个时间,
				// 但是很可能提前完成扫描(比如scanInterval > 12秒)
				// 设置一段时间后停止扫描(以防系统未正常停止扫描)
				final Handler handler = HandlerThread.newHandler(new Handler.Callback() {
					@Override
					public boolean handleMessage(Message msg) {
						// 若已经停止扫描(系统扫描结束/通过cancelDiscovery取消扫描),则再次调用该方法不会触发ACTION_DISCOVERY_FINISHED
						adapter.cancelDiscovery();
						return false;
					}
				});
				handler.sendEmptyMessageDelayed(0, scanInterval * 1000);

				// 准备开始扫描
				final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<HashMap<String, Object>>();
				bScanReceiver = new BroadcastReceiver() {
					public void onReceive(Context context, Intent intent) {
						try {
							String action = intent.getAction();

							if (action.equals(BluetoothDevice.ACTION_FOUND)) { //found device
								BluetoothDevice device = intent
										.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
								HashMap<String, Object> map = parseDevice2Map(device);
								// 该extra取值与BluetoothDevice对象中getName()取值一致,因此不需要通过它获取name
//							String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
								short defaultValue = 0;
								short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, defaultValue);
								map.put("rssi", rssi);
								scanResult.add(map);
							} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
								Log.d(TAG, "started");
							} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
								Log.d(TAG, "done");
								mScanning = false;
								bScanCallback.onScan(scanResult);
								// 若系统先扫描完,不需要再通过代码主动停止扫描
								handler.removeMessages(0);
								// 注销接收器
								unRegisterBScanReceiver();
							}
						} catch (Throwable t) {
							Log.d(TAG, t.getMessage() + "", t);
						}
					}
				};
				IntentFilter filter = new IntentFilter();
				// 用BroadcastReceiver来取得搜索结果
				filter.addAction(BluetoothDevice.ACTION_FOUND);
				filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
				// 两种情况会触发ACTION_DISCOVERY_FINISHED:1.系统结束扫描(约12秒);2.调用cancelDiscovery()方法主动结束扫描
				filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
				context.registerReceiver(bScanReceiver, filter);
				bScanRegistered = true;

				// 开始扫描
				mScanning = true;
				adapter.startDiscovery();
			} else {
				// 缺少权限,直接返回
				bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
			bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
		}
	}

	public void unRegisterBScanReceiver() {
		try {
			if (bScanReceiver != null && bScanRegistered) {
				context.unregisterReceiver(bScanReceiver);
				bScanRegistered = false;
				bScanReceiver = null;
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
		}
	}

	/**
	 * 查找低功耗蓝牙,该方法在4.3(API 18)以上,无法查找“传统蓝牙”
	 *
	 * @param scanInterval 扫描时长,单位:秒
	 * @param adapter
	 * @param bScanCallback 扫描结果回调
	 */
	@SuppressLint("MissingPermission")
	public void findLE(int scanInterval, final BluetoothAdapter adapter, final BScanCallback bScanCallback) {
		try {
			if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")
					&& DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
				if (!adapter.isEnabled()) {
					// 若蓝牙未打开,直接返回
					bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				if (mScanning) {
					// 正在扫描中,直接返回
					bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
					return;
				}
				// 默认扫描6秒,若scanInterval不合法,则使用默认值
				final int defaultInterval = 6;
				if (scanInterval <= 0) {
					scanInterval = defaultInterval;
				}
				// 4.3的低功耗蓝牙API
				if (Build.VERSION.SDK_INT >= 18) {
					final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<HashMap<String, Object>>();
					// 5.0又引入了新的蓝牙API(4.3版本的API仍然可用)
					if (Build.VERSION.SDK_INT < 21) {
						// 定义扫描结果回调
						final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
							/**
							 *
							 * @param device 扫描到的设备实例,可从实例中获取到相应的信息。如:名称,mac地址
							 * @param rssi 可理解成设备的信号值。该数值是一个负数,越大则信号越强
							 * @param scanRecord 远程设备提供的广播数据的内容
							 */
							@Override
							public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
								try {
									HashMap<String, Object> map = parseDevice2Map(device);
									map.put("rssi", rssi);
//									map.put("scanRecord", Data.byteToHex(scanRecord));
									scanResult.add(map);
								} catch (Throwable t) {
									Log.d(TAG, t.getMessage() + "", t);
								}
							}
						};

						// 开始扫描
						mScanning = true;
						adapter.startLeScan(leScanCallback);

						// 设置一段时间后停止扫描
						Handler handler = HandlerThread.newHandler(new Handler.Callback() {
							@Override
							public boolean handleMessage(Message msg) {
								mScanning = false;
								adapter.stopLeScan(leScanCallback);
								bScanCallback.onScan(scanResult);
								return false;
							}
						});
						handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
					} else {
						// 定义扫描结果回调
						final ScanCallback mScanCallback = new ScanCallback() {
							//当一个蓝牙ble广播被发现时回调
							@Override
							public void onScanResult(int callbackType, ScanResult result) {
								super.onScanResult(callbackType, result);
								//扫描类型有开始扫描时传入的ScanSettings相关
								//对扫描到的设备进行操作。如:获取设备信息。
								if (result != null) {
									HashMap<String, Object> map = new HashMap<String, Object>();
									BluetoothDevice device = result.getDevice();
									if (device != null) {
										map = parseDevice2Map(device);
									}
									map.put("rssi", result.getRssi());
									ScanRecord scanRecord = result.getScanRecord();
									scanResult.add(map);
								}
							}

							// 批量返回扫描结果。一般蓝牙设备对象都是通过onScanResult(int,ScanResult)返回,
							// 而不会在onBatchScanResults(List)方法中返回,除非手机支持批量扫描模式并且开启了批量扫描模式。
							// 批处理的开启请查看ScanSettings。
							//@param results 以前扫描到的扫描结果列表。
							@Override
							public void onBatchScanResults(List<ScanResult> results) {
								super.onBatchScanResults(results);
							}

							//当扫描不能开启时回调
							@Override
							public void onScanFailed(int errorCode) {
								super.onScanFailed(errorCode);
								//扫描太频繁会返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app无法注册,无法开始扫描。
							}
						};
						//开始扫描
						final BluetoothLeScanner mBLEScanner = adapter.getBluetoothLeScanner();
						mScanning = true;
/** 也可指定过滤条件和扫描配置
						 //创建ScanSettings的build对象用于设置参数
						 ScanSettings.Builder builder = new ScanSettings.Builder()
						 //设置高功耗模式
						 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
						 //android 6.0添加设置回调类型、匹配模式等
						 if(android.os.Build.VERSION.SDK_INT >= 23) {
						 //定义回调类型
						 builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
						 //设置蓝牙LE扫描滤波器硬件匹配的匹配模式
						 builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
						 }
						 // 若设备支持批处理扫描,可以选择使用批处理,但此时扫描结果仅触发onBatchScanResults()
						 //				if (bluetoothAdapter.isOffloadedScanBatchingSupported()) {
						 //					//设置蓝牙LE扫描的报告延迟的时间(以毫秒为单位)
						 //					//设置为0以立即通知结果
						 //					builder.setReportDelay(0L);
						 //				}
						 ScanSettings scanSettings = builder.build();
						 //可设置过滤条件,在第一个参数传入,但一般不设置过滤。
						 mBLEScanner.startScan(null, scanSettings, mScanCallback);
 */
						mBLEScanner.startScan(mScanCallback);
						// 设置一段时间后停止扫描
						Handler handler = HandlerThread.newHandler(new Handler.Callback() {
							@Override
							public boolean handleMessage(Message msg) {
								mScanning = false;
								mBLEScanner.stopScan((mScanCallback));
								bScanCallback.onScan(scanResult);
								return false;
							}
						});
						handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
					}
				} else {
					findLEAndClassic(scanInterval, bScanCallback);
				}
			} else {
				// 缺少权限,直接返回
				bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
			bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
		}
	}

	private BluetoothAdapter getBAdapter() {
		BluetoothAdapter adapter = null;
		try {
			if (Build.VERSION.SDK_INT >= 18) {
				BluetoothManager manager = (BluetoothManager) DeviceHelper.getInstance(context).getSystemServiceSafe(Context.BLUETOOTH_SERVICE);
				adapter = manager.getAdapter();

			} else {
				adapter = BluetoothAdapter.getDefaultAdapter();
			}
		} catch (Throwable t) {
			Log.d(TAG, t.getMessage() + "", t);
		}
		return adapter;
	}

	@SuppressLint("MissingPermission")
	private HashMap<String, Object> parseDevice2Map(BluetoothDevice device) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		if (device != null) {
			try {
				map.put("name", device.getName());
				map.put("address", device.getAddress());
				map.put("bondState", device.getBondState());

				BluetoothClass btClass = device.getBluetoothClass();
				int majorClass = btClass.getMajorDeviceClass();
				int deviceClass = btClass.getDeviceClass();
				map.put("majorClass", majorClass);
				map.put("deviceClass", deviceClass);

				if (Build.VERSION.SDK_INT >= 18) {
					map.put("type", device.getType());
				}
				// 已配对的设备,同时获取其uuids
				if (Build.VERSION.SDK_INT >= 15 && device.getBondState() == 12) {
					ArrayList<String> uuids = new ArrayList<String>();
					ParcelUuid[] parcelUuids = device.getUuids();
					if (parcelUuids != null && parcelUuids.length > 0) {
						for (ParcelUuid parcelUuid : parcelUuids) {
							if (parcelUuid != null && parcelUuid.getUuid() != null) {
								uuids.add(parcelUuid.getUuid().toString());
							}
						}
					}
					map.put("uuids", uuids);
				}
			} catch (Throwable t) {
				Log.d(TAG, t.getMessage() + "", t);
			}
		}
		return map;
	}

	public static class BOperationCallback {
		/**
		 * 打开蓝牙
		 */
		protected void onEnabled() {}

		/**
		 * 断开蓝牙
		 */
		protected void onDisabled() {}

		/**
		 * 蓝颜连接状态变化
		 *
		 * @param connect true:连接到第一个设备,false:断开最后一个设备
		 * @param btDevice 当前设备
		 */
		protected void onConnectionChanged(boolean connect, HashMap<String, Object> btDevice) {}

		/**
		 * 有远程设备成功连接
		 *
		 * @param btDevice
		 */
		protected void onDeviceConnected(HashMap<String, Object> btDevice) {}

		/**
		 * 有远程设备断开连接
		 *
		 * @param btDevice
		 */
		protected void onDeviceDisconnected(HashMap<String, Object> btDevice) {}
	}

	public interface BScanCallback {
		void onScan(ArrayList<HashMap<String, Object>> result);
	}
}

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在H5中获取Android蓝牙监听,需要通过JavaScript与原生Android代码进行交互。具体步骤如下: 1. 在Android端实现蓝牙监听功能,并创建一个JavaScript接口,在接口中将蓝牙监听数据传递给H5页面。 2. 在H5页面中使用JavaScript调用Android端的接口,获取蓝牙监听数据并处理。 以下是一个简单的示例代码: 在Android端实现蓝牙监听功能: ```java public class BluetoothListener { private static BluetoothListener instance; private BluetoothAdapter bluetoothAdapter; private BluetoothDevice bluetoothDevice; private BluetoothSocket bluetoothSocket; private InputStream inputStream; private OutputStream outputStream; private boolean isListening = false; public static BluetoothListener getInstance() { if (instance == null) { instance = new BluetoothListener(); } return instance; } public void startListening() { try { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); bluetoothDevice = bluetoothAdapter.getRemoteDevice(DEVICE_ADDRESS); bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(UUID.fromString(SERVICE_UUID)); bluetoothSocket.connect(); inputStream = bluetoothSocket.getInputStream(); outputStream = bluetoothSocket.getOutputStream(); isListening = true; while (isListening) { byte[] buffer = new byte[1024]; int count = inputStream.read(buffer); if (count > 0) { String data = new String(buffer, 0, count); sendBluetoothDataToJS(data); } } } catch (IOException e) { e.printStackTrace(); } } private void sendBluetoothDataToJS(String data) { // 将蓝牙监听数据传递给H5页面 String js = "javascript:handleBluetoothData('" + data + "')"; MainActivity.getInstance().runOnUiThread(() -> { MainActivity.getInstance().getWebView().loadUrl(js); }); } public void stopListening() { isListening = false; try { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } if (bluetoothSocket != null) { bluetoothSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } ``` 在H5页面中使用JavaScript调用Android端的接口,获取蓝牙监听数据并处理: ```javascript function handleBluetoothData(data) { // 处理蓝牙监听数据 console.log(data); } function startBluetoothListening() { // 调用Android端的接口开始监听蓝牙数据 Android.startListening(); } function stopBluetoothListening() { // 调用Android端的接口停止监听蓝牙数据 Android.stopListening(); } ``` 需要注意的是,在Android端实现蓝牙监听功能时需要获取蓝牙相关的权限,例如: ```xml <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未子涵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值