1. 程式人生 > >Android USB開發小結:host模式與accessory模式

Android USB開發小結:host模式與accessory模式

很早之前就想對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模式開發的套路是一致,只是配置的內容和呼叫的方法不一樣而已,在本小節的講解中將不進一步細分,僅作區別提示。

usb api

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模式請求授權介面如下: