最近蓝牙手环在Android9.0的手机上回连总是连接不上,老在扫描的地方onScanFailed里回调errorCode=2,errorCode共有下面4种情况:
errorCode=1;Fails to start scan as BLE scan with the same settings is already started by the app.
errorCode=2;Fails to start scan as app cannot be registered.
errorCode=3;Fails to start scan due an internal error
errorCode=4;Fails to start power optimized scan as this feature is not supported
发现BluetoothGatt.close()不管用,关闭重启手机蓝牙或者重启App是可以重新连接蓝牙,但是咱不能一碰到这个问题就让用户去重启App或者重启手机蓝牙。经过查阅各种资料,说问题的原因是:
手机在startScan扫描的过程中还没来得及stopScan ,就被系统强制杀掉了, 导致mClientIf未被正常释放,实例和相关蓝牙对象已被残留到系统蓝牙服务中, 打开app后又重新初始化ScanCallback多次被注册,导致每次的扫描mClientIf的值都在递增, 于是mClientIf的值在增加到一定程度时(最大mClientIf数量视国产系统而定 不做深究),onScanFailed 返回了 errorCode =2 。
解决办法是:重新刷新手机的蓝牙状态
public static boolean releaseAllScanClient() {
try {
Object mIBluetoothManager = getIBluetoothManager(BluetoothAdapter.getDefaultAdapter());
if (mIBluetoothManager == null) return false;
Object iGatt = getIBluetoothGatt(mIBluetoothManager);
if (iGatt == null) return false;
Method unregisterClient = getDeclaredMethod(iGatt, "unregisterClient", int.class);
Method stopScan;
int type;
try {
type = 0;
stopScan = getDeclaredMethod(iGatt, "stopScan", int.class, boolean.class);
} catch (Exception e) {
type = 1;
stopScan = getDeclaredMethod(iGatt, "stopScan", int.class);
}
for (int mClientIf = 0; mClientIf <= 40; mClientIf++) {
if (type == 0) {
try {
stopScan.invoke(iGatt, mClientIf, false);
} catch (Exception ignored) {
}
}
if (type == 1) {
try {
stopScan.invoke(iGatt, mClientIf);
} catch (Exception ignored) {
}
}
try {
unregisterClient.invoke(iGatt, mClientIf);
} catch (Exception ignored) {
}
}
stopScan.setAccessible(false);
unregisterClient.setAccessible(false);
// BLESupport.getDeclaredMethod(iGatt, "unregAll").invoke(iGatt);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@SuppressLint("PrivateApi")
public static Object getIBluetoothGatt(Object mIBluetoothManager) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method getBluetoothGatt = getDeclaredMethod(mIBluetoothManager, "getBluetoothGatt");
Object object=new Object();
try {
object=getBluetoothGatt.invoke(mIBluetoothManager);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return object;
}
@SuppressLint("PrivateApi")
public static Object getIBluetoothManager(BluetoothAdapter adapter) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method getBluetoothManager = getDeclaredMethod(BluetoothAdapter.class, "getBluetoothManager");
return getBluetoothManager.invoke(adapter);
}
public static Field getDeclaredField(Class> clazz, String name) throws NoSuchFieldException {
Field declaredField = clazz.getDeclaredField(name);
declaredField.setAccessible(true);
return declaredField;
}
public static Method getDeclaredMethod(Class> clazz, String name, Class>... parameterTypes) throws NoSuchMethodException {
Method declaredMethod = clazz.getDeclaredMethod(name, parameterTypes);
declaredMethod.setAccessible(true);
return declaredMethod;
}
public static Field getDeclaredField(Object obj, String name) throws NoSuchFieldException {
Field declaredField = obj.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
return declaredField;
}
public static Method getDeclaredMethod(Object obj, String name, Class>... parameterTypes) throws NoSuchMethodException {
Method declaredMethod = obj.getClass().getDeclaredMethod(name, parameterTypes);
declaredMethod.setAccessible(true);
return declaredMethod;
}
调用上面的反射还不行,因为Android9.0对SDK的反射会抛出异常,弹出下面的警告窗口:
所以需要在App初始化的时候调用下面的反射来关闭Android9.0的弹窗:
@SuppressLint("PrivateApi") private void closeAndroidPDialog(){ try { Class aClass = Class.forName("android.content.pm.PackageParser$Package"); Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class); declaredConstructor.setAccessible(true); } catch (Exception e) { e.printStackTrace(); } try { Class cls = Class.forName("android.app.ActivityThread"); Method declaredMethod = cls.getDeclaredMethod("currentActivityThread"); declaredMethod.setAccessible(true); Object activityThread = declaredMethod.invoke(null); Field mHiddenApiWarningShown = cls.getDeclaredField("mHiddenApiWarningShown"); mHiddenApiWarningShown.setAccessible(true); mHiddenApiWarningShown.setBoolean(activityThread, true); } catch (Exception e) { e.printStackTrace(); } }