Android USB开发小结:host模式与accessory模式

时间:2024-03-19 18:41:20

很早之前就想对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模式连接示例图如下:
Android USB开发小结:host模式与accessory模式

1.2 accessory模式

 在accessory模式中,USB配件将充当主机(控制读写、枚举连接的设备),并为Android设备提供电源,从而使得在Android设备在无法充当USB主机的情况下仍然可以与USB硬件交互。所谓USB配件,是指专为Android设备设计的USB主机配件,该配件必须遵从Android Accessory Development Kit文档中列举出来的Android配件协议,常见的USB配件有无人机远程控制器、音乐设备、电话等。
 accessory模式连接示例图如下:

Android USB开发小结:host模式与accessory模式

2. Android USB模式开发详解

 在Android SDK中,与USB相关的API主要位于路径名为android.hardware.usb的包中,由它们提供对USB应用开发的支持。但是,在使用这些APIs之前,我们需要在AndroidManifest.xml清单文件中作相关的配置,然后再通过API获取USB设备相关信息和实现与其之间的数据交互。由于host模式和accessory模式开发的套路是一致,只是配置的内容和调用的方法不一样而已,在本小节的讲解中将不进一步细分,仅作区别提示。

Android 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模式请求授权界面如下:

Android USB开发小结:host模式与accessory模式