异常描述
在蓝牙HID的开发过程中,使用红米K30手机 MIUI12.5(Android 11) 系统,打算将手机打造成蓝牙外设(键盘、触摸板、游戏手柄等)。首先调用下面的方式与系统蓝牙HID服务绑定:
mBtAdapter.getProfileProxy(mContext, mServiceListener, BluetoothProfile.HID_DEVICE);
出现下面的错误信息
Could not bind to Bluetooth Service with Intent { act=android.bluetooth.IBluetoothHidDevice }
上述报错后就不会与系统蓝牙HID服务绑定,从而无法得到 BluetoothHidDevice
进行注册。而使用 BluetoothProfile.A2DP
绑定时则无此问题。
源码分析
方法 BluetoothAdapter.getProfileProxy()
的代码如下:
public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
int profile) {
if (context == null || listener == null) {
return false;
}
if (profile == BluetoothProfile.HEADSET) {
BluetoothHeadset headset = new BluetoothHeadset(context, listener, this);
return true;
} else if (profile == BluetoothProfile.A2DP) {
...代码省略
} else if (profile == BluetoothProfile.HID_DEVICE) {
BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener, this);
return true;
} else if (profile == BluetoothProfile.HEARING_AID) {
...代码省略
}
根据 BluetoothProfile.HID_DEVICE
创建 BluetoothHidDevice
,BluetoothHidDevice
构造函数中会调用 doBind()
与系统蓝牙应用的蓝牙HID服务进行绑定,在Android 9 源码如下:
/frameworks/base/core/java/android/bluetooth/BluetoothHidDevice.java
//BluetoothHidDevice.java
boolean doBind() {
Intent intent = new Intent(IBluetoothHidDevice.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
mContext.getUser())) {
Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
return false;
}
Log.d(TAG, "Bound to HID Device Service");
return true;
}
以 android.bluetooth.IBluetoothHidDevice
作为 Action 与系统应用的蓝牙服务绑定。
Intent.resolveSystemService()
方法查看系统服务:
@UnsupportedAppUsage
public @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
@PackageManager.ComponentInfoFlags int flags) {
if (mComponent != null) {
return mComponent;
}
List<ResolveInfo> results = pm.queryIntentServices(this, flags);
if (results == null) {
return null;
}
ComponentName comp = null;
for (int i=0; i<results.size(); i++) {
ResolveInfo ri = results.get(i);
if ((ri.serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
continue;
}
ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
ri.serviceInfo.name);
if (comp != null) {
throw new IllegalStateException("Multiple system services handle " + this
+ ": " + comp + ", " + foundComp);
}
comp = foundComp;
}
return comp;
}
Intent.resolveSystemService()
的方法是系统用于解决系统应用程序服务意向的特殊功能。如果存在与Intent的多个潜在匹配,则引发异常。如果没有匹配项,则返回null。
查找不到 android.bluetooth.IBluetoothHidDevice
作为 Action的系统蓝牙HID服务将 ComponentName
为 null ,因此出现 “Could not bind to Bluetooth HID Device Service with Intent { act=android.bluetooth.IBluetoothHidDevice }” 的报错。
Android 10 不直接调用 doBind()
方法进行绑定,而是增加 BluetoothProfileConnector
进行绑定,源码如下:
/frameworks/base/core/java/android/bluetooth/BluetoothProfileConnector.java
//BluetoothProfileConnector.java
private boolean doBind() {
synchronized (mConnection) {
if (mService == null) {
logDebug("Binding service...");
try {
Intent intent = new Intent(mServiceName);
ComponentName comp = intent.resolveSystemService(
mContext.getPackageManager(), 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
UserHandle.CURRENT_OR_SELF)) {
logError("Could not bind to Bluetooth Service with " + intent);
return false;
}
} catch (SecurityException se) {
logError("Failed to bind service. " + se);
return false;
}
}
}
return true;
}
Android 9 和 Android 10 的错误打印不一致,Android 9 为 “Could not bind to Bluetooth HID Device Service with”, 而 Android 10 以上为 "Could not bind to Bluetooth Service with "。
但不管哪个版本都是与系统蓝牙应用的HID服务进行绑定。
packages\apps\Bluetooth\src\com\android\bluetooth\hid\HidDeviceService.java
系统中找不到蓝牙服务、无法绑定、或者蓝牙应用异常同样也无法正常获取服务注册蓝牙HID。
判断设备是否支持蓝牙HID
根据上述 Intent.resolveSystemService()
的方法,第三方应用可以执行下面方法判断设备是否有蓝牙HID服务来,返回 true 代表支持第三方应用注册蓝牙HID,反之当前设备的系统不支持。
public boolean isSupportBluetoothHid() {
Intent intent = new Intent("android.bluetooth.IBluetoothHidDevice");
List<ResolveInfo> results = pm.queryIntentServices(intent, 0);
if (results == null) {
return false;
}
ComponentName comp = null;
for (int i=0; i<results.size(); i++) {
ResolveInfo ri = results.get(i);
if ((ri.serviceInfo.applicationInfo.flags& ApplicationInfo.FLAG_SYSTEM) == 0) {
continue;
}
ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
ri.serviceInfo.name);
if (comp != null) {
throw new IllegalStateException("Multiple system services handle " + this
+ ": " + comp + ", " + foundComp);
}
comp = foundComp;
}
return comp != null;
}
解决方案
红米K30手机出现此问题可能是本身小米系统移植存在差异导致的,升级MIUI13(Android 12)后无此报错。
由于没有小米系统的源码,无法进行分析修改。只能尝试升级其他系统版本,根据上述 判断设备是否支持蓝牙HID 来识别当前系统的支持情况。
如果当前系统支持HID,那么通过调用 BluetoothHidDevice.registerApp()
方法 将回调 onAppStatusChanged()
状态 registered 为 true 时,如下:
onAppStatusChanged: pluggedDevice:null registered:true
这代表注册成功,通过另一个设备查找你的手机进行配对,需要调用 setScanMode
的模式让另一设备发现并连接,具体方式可查看另一篇博文 蓝牙HID——将android设备变成蓝牙键盘(BluetoothHidDevice)。配对成功后即可充当HID设备控制另一个终端设备。
在 Android 9 之前只有系统应用才可以注册 HID ,Android 9 之前的系统版本参考此篇博客 Android 蓝牙开发(三)蓝牙Hid 开发