天天看點

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);
	}
}