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模式開發的套路是一致,只是配置的內容和呼叫的方法不一樣而已,在本小節的講解中將不進一步細分,僅作區別提示。
2.1 配置AndroidManifest.xml檔案
對於Android應用來說,它們是預設不具備USB開發特性的,也就是說,無論是USB外設還是USB配件,當插入到Android裝置時應用都不會對其作出響應。如果需要我們的應用支援USB開發,就需要在清單檔案中使用<uses-feature/>
<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模式請求授權介面如下: