很早之前就想对Android USB的两种模式作个小结,但是一直没有空去搞,毕竟USB这块应该属于冷门方向,并且应用层能够做的比较少也很简单。最近刚好在做大疆无人机的二次开发,想着对USB连接检测这块做下优化,毕竟Android终端主要是通过USB连接到远程控制器来与无人机进行交互。但与AndroidUSBCamera一文中提及的USB Camera场景不同,无人机使用的是Android终端的accessory模式,而USB Camera使用的是Android终端的host模式。为此,本文将详细讲解Android系统中这两种USB模式。
1. Android USB模式
Android的USB接口有两种模式,即主机(host)模式
和附件(accessory)模式
,分别用于支持接入各种USB外设和USB配件。它们的区别如下:
1.1 host模式
在host模式中,Android设备将充当主机(控制读写、枚举连接的设备),并为USB外设
提供电源,常见的USB外设有数码相机、USB Camera、键盘、鼠标、U盘以及游戏控制器等。
host模式连接示例图如下:
1.2 accessory模式
在accessory模式中,USB配件
将充当主机(控制读写、枚举连接的设备),并为Android设备提供电源,从而使得在Android设备在无法充当USB主机的情况下仍然可以与USB硬件交互。所谓USB配件,是指专为Android设备设计的USB主机配件,该配件必须遵从Android Accessory Development Kit文档中列举出来的Android配件协议,常见的USB配件有无人机远程控制器、音乐设备、电话等。
accessory模式连接示例图如下:
2. Android USB模式开发详解
在Android SDK中,与USB相关的API主要位于路径名为android.hardware.usb的包中,由它们提供对USB应用开发的支持。但是,在使用这些APIs之前,我们需要在AndroidManifest.xml清单文件中作相关的配置,然后再通过API获取USB设备相关信息和实现与其之间的数据交互。由于host模式和accessory模式开发的套路是一致,只是配置的内容和调用的方法不一样而已,在本小节的讲解中将不进一步细分,仅作区别提示。
2.1 配置AndroidManifest.xml文件
对于Android应用来说,它们是默认不具备USB开发特性的,也就是说,无论是USB外设还是USB配件,当插入到Android设备时应用都不会对其作出响应。如果需要我们的应用支持USB开发,就需要在清单文件中使用<uses-feature/>
标签来声明本应用应该具有哪种特性。除此之外,如果我们希望自己的应用在USB外设或USB配件连接到Android设备时能够接收到通知,可以在Android的组件中配置<intent-filter/>
和<meta-data/>
标签,其中,<meta-data/>
元素指向一个外部XML资源文件,用于指定需要检测的设备信息,即对检测设备进行过滤,如果该文件没填写任何信息,则说明允许所有USB外设或USB配件。具体实现如下:
(1) 声明特性
- host特性
<uses-feature android:name="android.hardware.usb.host"/>
- accessory特性
<uses-feature android:name="android.hardware.usb.accessory"/>
(2) 通知、过滤
- host模式
<activity
android:name="com.jiangdg.aircraft.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!--接收USB外设接入时通知-->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<!--过滤USB外设设备-->
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
其中,device_filter.xml时res/xml目录下的资源文件,用于指定要过滤设备的属性。示例如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device class="255" product-id="5678" protocol="1" subclass="66" vendor-id="1234"/>
</resources>
- accessory模式
<activity
android:name="com.jiangdg.aircraft.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!--接收USB配件接入时通知-->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<!--过滤USB配件设备-->
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
</activity>
其中,accessory_filter.xml时res/xml目录下的资源文件,用于指定要过滤设备的属性。示例如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory model="T600" manufacturer="DJI"/>
</resources>
2.2 USB设备连接与通信
无论是host模式还是accessory模式,对USB外设或USB配件的检测过程基本一致,只是调用不同的方法罢了。类似Window,USB管理的核心逻辑也是在系统服务中实现的,Android系统对外提供了一个名为UsbManager
的接口用于外界访问USB系统服务,实质上,从应用进程到系统进程之间的访问是一次IPC过程。
总的来说,关于USB应用的开发主要分为如下几步:
(1) 通过Context.getSystemService()方法得到USB系统服务对外管理接口UsbManager;
(2) 枚举所有已连接的USB外设或USB配件;
(3) 获取已连接的USB外设或USB配件,请求用户授予其通信权限;
(4) 实现一个广播接收器用于接收用户授权的结果;
(5) 实现Android设备与USB外设或USB配件进行数据通信(本文暂时不涉及,故不介绍,详情见APIs)。
- host模式
// 自定义action
private static final String ACTION_USB_PEMISSION = "com.teligen.aircraft.usb.permission";
// 1. 获取UsbManager
UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
// 2. 枚举所有已连接的USB外设
// 其中,UsbDevice对象对应于一个USB外设,通过该对象可获得设备详情
HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
// 3. 请求用户授权通信权限,遍历所有USB外设设备
// 其中,PendingIntent用于存储用户的授权结果
if(deviceList != null) {
Iterator<usbdevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
UsbDevice device = deviceIterator.next();
Intent intent = new Intent(ACTION_USB_PEMISSION);
PendingIntent pIntent = PendingIntent.getBroadcast(this,0,intent,0);
if(mUsbManager.hasPermission(device)) {
// 已经授权,无需再次请求授权
} else {
// 请求用户授权
mUsbManager.requestPermission(device,pIntent);
}
}
}
// 4.注册USB权限授予情况广播接收器
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_USB_PEMISSION);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(ACTION_USB_PEMISSION.equals(action)) {
if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,false)){
// 用户授权成功
} else {
// 用户拒绝授权
}
}
}
},intentFilter);
// 注释:当然我们也可以在onReceive中获取具体的USB外设对象
// UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
- accessory模式
// 自定义action
private static final String ACTION_USB_PEMISSION = "com.teligen.aircraft.usb.permission";
// 1. 获取UsbManager
UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
// 2. 枚举所有已连接的USB配件
// 其中,UsbAccessory对象对应于一个USB配件设备,通过该对象可获得设备详情
UsbAccessory[] accessoryList = mUsbManager.getAccessoryList();
// 3. 请求用户授权通信权限,这里只请求一个设备
// 其中,PendingIntent用于存储用户的授权结果
if(accessoryList != null && accessoryList.length > 0) {
UsbAccessory accessory = accessoryList[0];
Intent intent = new Intent(ACTION_USB_PEMISSION);
PendingIntent pIntent = PendingIntent.getBroadcast(this,0,intent,0);
if(mUsbManager.hasPermission(accessory)) {
// 已经授权
...
} else {
// 请求用户授权
mUsbManager.requestPermission(accessory,pIntent);
}
}
// 4.注册USB权限授予情况广播接收器
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_USB_PEMISSION);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(ACTION_USB_PEMISSION.equals(action)) {
if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,false)){
// 用户授权成功
} else {
// 用户拒绝授权
}
}
}
},intentFilter);
// 注释:当然我们也可以在onReceive中获取具体的USB配件对象
// UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
accessory模式请求授权界面如下: