基礎(chǔ)知識
藍(lán)牙操作主要有四項(xiàng)任務(wù):設(shè)置藍(lán)牙、查找局部區(qū)域內(nèi)的配對設(shè)備或可用設(shè)備、連接設(shè)備,以及在設(shè)備間傳輸數(shù)據(jù)。
藍(lán)牙的分類
傳統(tǒng)藍(lán)牙(Classic Bluetooth)
- 電池使用強(qiáng)度大
- 可用于數(shù)據(jù)量較大的傳輸,如語音,音樂,較高數(shù)據(jù)量傳輸?shù)?/li>
- 廣泛用于音箱,耳機(jī),汽車電子及傳統(tǒng)數(shù)傳行業(yè)
低功耗藍(lán)牙(Bluetooth LE)
- 功耗低
- 不支持音頻協(xié)議,傳輸速率較低
- 主要用于移動互聯(lián)和健康醫(yī)療,如鼠標(biāo),鍵盤,遙控鼠標(biāo)(Air Mouse),傳感設(shè)備的數(shù)據(jù)發(fā)送,如心跳帶,血壓計(jì),溫度傳感器,體重秤,健康手環(huán)等。
雙模藍(lán)牙
- 同時(shí)支持傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙模組
使用方法可以參考以下官方文檔:
以及簡書:
android藍(lán)牙BLE掃描實(shí)現(xiàn)方法
Android中兩種藍(lán)牙API的選擇
傳統(tǒng)藍(lán)牙的電池使用強(qiáng)度較大,Android 4.3(API 18)中引入了面向低功耗藍(lán)牙(BLe)的API支持。但這并不是說,4.3以上的設(shè)備就一定搭載了低功耗藍(lán)牙。反而是更多地搭載“經(jīng)典藍(lán)牙”或“雙模藍(lán)牙”,畢竟要傳輸音頻。
從測試結(jié)果來看,傳統(tǒng)藍(lán)牙API 可以同時(shí)掃描出 傳統(tǒng)藍(lán)牙 和 低功耗藍(lán)牙 ,而低功耗藍(lán)牙API 則只能用于掃描 低功耗藍(lán)牙 。
所以,千萬別以為4.3以上的設(shè)備就應(yīng)該用BLE API開發(fā)藍(lán)牙功能,除非你的業(yè)務(wù)需求是針對BLE設(shè)備的,如果你需要掃描車載藍(lán)牙或各種使用藍(lán)牙連接的外設(shè),那么建議使用傳統(tǒng)藍(lán)牙API,不然基本上掃不到設(shè)備。
功能講解
查找已配對設(shè)備列表
權(quán)限
- BLUETOOTH(普通權(quán)限)
- ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION(危險(xiǎn)權(quán)限)
注:對于定位權(quán)限的依賴區(qū)分系統(tǒng)版本,較老版本(大概是6.0以前)中不需要定位權(quán)限,新版本(大概是6.0至9.0)需要任意一個(gè)定位權(quán)限,而從Q開始,必須擁有精確定位權(quán)限(具體的版本界限需要測試得出)。
其他要求
- 必須開啟藍(lán)牙
注:如果設(shè)備已開啟藍(lán)牙,可以靜默獲取數(shù)據(jù),但若是要通過代碼開啟藍(lán)牙,則需要
BLUETOOTH_ADMIN權(quán)限,系統(tǒng)會在執(zhí)行開啟藍(lán)牙操作時(shí),向用戶顯示一個(gè)彈框,等待用戶授權(quán)。
可獲取的數(shù)據(jù)
來自BluetoothDevice對象的數(shù)據(jù)
- 藍(lán)牙名稱
- 藍(lán)牙硬件地址
- 綁定狀態(tài)
- 藍(lán)牙類型
- uuids
- 該藍(lán)牙所屬設(shè)備類型大分類(詳見【藍(lán)牙相關(guān)字段說明】)
- 該藍(lán)牙所屬設(shè)備類型小分類(詳見【藍(lán)牙相關(guān)字段說明】)
獲取方法
藍(lán)牙開啟的情況下,同步獲取
核心代碼
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;
}
/**
* 查詢已配對的藍(lán)牙設(shè)備
*
* @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());
}
// 已配對的設(shè)備,同時(shí)獲取其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;
}
獲取當(dāng)前連接的設(shè)備
方法一
先獲取已匹配設(shè)備列表,再利用API返回的BluetoothDevice對象,反射調(diào)用其中的 isConnected 實(shí)例方法。
核心代碼:
public static boolean isConnectedDevice(BluetoothDevice device) {
boolean isConnected = false;
if (device != null) {
try {
if (DeviceUtils.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
// isConnected方法只能反射調(diào)用
Boolean result = ReflectUtils.invokeInstanceMethod(device, "isConnected");
if (result != null) {
isConnected = result.booleanValue();
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
return isConnected;
}
方法二
廣播監(jiān)聽,向系統(tǒng)注冊 BluetoothDevice.ACTION_ACL_CONNECTED 廣播,可以監(jiān)聽藍(lán)牙狀態(tài),每次有遠(yuǎn)程設(shè)備連接至本機(jī)時(shí),會收到廣播。不過該方式只能監(jiān)聽廣播注冊之后的藍(lán)牙連接,注冊之前已經(jīng)連接的設(shè)備當(dāng)然獲取不到。
使用時(shí)需要注意:
- 用完后別忘了注銷廣播接收器。
- 廣播接收器的
onReceive()方法是在S 主線程 觸發(fā)的,所以不要在其中處理耗時(shí)操作,如果使用了callback返回藍(lán)牙操作的相關(guān)結(jié)果給外界,那么在callback中同樣不能做耗時(shí)操作。
核心代碼:
/**
* 注冊廣播接收器,用于接收藍(lán)牙相關(guān)操作的結(jié)果
*/
public static void registerBOperationReceiver() {
if (btOperationReceiver == null) {
try {
btOperationReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
try {
String action = intent.getAction();
// 藍(lán)牙開關(guān)狀態(tài)變化
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
//獲取藍(lán)牙廣播中的藍(lán)牙新狀態(tài)
int blueNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
//獲取藍(lán)牙廣播中的藍(lán)牙舊狀態(tài)
int blueOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, 0);
switch (blueNewState) {
//正在打開藍(lán)牙
case BluetoothAdapter.STATE_TURNING_ON: {
Toast.makeText(context, "STATE_TURNING_ON", Toast.LENGTH_SHORT).show();
break;
}
//藍(lán)牙已打開
case BluetoothAdapter.STATE_ON: {
Toast.makeText(context, "STATE_ON", Toast.LENGTH_SHORT).show();
break;
}
//正在關(guān)閉藍(lán)牙
case BluetoothAdapter.STATE_TURNING_OFF: {
Toast.makeText(context, "STATE_TURNING_OFF", Toast.LENGTH_SHORT).show();
break;
}
//藍(lán)牙已關(guān)閉
case BluetoothAdapter.STATE_OFF: {
Toast.makeText(context, "STATE_OFF", Toast.LENGTH_SHORT).show();
break;
}
}
}
/*
* 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化
*
* 特指“無任何連接”→“連接任意遠(yuǎn)程設(shè)備”,以及“連接任一或多個(gè)遠(yuǎn)程設(shè)備”→“無任何連接”的狀態(tài)變化,
* 即“連接第一個(gè)遠(yuǎn)程設(shè)備”與“斷開最后一個(gè)遠(yuǎn)程設(shè)備”時(shí)才會觸發(fā)該Action
*/
else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
//獲取藍(lán)牙廣播中的藍(lán)牙連接新狀態(tài)
int newConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0);
//獲取藍(lán)牙廣播中的藍(lán)牙連接舊狀態(tài)
int oldConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, 0);
// 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
HashMap<String, Object> map = parseBtDevice2Map(device);
switch (newConnState) {
//藍(lán)牙連接中
case BluetoothAdapter.STATE_CONNECTING: {
Log.d(TAG, "STATE_CONNECTING, " + map.get("name"));
Toast.makeText(context, "STATE_CONNECTING", Toast.LENGTH_SHORT).show();
break;
}
//藍(lán)牙已連接
case BluetoothAdapter.STATE_CONNECTED: {
Log.d(TAG, "STATE_CONNECTED, " + map.get("name"));
Toast.makeText(context, "STATE_CONNECTED", Toast.LENGTH_SHORT).show();
break;
}
//藍(lán)牙斷開連接中
case BluetoothAdapter.STATE_DISCONNECTING: {
Log.d(TAG, "STATE_DISCONNECTING, " + map.get("name"));
Toast.makeText(context, "STATE_DISCONNECTING", Toast.LENGTH_SHORT).show();
break;
}
//藍(lán)牙已斷開連接
case BluetoothAdapter.STATE_DISCONNECTED: {
Log.d(TAG, "STATE_DISCONNECTED, " + map.get("name"));
Toast.makeText(context, "STATE_DISCONNECTED", Toast.LENGTH_SHORT).show();
break;
}
}
}
// 有遠(yuǎn)程設(shè)備成功連接至本機(jī)
else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
// 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
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();
}
// 有遠(yuǎn)程設(shè)備斷開連接
else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
// 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
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();
// 藍(lán)牙開關(guān)狀態(tài)
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
// 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化(連接第一個(gè)遠(yuǎn)程設(shè)備與斷開最后一個(gè)遠(yuǎn)程設(shè)備才觸發(fā))
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
// 有遠(yuǎn)程設(shè)備成功連接至本機(jī)(每個(gè)遠(yuǎn)程設(shè)備都會觸發(fā))
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
// 有遠(yuǎn)程設(shè)備斷開連接(每個(gè)遠(yuǎn)程設(shè)備都會觸發(fā))
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
try {
context.registerReceiver(btOperationReceiver, filter);
} catch (Throwable t) {}
}
}
藍(lán)牙狀態(tài)廣播可根據(jù)業(yè)務(wù)需求,選擇使用以下ACTION:
- BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED
- 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化時(shí)觸發(fā)。特指“無任何連接”→“連接任意遠(yuǎn)程設(shè)備”,以及“連接任一或多個(gè)遠(yuǎn)程設(shè)備”→“無任何連接”的狀態(tài)變化,即“連接第一個(gè)遠(yuǎn)程設(shè)備”與“斷開最后一個(gè)遠(yuǎn)程設(shè)備”時(shí)才會觸發(fā)該Action。
- BluetoothDevice.ACTION_ACL_CONNECTED / BluetoothDevice.ACTION_ACL_DISCONNECTED
- 每個(gè)遠(yuǎn)程設(shè)備的連接與斷開都會觸發(fā)
發(fā)現(xiàn)設(shè)備
權(quán)限
- BLUETOOTH(普通權(quán)限)
- BLUETOOTH_ADMIN(用于掃描藍(lán)牙,不需動態(tài)申請,不會彈框,除非執(zhí)行“開啟藍(lán)牙”動作)
- ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION(危險(xiǎn)權(quán)限)
注:對于定位權(quán)限的依賴區(qū)分系統(tǒng)版本,較老版本(大概是6.0以前)中不需要定位權(quán)限,新版本(大概是6.0至9.0)需要任意一個(gè)定位權(quán)限,而從Q開始,必須擁有精確定位權(quán)限(具體的版本界限需要測試得出)。
其他要求
- 必須開啟藍(lán)牙
- 7.0 后不能在30秒內(nèi)掃描和停止超過5次
- 否則掃描不到結(jié)果,并收到
onScanFailed(int),返回錯(cuò)誤碼:ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無法注冊,無法開始掃描)。
- 否則掃描不到結(jié)果,并收到
可獲取的數(shù)據(jù)
來自BluetoothDevice對象的數(shù)據(jù)
- 藍(lán)牙名稱(傳統(tǒng)設(shè)備、混合模式設(shè)備能拿到,低功耗設(shè)備基本拿不到)
- 藍(lán)牙硬件地址
- 綁定狀態(tài)
- 藍(lán)牙類型
- uuids(通常拿不到,只有已配對的設(shè)備才能拿到)
- 該藍(lán)牙所屬設(shè)備類型大分類(詳見【藍(lán)牙相關(guān)字段說明】)
- 該藍(lán)牙所屬設(shè)備類型小分類(詳見【藍(lán)牙相關(guān)字段說明】)
藍(lán)牙發(fā)現(xiàn)功能特有的數(shù)據(jù)
- rssi:可理解成設(shè)備的信號值。該數(shù)值是一個(gè)負(fù)數(shù),越大則信號越強(qiáng)(傳統(tǒng)藍(lán)牙API與BLE API均可獲?。?/li>
- scanRecord:遠(yuǎn)程設(shè)備提供的廣播數(shù)據(jù)的內(nèi)容,是一個(gè)二進(jìn)制數(shù)組(BLE API特有)
獲取方法
- 異步獲取
- 掃描時(shí)長需要主動控制(不建議一次掃描太久,耗電)
- 注意:已匹配的設(shè)備不會出現(xiàn)在“發(fā)現(xiàn)列表”中(測試發(fā)現(xiàn)匹配的iphone會出現(xiàn)在發(fā)現(xiàn)列表)
說明
- 傳統(tǒng)藍(lán)牙API的掃描時(shí)長,系統(tǒng)默認(rèn)是12秒左右,但可以主動停止,可以根據(jù)業(yè)務(wù)需求,設(shè)置一個(gè)掃描超時(shí)時(shí)間
傳統(tǒng)藍(lán)牙API實(shí)現(xiàn)掃描的核心代碼
/**
* 查找藍(lán)牙,包括傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙
*
* 注:
* 1.該方式在查找低功耗藍(lán)牙上效率較低
* 2.若只需要查找低功耗藍(lán)牙,應(yīng)該使用“低功耗藍(lán)牙API”,即 findBluetoothLE() 方法
*
* @param scanInterval 掃描時(shí)長,單位:秒
* @param bluetoothAdapter
* @param btScanCallback 掃描結(jié)果回調(diào)
*/
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()) {
// 若藍(lán)牙未打開,直接返回
btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
if (mScanning) {
// 正在掃描中,直接返回
btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
// 默認(rèn)掃描6秒,若scanInterval不合法,則使用默認(rèn)值
final int defaultInterval = 6;
if (scanInterval <= 0) {
scanInterval = defaultInterval;
}
// 通過bluetoothAdapter.startDiscovery()實(shí)現(xiàn)的掃描,系統(tǒng)會在掃描結(jié)束(通常是12秒)后自動停止,
// 而cancelDiscovery()可以提前終止掃描。 所以這里的控制邏輯,相當(dāng)于設(shè)置一個(gè)最大時(shí)間,限制掃描不得超出這個(gè)時(shí)間,
// 但是很可能提前完成掃描(比如scanInterval > 12秒)
// 設(shè)置一段時(shí)間后停止掃描(以防系統(tǒng)未正常停止掃描)
final Handler handler = HandlerThread.newHandler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d(TAG, "Cancel bluetooth scan");
// 若已經(jīng)停止掃描(系統(tǒng)掃描結(jié)束/通過cancelDiscovery取消掃描),則再次調(diào)用該方法不會觸發(fā)ACTION_DISCOVERY_FINISHED
bluetoothAdapter.cancelDiscovery();
return false;
}
});
handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
// 準(zhǔn)備開始掃描
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對象中g(shù)etName()取值一致,因此不需要通過它獲取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);
// 若系統(tǒng)先掃描完,不需要再通過代碼主動停止掃描
handler.removeMessages(0);
// 注銷接收器
unRegisterBtScanReceiver();
}
}
};
IntentFilter filter = new IntentFilter();
// 用BroadcastReceiver來取得搜索結(jié)果
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
// 兩種情況會觸發(fā)ACTION_DISCOVERY_FINISHED:1.系統(tǒng)結(jié)束掃描(約12秒);2.調(diào)用cancelDiscovery()方法主動結(jié)束掃描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
context.registerReceiver(btScanReceiver, filter);
// 開始掃描
mScanning = true;
bluetoothAdapter.startDiscovery();
} else {
// 缺少權(quán)限,直接返回
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實(shí)現(xiàn)掃描的核心代碼
/**
* 查找低功耗藍(lán)牙,該方法在4.3(API 18)以上,無法查找“傳統(tǒng)藍(lán)牙”
*
* @param scanInterval 掃描時(shí)長,單位:秒
* @param bluetoothAdapter
* @param btScanCallback 掃描結(jié)果回調(diào)
*/
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()) {
// 若藍(lán)牙未打開,直接返回
btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
if (mScanning) {
// 正在掃描中,直接返回
btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
// 默認(rèn)掃描6秒,若scanInterval不合法,則使用默認(rèn)值
final int defaultInterval = 6;
if (scanInterval <= 0) {
scanInterval = defaultInterval;
}
// 4.3的低功耗藍(lán)牙API
if (Build.VERSION.SDK_INT >= 18) {
final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<>();
// 5.0又引入了新的藍(lán)牙API(4.3版本的API仍然可用)
if (Build.VERSION.SDK_INT < 21) {
// 定義掃描結(jié)果回調(diào)
final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
/**
*
* @param device 掃描到的設(shè)備實(shí)例,可從實(shí)例中獲取到相應(yīng)的信息。如:名稱,mac地址
* @param rssi 可理解成設(shè)備的信號值。該數(shù)值是一個(gè)負(fù)數(shù),越大則信號越強(qiáng)
* @param scanRecord 遠(yuǎn)程設(shè)備提供的廣播數(shù)據(jù)的內(nèi)容
*/
@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);
// 設(shè)置一段時(shí)間后停止掃描
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 {
// 定義掃描結(jié)果回調(diào)
final ScanCallback mScanCallback = new ScanCallback() {
//當(dāng)一個(gè)藍(lán)牙ble廣播被發(fā)現(xiàn)時(shí)回調(diào)
@Override
public void onScanResult(int callbackType, ScanResult result) {
Log.d(TAG, "onScanResult: " + result.getDevice().getAddress() + ", " + result.getDevice().getName());
super.onScanResult(callbackType, result);
//掃描類型有開始掃描時(shí)傳入的ScanSettings相關(guān)
//對掃描到的設(shè)備進(jìn)行操作。如:獲取設(shè)備信息。
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);
}
}
// 批量返回掃描結(jié)果。一般藍(lán)牙設(shè)備對象都是通過onScanResult(int,ScanResult)返回,
// 而不會在onBatchScanResults(List)方法中返回,除非手機(jī)支持批量掃描模式并且開啟了批量掃描模式。
// 批處理的開啟請查看ScanSettings。
//@param results 以前掃描到的掃描結(jié)果列表。
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
Log.d(TAG, "onBatchScanResults");
}
//當(dāng)掃描不能開啟時(shí)回調(diào)
@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;
/** 也可指定過濾條件和掃描配置
//創(chuàng)建ScanSettings的build對象用于設(shè)置參數(shù)
ScanSettings.Builder builder = new ScanSettings.Builder()
//設(shè)置高功耗模式
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
//android 6.0添加設(shè)置回調(diào)類型、匹配模式等
if(android.os.Build.VERSION.SDK_INT >= 23) {
//定義回調(diào)類型
builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
//設(shè)置藍(lán)牙LE掃描濾波器硬件匹配的匹配模式
builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
}
// 若設(shè)備支持批處理掃描,可以選擇使用批處理,但此時(shí)掃描結(jié)果僅觸發(fā)onBatchScanResults()
// if (bluetoothAdapter.isOffloadedScanBatchingSupported()) {
// //設(shè)置藍(lán)牙LE掃描的報(bào)告延遲的時(shí)間(以毫秒為單位)
// //設(shè)置為0以立即通知結(jié)果
// builder.setReportDelay(0L);
// }
ScanSettings scanSettings = builder.build();
//可設(shè)置過濾條件,在第一個(gè)參數(shù)傳入,但一般不設(shè)置過濾。
mBLEScanner.startScan(null, scanSettings, mScanCallback);
*/
mBLEScanner.startScan(mScanCallback);
// 設(shè)置一段時(shí)間后停止掃描
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 {
// 缺少權(quán)限,直接返回
btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
}
} catch (Throwable t) {
t.printStackTrace();
btScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
}
}
Demo驗(yàn)證獲取結(jié)果說明
- 按照官方的集成指導(dǎo),4.3以上設(shè)備使用了BLE API進(jìn)行掃描,能夠掃描到很多藍(lán)牙,但是卻并不能發(fā)現(xiàn)測試目標(biāo)藍(lán)牙,包括測試車載藍(lán)牙的掃描也一樣,另外,此方案下幾乎獲取不到藍(lán)牙名稱。
- 反而使用傳統(tǒng)藍(lán)牙API,雖然掃描到的結(jié)果比低功耗藍(lán)牙API少,但是能掃描到測試目標(biāo)藍(lán)牙,而且很多藍(lán)牙名稱是可以獲取到的。
對于以上結(jié)果,懷疑的問題點(diǎn):
- 官方的低功耗藍(lán)牙中提到過:你只能要么掃低功耗藍(lán)牙(type=2),要么掃傳統(tǒng)藍(lán)牙(type=1),不能同時(shí)掃。(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.)
- 低功耗藍(lán)牙API是不是只能掃低功耗藍(lán)牙,不能掃傳統(tǒng)藍(lán)牙?
- 測試用的兩個(gè)設(shè)備正好都是傳統(tǒng)藍(lán)牙,
- 使用低功耗藍(lán)牙API,掃描列表中掃出的藍(lán)牙只有“低功耗”和“未知”
- 使用傳統(tǒng)藍(lán)牙API,掃描列表中同時(shí)有“低功耗”、“傳統(tǒng)”和“混合模式”,另外“傳統(tǒng)”一般都能拿到“藍(lán)牙名稱”,“低功耗”幾乎拿不到。
- 對于藍(lán)牙掃描的說明 中,也確實(shí)提到了:低功耗藍(lán)牙API只能掃描低功耗藍(lán)牙,而傳統(tǒng)藍(lán)牙API,在大部分機(jī)型上,可以掃描“低功耗”和“傳統(tǒng)”。局限在于,掃“低功耗”的效率低,不能返回“設(shè)備廣播(ScanRecord)”。
藍(lán)牙相關(guān)字段說明
綁定狀態(tài)(bondState)
- 10:未綁定,表示遠(yuǎn)程設(shè)備未綁定,沒有共享鏈接密鑰,因此通信(如果允許的話)將是未經(jīng)身份驗(yàn)證和未加密的。
- 11:綁定中,表示正在與遠(yuǎn)程設(shè)備進(jìn)行綁定
- 12:已綁定,表示遠(yuǎn)程設(shè)備已綁定,遠(yuǎn)程設(shè)備本地存儲共享連接的密鑰,因此可以對通信進(jìn)行身份驗(yàn)證和加密。
藍(lán)牙類型(type)(API 18開始)
- 0:Unknown
- 1:傳統(tǒng)藍(lán)牙(Classic - BR/EDR devices)
- 2:低功耗藍(lán)牙(Low Energy - LE-only)
- 3:混合模式(Dual Mode - BR/EDR/LE)
遠(yuǎn)程設(shè)備支持的功能(uuids)(API 15開始)
- the supported features (UUIDs) of the remote device
該藍(lán)牙所屬設(shè)備類型大分類(majorClass)
- This value can be compared with the public constants in
BluetoothClass.Device.Majorto determine which major class is encoded in this Bluetooth class. - int型,取值如下:
- 0:MISC
- 256:COMPUTER
- 512:PHONE
- 768:NETWORKING
- 1024:AUDIO_VIDEO
- 1280:PERIPHERAL(外圍設(shè)備)
- 1536:IMAGING
- 1792:WEARABLE
- 2048:TOY
- 2304:HEALTH
- 7936:BITMASK / UNCATEGORIZED
該藍(lán)牙所屬設(shè)備類型小分類(deviceClass)
- This value can be compared with the public constants in
BluetoothClass.Deviceto 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(系統(tǒng)隱藏)
- 1344:PERIPHERAL_KEYBOARD(系統(tǒng)隱藏)
- 1408:PERIPHERAL_POINTING(系統(tǒng)隱藏)
- 1472:PERIPHERAL_KEYBOARD_POINTING(系統(tǒng)隱藏)
實(shí)現(xiàn)一個(gè)藍(lán)牙工具類
最后附上一個(gè)完整的藍(lán)牙操作工具類:
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;
// 藍(lán)牙開關(guān)/連接接收器
private BroadcastReceiver bOperationReceiver;
private boolean bOperationRegistered = false;
// 藍(lán)牙掃描接收器
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;
}
// 根據(jù)id注銷指定的監(jiān)聽器
public void unRegisterBOperationReceiver(String id) {
try {
if (bOperationCallbackMap != null && !bOperationCallbackMap.containsKey(id)) {
bOperationCallbackMap.remove(id);
}
// 當(dāng)前沒有任何運(yùn)行中的監(jiān)聽器時(shí),才需要注銷廣播接收器
if (bOperationCallbackMap.isEmpty()) {
if (bOperationReceiver != null && bOperationRegistered) {
context.unregisterReceiver(bOperationReceiver);
bOperationRegistered = false;
bOperationReceiver = null;
}
}
} catch (Throwable t) {
Log.d(TAG, t.getMessage() + "", t);
}
}
/**
* 注冊廣播接收器,用于接收藍(lán)牙相關(guān)操作的結(jié)果
* 參數(shù)中增加id,目的是支持同時(shí)注冊多個(gè)監(jiān)聽器,否則后注冊的監(jiān)聽器會覆蓋前面的監(jiān)聽器,導(dǎo)致同一時(shí)間只能有一個(gè)地方使用藍(lán)牙工具類
*/
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();
// 藍(lán)牙開關(guān)狀態(tài)變化
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
//獲取藍(lán)牙廣播中的藍(lán)牙新狀態(tài)
int bNewState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
//獲取藍(lán)牙廣播中的藍(lán)牙舊狀態(tài)
int bOldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, 0);
switch (bNewState) {
//正在打開藍(lán)牙
case BluetoothAdapter.STATE_TURNING_ON: {
// no need to monitor this action
break;
}
//藍(lán)牙已打開
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;
}
//正在關(guān)閉藍(lán)牙
case BluetoothAdapter.STATE_TURNING_OFF: {
// no need to monitor this action
break;
}
//藍(lán)牙已關(guān)閉
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;
}
}
}
/*
* 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化
*
* 特指“無任何連接”→“連接任意遠(yuǎn)程設(shè)備”,以及“連接任一或多個(gè)遠(yuǎn)程設(shè)備”→“無任何連接”的狀態(tài)變化,
* 即“連接第一個(gè)遠(yuǎn)程設(shè)備”與“斷開最后一個(gè)遠(yuǎn)程設(shè)備”時(shí)才會觸發(fā)該Action
*/
else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
//獲取藍(lán)牙廣播中的藍(lán)牙連接新狀態(tài)
int newConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 0);
//獲取藍(lán)牙廣播中的藍(lán)牙連接舊狀態(tài)
int oldConnState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, 0);
// 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
HashMap<String, Object> map = parseDevice2Map(device);
switch (newConnState) {
//藍(lán)牙連接中
case BluetoothAdapter.STATE_CONNECTING: {
// no need to monitor this action
break;
}
//藍(lán)牙已連接
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;
}
//藍(lán)牙斷開連接中
case BluetoothAdapter.STATE_DISCONNECTING: {
// no need to monitor this action
break;
}
//藍(lán)牙已斷開連接
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;
}
}
}
// 有遠(yuǎn)程設(shè)備成功連接至本機(jī)
else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
// 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
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);
}
}
}
}
// 有遠(yuǎn)程設(shè)備斷開連接(連接至一個(gè)藍(lán)牙設(shè)備時(shí),若關(guān)閉藍(lán)牙,則只會觸發(fā)STATE_DISCONNECTED,不會觸發(fā)ACTION_ACL_DISCONNECTED)
else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
// 當(dāng)前遠(yuǎn)程藍(lán)牙設(shè)備
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();
// 藍(lán)牙開關(guān)狀態(tài)
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
// 本機(jī)的藍(lán)牙連接狀態(tài)發(fā)生變化(連接第一個(gè)遠(yuǎn)程設(shè)備與斷開最后一個(gè)遠(yuǎn)程設(shè)備才觸發(fā))
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
// 有遠(yuǎn)程設(shè)備成功連接至本機(jī)(每個(gè)遠(yuǎn)程設(shè)備都會觸發(fā))
filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
// 有遠(yuǎn)程設(shè)備斷開連接(每個(gè)遠(yuǎn)程設(shè)備都會觸發(fā))
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
context.registerReceiver(bOperationReceiver, filter);
bOperationRegistered = true;
} catch (Throwable t) {
Log.d(TAG, t.getMessage() + "", t);
}
}
}
}
/**
* 打開藍(lán)牙
*
*/
@SuppressLint("MissingPermission")
public void open() {
try {
if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")
&& DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH_ADMIN")) {
//方式一:請求打開藍(lán)牙
// Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
// activity.startActivityForResult(intent, 1);
//方式二:半靜默打開藍(lán)牙
//低版本android會靜默打開藍(lán)牙,高版本android會請求打開藍(lán)牙
BluetoothAdapter adapter = getBAdapter();
adapter.enable();
}
} catch (Throwable t) {
Log.d(TAG, t.getMessage() + "", t);
}
}
/**
* 判斷藍(lán)牙是否已打開
*
* @return
*/
@SuppressLint("MissingPermission")
public boolean isEnabled() {
boolean enabled = false;
try {
if (DeviceHelper.getInstance(context).checkPermission("android.permission.BLUETOOTH")) {
BluetoothAdapter adapter = getBAdapter();
if (adapter != null) {
//判斷藍(lán)牙是否開啟
if (adapter.isEnabled()) {
enabled = true;
}
} else {
// Device does not support Bluetooth
}
}
} catch (Throwable t) {
Log.d(TAG, t.getMessage() + "", t);
}
return enabled;
}
/**
* 查詢已配對的藍(lán)牙設(shè)備
*/
@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;
}
/**
* 查找藍(lán)牙,包括傳統(tǒng)藍(lán)牙和低功耗藍(lán)牙
*
* 注:
* 1.該方式在查找低功耗藍(lán)牙上效率較低
* 2.若只需要查找低功耗藍(lán)牙,應(yīng)該使用“低功耗藍(lán)牙API”,即 findLE() 方法
* 3.為防止非正常終止掃描造成的內(nèi)存泄漏,使用該方法后,需在適當(dāng)?shù)臅r(shí)機(jī),主動調(diào)用一次unRegisterBtScanReceiver(),以注銷接收器
*
* @param scanInterval 掃描時(shí)長,單位:秒,建議取值范圍(0,12]
* @param bScanCallback 掃描結(jié)果回調(diào)
*/
@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()) {
// 若藍(lán)牙未打開,直接返回
bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
if (mScanning) {
// 正在掃描中,直接返回
bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
// 默認(rèn)掃描6秒,若scanInterval不合法,則使用默認(rèn)值
final int defaultInterval = 6;
if (scanInterval <= 0) {
scanInterval = defaultInterval;
}
// 通過bluetoothAdapter.startDiscovery()實(shí)現(xiàn)的掃描,系統(tǒng)會在掃描結(jié)束(通常是12秒)后自動停止,
// 而cancelDiscovery()可以提前終止掃描。 所以這里的控制邏輯,相當(dāng)于設(shè)置一個(gè)最大時(shí)間,限制掃描不得超出這個(gè)時(shí)間,
// 但是很可能提前完成掃描(比如scanInterval > 12秒)
// 設(shè)置一段時(shí)間后停止掃描(以防系統(tǒng)未正常停止掃描)
final Handler handler = HandlerThread.newHandler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// 若已經(jīng)停止掃描(系統(tǒng)掃描結(jié)束/通過cancelDiscovery取消掃描),則再次調(diào)用該方法不會觸發(fā)ACTION_DISCOVERY_FINISHED
adapter.cancelDiscovery();
return false;
}
});
handler.sendEmptyMessageDelayed(0, scanInterval * 1000);
// 準(zhǔn)備開始掃描
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對象中g(shù)etName()取值一致,因此不需要通過它獲取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);
// 若系統(tǒng)先掃描完,不需要再通過代碼主動停止掃描
handler.removeMessages(0);
// 注銷接收器
unRegisterBScanReceiver();
}
} catch (Throwable t) {
Log.d(TAG, t.getMessage() + "", t);
}
}
};
IntentFilter filter = new IntentFilter();
// 用BroadcastReceiver來取得搜索結(jié)果
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
// 兩種情況會觸發(fā)ACTION_DISCOVERY_FINISHED:1.系統(tǒng)結(jié)束掃描(約12秒);2.調(diào)用cancelDiscovery()方法主動結(jié)束掃描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
context.registerReceiver(bScanReceiver, filter);
bScanRegistered = true;
// 開始掃描
mScanning = true;
adapter.startDiscovery();
} else {
// 缺少權(quán)限,直接返回
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);
}
}
/**
* 查找低功耗藍(lán)牙,該方法在4.3(API 18)以上,無法查找“傳統(tǒng)藍(lán)牙”
*
* @param scanInterval 掃描時(shí)長,單位:秒
* @param adapter
* @param bScanCallback 掃描結(jié)果回調(diào)
*/
@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()) {
// 若藍(lán)牙未打開,直接返回
bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
if (mScanning) {
// 正在掃描中,直接返回
bScanCallback.onScan(new ArrayList<HashMap<String, Object>>());
return;
}
// 默認(rèn)掃描6秒,若scanInterval不合法,則使用默認(rèn)值
final int defaultInterval = 6;
if (scanInterval <= 0) {
scanInterval = defaultInterval;
}
// 4.3的低功耗藍(lán)牙API
if (Build.VERSION.SDK_INT >= 18) {
final ArrayList<HashMap<String, Object>> scanResult = new ArrayList<HashMap<String, Object>>();
// 5.0又引入了新的藍(lán)牙API(4.3版本的API仍然可用)
if (Build.VERSION.SDK_INT < 21) {
// 定義掃描結(jié)果回調(diào)
final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
/**
*
* @param device 掃描到的設(shè)備實(shí)例,可從實(shí)例中獲取到相應(yīng)的信息。如:名稱,mac地址
* @param rssi 可理解成設(shè)備的信號值。該數(shù)值是一個(gè)負(fù)數(shù),越大則信號越強(qiáng)
* @param scanRecord 遠(yuǎn)程設(shè)備提供的廣播數(shù)據(jù)的內(nèi)容
*/
@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);
// 設(shè)置一段時(shí)間后停止掃描
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 {
// 定義掃描結(jié)果回調(diào)
final ScanCallback mScanCallback = new ScanCallback() {
//當(dāng)一個(gè)藍(lán)牙ble廣播被發(fā)現(xiàn)時(shí)回調(diào)
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//掃描類型有開始掃描時(shí)傳入的ScanSettings相關(guān)
//對掃描到的設(shè)備進(jìn)行操作。如:獲取設(shè)備信息。
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);
}
}
// 批量返回掃描結(jié)果。一般藍(lán)牙設(shè)備對象都是通過onScanResult(int,ScanResult)返回,
// 而不會在onBatchScanResults(List)方法中返回,除非手機(jī)支持批量掃描模式并且開啟了批量掃描模式。
// 批處理的開啟請查看ScanSettings。
//@param results 以前掃描到的掃描結(jié)果列表。
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
//當(dāng)掃描不能開啟時(shí)回調(diào)
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
//掃描太頻繁會返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app無法注冊,無法開始掃描。
}
};
//開始掃描
final BluetoothLeScanner mBLEScanner = adapter.getBluetoothLeScanner();
mScanning = true;
/** 也可指定過濾條件和掃描配置
//創(chuàng)建ScanSettings的build對象用于設(shè)置參數(shù)
ScanSettings.Builder builder = new ScanSettings.Builder()
//設(shè)置高功耗模式
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
//android 6.0添加設(shè)置回調(diào)類型、匹配模式等
if(android.os.Build.VERSION.SDK_INT >= 23) {
//定義回調(diào)類型
builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
//設(shè)置藍(lán)牙LE掃描濾波器硬件匹配的匹配模式
builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY);
}
// 若設(shè)備支持批處理掃描,可以選擇使用批處理,但此時(shí)掃描結(jié)果僅觸發(fā)onBatchScanResults()
// if (bluetoothAdapter.isOffloadedScanBatchingSupported()) {
// //設(shè)置藍(lán)牙LE掃描的報(bào)告延遲的時(shí)間(以毫秒為單位)
// //設(shè)置為0以立即通知結(jié)果
// builder.setReportDelay(0L);
// }
ScanSettings scanSettings = builder.build();
//可設(shè)置過濾條件,在第一個(gè)參數(shù)傳入,但一般不設(shè)置過濾。
mBLEScanner.startScan(null, scanSettings, mScanCallback);
*/
mBLEScanner.startScan(mScanCallback);
// 設(shè)置一段時(shí)間后停止掃描
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 {
// 缺少權(quán)限,直接返回
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());
}
// 已配對的設(shè)備,同時(shí)獲取其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 {
/**
* 打開藍(lán)牙
*/
protected void onEnabled() {}
/**
* 斷開藍(lán)牙
*/
protected void onDisabled() {}
/**
* 藍(lán)顏連接狀態(tài)變化
*
* @param connect true:連接到第一個(gè)設(shè)備,false:斷開最后一個(gè)設(shè)備
* @param btDevice 當(dāng)前設(shè)備
*/
protected void onConnectionChanged(boolean connect, HashMap<String, Object> btDevice) {}
/**
* 有遠(yuǎn)程設(shè)備成功連接
*
* @param btDevice
*/
protected void onDeviceConnected(HashMap<String, Object> btDevice) {}
/**
* 有遠(yuǎn)程設(shè)備斷開連接
*
* @param btDevice
*/
protected void onDeviceDisconnected(HashMap<String, Object> btDevice) {}
}
public interface BScanCallback {
void onScan(ArrayList<HashMap<String, Object>> result);
}
}