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