解決安卓7.0BLE(低功耗藍牙)掃描返回空的問題
Android7.0後之前的採用BluetoothAdapter的startLeScan方法已經無法獲取到ibeacon的信息了。
會報權限安全的錯誤,要求申請下面的權限之一
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
經過查找資料發現,7.0從安全和功耗的角度出發,把BLE掃描跟定位權限綁在一起了(室內定位需要BLE採集數據),因此我們出了上面的靜態權限還需要在代碼裏通過運行時權限獲得GPS定位權限再操作。
查看了Android的藍牙源碼發現,Android7.0修改了藍牙BLE掃描的API,把之前的BlueToothAdapter方法標記爲了Deprecated方法,如圖1所示
同時在源碼的註釋裏面推薦我們使用BluetoothLeScanner類的startScan方法。搞清楚了原因就動手修改代碼了。
先貼上7.0以上版本的核心代碼:
BluetoothManager bluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothLeScanner bleScanner = btAdapter.getBluetoothLeScanner();//用過單例的方式獲取實例
開啓BLE掃描
bleScanner.startScan(scanCallback);
ScanCallback scanCallback = new ScanCallback() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
int rssi = result.getRssi();//獲取rssi
//這裏寫你自己的邏輯
}
};
停止掃描的代碼
bleScanner.stopScan(scanCallback);
此外,不要忘記申請定位權限
if (ContextCompat.checkSelfPermission(Objects.requireNonNull(getActivity()), Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// 申請定位授權
ActivityCompat.requestPermissions(Objects.requireNonNull(getActivity()),
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_CODE);
} else {
isScanning = true;
bleScanner.startScan(scanCallback);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 110:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
bleImage.setImageDrawable(getResources().getDrawable(R.drawable.ic_bluetooth_searching_black_24dp));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bleScanner.startScan(scanCallback);//開啓BLE掃描
}
} else
Toast.makeText(getContext(), "沒有獲取到定位權限", Toast.LENGTH_LONG).show();
break;
}
}
完整的全版本BLE掃描代碼如下
//通過HashMap保存掃描到的藍牙信息。可以有效防止重複
private HashMap<String, String> ibeaconAddrHM = new HashMap<>();
private StringBuilder TextViewMsg = new StringBuilder();
private int bleScanTimes = 0;
//Handler.Callback()可以解決編譯器Handler.leak的提示
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 1010:
bleText.setText(TextViewMsg.toString() +
"\r\n" + "進行了 " + bleScanTimes + " 次掃描"
);
break;
}
return false;
}
});
//region Button Task
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.image_ble://一個藍牙的圖片按鈕
if (!isScanning) {
if (isBluetoothValid()) { //判斷設備是否支持Ble藍牙
if (isBluetoothOpen()) { //藍牙已經打開,則開始進行藍牙搜索
ourBleScan();//開啓BLE掃描
} else {
enableBluetooth();//開啓藍牙
ourBleScan();//開啓BLE掃描
}
}
} else {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
//安卓6.0及以下版本BLE操作的代碼
btAdapter.stopLeScan(mLeScanCallback);
} else
//安卓7.0及以上版本BLE操作的代碼
bleScanner.stopScan(scanCallback);
bleScanTimes = 0;
Toast.makeText(getContext(), "BLE掃描已經關閉", Toast.LENGTH_SHORT).show();
isScanning = false;
}
break;
}
}
//endregion
//region 判斷是否支持藍牙設備
public boolean isBluetoothValid() {
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false;
}
BluetoothManager bluetoothManager = (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
btAdapter = bluetoothManager.getAdapter();
if (btAdapter == null) {
return false;
}
//
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && bleScanner == null) {
bleScanner = btAdapter.getBluetoothLeScanner();
}
return true;
}
//endregion
//region 藍牙是否打開
private boolean isBluetoothOpen() {
return btAdapter.isEnabled();
}
//endregion
//region 打開藍牙
private void enableBluetooth() {
if (!btAdapter.isEnabled()) {
btAdapter.enable();
}
}
//endregion
//region Ble Scan
private void ourBleScan() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {//安卓6.0以下的方案
btAdapter.startLeScan(mLeScanCallback);
isScanning = true;
} else {//安卓7.0及以上的方案
if (ContextCompat.checkSelfPermission(Objects.requireNonNull(getActivity()), Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// 申請定位授權
ActivityCompat.requestPermissions(Objects.requireNonNull(getActivity()),
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_CODE);
} else {
isScanning = true;
bleScanner.startScan(scanCallback);
}
}
}
//endregion
//region Android M 以下的回調
BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
// TODO Auto-generated method stub
bleScanTimes++;//Scan times plus one
String macAddr = device.getAddress();
String ibeconInfo = "\r\n" + macAddr + " -- " + rssi + "DB";
if (macAddr != null) {
TextViewMsg.delete(0, TextViewMsg.length());//initialize StringBuilder
ibeaconAddrHM.put(macAddr, ibeconInfo);
Iterator it = ibeaconAddrHM.keySet().iterator();
while (it.hasNext()) {
TextViewMsg.append(ibeaconAddrHM.get(it.next()));/這裏的TextViewMsg是一個TextView
}
//寫入文件,這個方法可以參考我的另一篇博文 Android/安卓開發兩句代碼寫文件到外部存儲
// WriteToFile.writeToFile(TextViewMsg.toString());
Message message = new Message();
message.what = 1010;
handler.sendMessage(message);
}
Log.i(TAG, macAddr);
}
};
//endregion
//region Android M 以上的回調
ScanCallback scanCallback = new ScanCallback() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
int rssi = result.getRssi();
bleScanTimes++;//Scan times plus one
String macAddr = device.getAddress();
String ibeconInfo = "\r\n" + macAddr + " -- " + rssi + "DB";
if (macAddr != null) {
TextViewMsg.delete(0, TextViewMsg.length());//initialize StringBuilder
ibeaconAddrHM.put(macAddr, ibeconInfo);
Iterator it = ibeaconAddrHM.keySet().iterator();
while (it.hasNext()) {
TextViewMsg.append(ibeaconAddrHM.get(it.next()));/這裏的TextViewMsg是一個TextView
}
//寫入文件,這個方法可以參考我的另一篇博文 Android/安卓開發兩句代碼寫文件到外部存儲
// WriteToFile.writeToFile(TextViewMsg.toString());
Message message = new Message();
message.what = 1010;
handler.sendMessage(message);
}
Log.i(TAG, macAddr);
}
};
//endregion
//region Request Permissions
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 110:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
isScanning = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bleScanner.startScan(scanCallback);
}
} else
Toast.makeText(getContext(), "沒有獲取到定位權限", Toast.LENGTH_LONG).show();
break;
}
}
//endregion
}