一、前言
截止到本文完成的日期为止(2020年04月16日),笔者对Android 5.0~Android 10的部分手机进行了适配测试。该文中所遇到的问题基本都出现在国产定制系统(EMUI、MIUI、ColorOS)上。开发环境为macOS+idea。
二、相关代码
1、(基本)在AndroidManifest.xml中静态申请如下权限:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
检测是否打开位置服务:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
*BLE的扫描需要开启位置信息相关的权限,否则将会无法扫描到BLE设备。
*在MIUI11(Android 10)中,需要另行打开位置服务(GPS),否则也不能扫描到BLE设备。部分其它国产ROM在Android 10也可能存在此问题,解决办法同上。判断标准是:BluetoothLeScanner被正确地实例化(不为null),但始终无法发现附近的BLE设备。
+ 2、(Android 6.0及以上)在onCreate中动态申请位置信息权限:
~~~java
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
Log.i(TAG, "sdk < Android M");
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
String[] strings =
{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
ActivityCompat.requestPermissions(this, strings, 1);
}
} else {
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(this,
"android.permission.ACCESS_BACKGROUND_LOCATION") != PackageManager.PERMISSION_GRANTED) {
String[] strings = {android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
"android.permission.ACCESS_BACKGROUND_LOCATION"};
ActivityCompat.requestPermissions(this, strings, 2);
}
}如果未打开位置服务,让用户去打开:1
2
3
4
5
6
7public static final boolean isLocationEnable(Context context) {
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
boolean networkProvider = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
boolean gpsProvider = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (networkProvider || gpsProvider) return true;
return false;
}进入设置,让用户自己选择是否打开位置服务,然后获取选择结果:1
2
3
4
5private static final int REQUEST_CODE_LOCATION_SETTINGS = 2;
private void setLocationService() {
Intent locationIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
this.startActivityForResult(locationIntent, REQUEST_CODE_LOCATION_SETTINGS);
}1
2
3
4
5
6
7
8
9
10
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_LOCATION_SETTINGS) {
if (isLocationEnable(this)) {
//定位已打开的处理
} else {
//定位依然没有打开的处理
}
} else super.onActivityResult(requestCode, resultCode, data);
}3、(基本)检测是否支持BLE蓝牙及蓝牙开关状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//是否支持
public static boolean isSupportBle(Context context) {
if (context == null || !context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
return false;
}
BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
return manager.getAdapter() != null;
}
//是否开启
public static boolean isBleEnable(Context context) {
if (!isSupportBle(context)) {
return false;
}
BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
return manager.getAdapter().isEnabled();
}
//开启蓝牙
public static void enableBluetooth(Activity activity, int requestCode) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(intent, requestCode);
}4、(基本&&Android 7.0+适配特别注意!)进行BLE设备扫描
在Android 7.0及以后的系统中,BluetoothAdapter.startLeScan()方法被废弃,所以需要根据系统版本的不同来执行不同的扫描方法,否则在MIUI11及EMUI10的部分机型上会扫描不到BLE设备。
1
2
3
4
5
6
7
8
9
10
11
12
13
14private void startScan() throws Exception {
isScanning = true;
handler.postDelayed(scanRunnable, TIME_OUT);
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
LogUtil.e("系统版本:","<7.0");
bluetoothManager.getAdapter()
.startLeScan(mLeScanCallback);
} else {//安卓7.0及以上的方案
LogUtil.e("系统版本:",">=7.0");
bleScanner = bluetoothManager.getAdapter().getBluetoothLeScanner();
bleScanner.startScan(scanCallback);
}
}1
2
3
4
5
6
7
8
9
10
11//停止扫描
private void stopScan() {
isScanning = false;
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
//安卓6.0及以下版本BLE操作的代码
bluetoothManager.getAdapter().stopLeScan(mLeScanCallback);
} else
//安卓7.0及以上版本BLE操作的代码
bleScanner.stopScan(scanCallback);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50//扫描结果回调-Android 7.0-
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
LogUtil.e(TAG, "开始搜索设备:" + mac);
if (device.getAddress().equals(mac)) {
LogUtil.e(TAG, "搜索到设备 " + mac);
new Handler() {
public void handleMessage(Message msg) {
if (mBluetoothLeService != null) {
mBluetoothLeService.connect(mac);
}
}
}.sendEmptyMessageDelayed(3000, 200);
stopScan();
isScanning = false;
handler.removeCallbacks(scanRunnable);
}
}
};
//扫描结果回调-7.0+
private ScanCallback scanCallback = new ScanCallback() {
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
String macAddr = device.getAddress();
LogUtil.e("发现设备:", macAddr);
if (macAddr.equals(mac)) {
LogUtil.e(TAG, "搜索到匹配的蓝牙设备(6.0+): " + mac);
new Handler() {
public void handleMessage(Message msg) {
if (mBluetoothLeService != null) {
mBluetoothLeService.connect(mac);
}
}
}.sendEmptyMessageDelayed(3000, 200);
stopScan();
isScanning = false;
handler.removeCallbacks(scanRunnable);
}
Log.i(TAG, macAddr);
}
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//重连计时器
private Runnable scanRunnable = new Runnable() {
public void run() {
connTimeOutTimes++;
LogUtil.e(TAG, "重连第" + connTimeOutTimes + "次");
if (connTimeOutTimes < RECONNECT_TIMES) {
handler.postDelayed(scanRunnable, TIME_OUT);
} else {
stopScan();
connTimeOutTimes = 0;
if (connectFunction != null) {
LogUtil.e(TAG, "连接超时:" + mBluetoothLeService.getConnectState());
connectFunction.onCallBack("{\"code\":-1,\"msg\":\"连接超时\",\"data\":\"false\"}");
connectFunction = null;
}
if (cardConnectFunction != null) {
cardConnectFunction.onCallBack("{\"code\":-1,\"msg\":\"连接超时\",\"data\":\"false\"}");
cardConnectFunction = null;
}
mBluetoothLeService.setConnectionState(0);
mBluetoothLeService.release();
bindStatus = 0;
}
}
};
三、踩过坑的机型/ROM
1、小米/红米
MIUI11:必须打开位置服务才可搜索到BLE设备;
MIUI11及更早:
初始化BlueToothGatt时务必将connectGatt的autoConnect设为false,否则会被系统杀掉;
1
2
3
4
5
6
7
8package android.bluetooth;
public final class BluetoothDevice implements Parcelable {
...
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback) {
return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));
}
}必须针对Android M及以上的MIUI配置调用BluetoothLeScanner.startScan()方法来搜索BLE设备,而非被废弃的BluetoothAdapter.startLeScan(),否则将无法搜索到设备,日志如下:
1
2
3D/BluetoothAdapter: startLeScan(): null **注意,此处为null
D/BluetoothAdapter: isLeEnabled(): ON
D/BluetoothLeScanner: onScannerRegistered() - status=0 scannerId=6 mScannerId=02、华为/荣耀
EMUI10(仅Mate 30/P30系列):必须打开位置服务才可搜索到BLE设备
3、部分国产ROM(包括上述两类):
部分机型需打开位置服务才可搜索到BLE设备;
部分机型需动态申请位置权限才可搜索到BLE设备;
部分机型需将connectGatt的autoConnect设为false(MIUI、ColorOS);
部分机型需针对Android M及以上的系统版本配置调用BluetoothLeScanner.startScan()方法来搜索BLE设备;
四、附录:连接日志(MIUI)
1 | E/系统版本:: >=6.0 |