以下內容翻譯自USB Accessory
一、USB accessory和USB host
Android系統對各種USB設備和Android USB配件(這些配件遵守Android accessory protocol)的支持基於兩種模式:USB accessory和USB host。在USB accessory模式下,外部USB設備作爲usb host,這些外部設備有:機器人控制器、擴展塢、音頻設備、讀卡器等。這個模式允許沒有host能力的Android設備跟USB設備(此設備必須支持Android accessory communication protocol)交互。在USB host模式下,Android設備作爲host。支持的外部設備有數碼相機、鍵盤、鼠標、遊戲主機等。
圖1顯示了兩種模式的不同。Android設備在host模式時,作爲USB host,並驅動通信總線。Android設備在accessory模式時,外部USB設備作爲host並驅動數據總線。
Android從Android 3.1(API 12)開始支持這兩種模式。USB accessory模式可以通過附加的library兼容到Android 2.3.4(API 10),以支持更多的設備。設備廠商可以自主選擇是否將此library添加到系統中。
注意:對兩種模式的支持,最終取決於設備硬件,而不是平臺level。你可以用<uses-feature>
標籤過濾掉不支持這兩種模式的設備
二、USB accessory
USB accessory模式允許用戶連接到針對Android特別設計過的USB host設備。這些設備必須支持Android accessory development kit文檔中描述的Android accessory protocol協議。這個協議使不能作爲USB host的Android設備可以和USB設備通信。當Android設備處於USB accessory模式時,連接的USB設備作爲host,爲usb總線提供能量,並且可以枚舉已連接設備。Android從Android 3.1(API 12)開始支持usb accessory模式,USB accessory模式可以通過附加的library兼容到Android 2.3.4(API 10),以支持更多的設備。
三、選擇正確的usb accessory API
雖然USB accessory API從Android 3.1開始支持,但是你也可以通過Google API附加的library將它兼容到Android 2.3.4。由於這些API是通過外部library加入的,所以需要導入兩個包。根據你想要支持的Android設備,你可以選擇下面兩個包中的一個:
com.android.future.usb:這個包中的內容是爲了將USB accessory兼容到Android 2.3.4在Google API附加的library。Android 3.1也可以導入這個包並調用其中的類。這個library是對android.hardware.usb中的accessory API的輕量包裝,且不支持USB host模式。如果你想要兼容更多的設備,你可以將這個library導入到項目中。需要注意的是,不是所有的Android 2.3.4設備都支持USB accessory特性。設備廠商可以自主選擇是否支持此特性,所以你最好在manifest文件中聲明你需要此特性。
android.hardware.usb:這個包中的內容是Android 3.1加入的支持USB accessory的類。這個包是框架API的一部分,所以Android 3.1不需要導入額外的library就可以支持USB accessory。如果你只需要支持Android 3.1及更高的設備,你可以使用這個包,並在manifest文件中聲明特性。
四、API簡介
由於附加的library是對框架API的封裝,所以這些類都很相似。你甚至可以直接使用android.hardware.usb的文檔使用附加library。
注意:在附加library和框架API之間還是存在一些細微差別的
下表描述了USB accessory API相關的類:
Class | 描述 |
---|---|
UsbManager | 允許你枚舉已連接設備並通信 |
UsbAccessory | 表示一個usb accessory,包含訪問識別信息的方法 |
4.1 附加library和框架API之間的差別
附加library和框架API之間有兩個差別。
如果你使用附加library,獲取UsbManger對象方式如下:
UsbManager manager = UsbManager.getInstance(this);
如果你使用框架API,獲取UsbManager對象方式如下:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
當你用intent filter過濾已連接的accessory時,可以從傳遞到你的應用的intent中獲取UsbAccessory對象。而如果你使用的是附加的library的話,你就需要用以下方式獲取UsbAccessory對象了:
UsbAccessory accessory = UsbManager.getAccessory(intent);
如果你使用框架API,則代碼如下:
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
五、Android manifest設置
下面描述的是在使用usb accessory API前需要在manifest文件中添加的東西:
- 由於不是所有設備都支持USB accessory API,所以請用標籤聲明你的應用會使用android.hardware.usb.accessory特性。
- 如果你使用的是附加的library,請添加
<uses-librara>
標籤指定com.android.future.usb.accessory。 - 如果你使用的是附加的library,請將SDK最小值設爲API 10,如果使用的是框架API,請設爲12。
如果你希望你的應用可以收到附加usb accessory的信息,可以在activity中指定
<intent-filter>
和<meta-data>
以接收指定intent。其中action爲android.hardware.usb.action.USB_ACCESSORY_ATTACHED,<meta-data>
指定一個包含你想要知道的識別信息的外部XML文件。
XML文件中需要聲明你想要過濾的accessory的信息(用<usb-accessory>
標籤)。每個<usb-accessory>
標籤都包含以下屬性:- manufacturer
- model
- version
將這個resource文件保存在res/xml/目錄。resource文件名稱必須與
<meta-data>
中(沒有.xml後綴)所寫一致。
下面是一個manifest文件和對應resource文件示例:
<manifest ...>
<uses-feature android:name="android.hardware.usb.accessory" />
<uses-sdk android:minSdkVersion="<version>" />
...
<application>
<uses-library android:name="com.android.future.usb.accessory" />
<activity ...>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
</activity>
</application>
</manifest>
resource文件保存爲res/xml/accessory_filter.xml,並指定model、manufacturer、version:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>
六、使用accessory
當用戶將USB配件連接到Android設備時,Android系統會判斷你的應用是否對此連接的配件感興趣。如果是的話,你就可以跟此配件通信了。要聲明你的應用對何種附件感興趣,你可以:
- 通過intent filter獲取配件相關事件從而發現已連接配件,或直接枚舉已連接配件
- 向用戶申請跟配件通信的權限
- 在適當的接口上跟配件用讀寫操作通信
6.1 發現配件
當用戶連接某個配件時,你的應用可以通過intent filter獲取到它。你也可以直接枚舉已連接的配件。如果你希望應用可以自動發現指定配件,你可以使用intent filter。如果你希望獲取已連接設備列表或不想使用intent filter,可以使用枚舉的方法。
6.1.1 使用intent filter
你可以使用添加了android.hardware.usb.action.USB_ACCESSORY_ATTACHED的intent filter指定你想要發現的USB配件。在這個intent filter中,你需要指定一個resource文件,在這個文件中包含你想要的USB配件的一些屬性,如廠商、型號、版本。當用戶將符合你的需求的配件連接到Android設備時,系統會通過intent通知你。
下面的例子顯示瞭如何聲明intent filter:
<activity ...>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter" />
</activity>
下面的例子顯示瞭如何聲明你感興趣的配件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>
在你的activity中,你可以用以下代碼從intent中獲取UsbAccessory:
UsbAccessory accessory = UsbManager.getAccessory(intent);
如果你使用的是框架API,則是使用以下代碼:
UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
6.1.2 枚舉配件
你可以在應用運行時枚舉到已註冊的配件。
使用getAccessoryList()方法可以獲取表示所有已連接usb配件的數組:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAcccessoryList();
注意:當前版本,每次只能獲取一個已連接配件,在將來,會支持多配件獲取
6.2 獲取通信權限
在與USB配件通信前,你的應用必須向用戶申請權限。
注意:如果你的應用使用intent filter方式發現配件,那麼在用戶允許你的應用處理相關intent時已經自動給予你的應用相關權限。如果使用的是其他方式,那麼必須在連接配件前明確的申請權限。
顯式申請權限在很多情況下都是必要的,比如你的應用要枚舉已連接設備時。你必須在訪問配件前檢查相關權限,否則可能會收到運行時異常(如果用戶拒絕給予你的應用權限)。
要顯式的獲取權限,首先需要創建一個broadcast receiver。這個receiver會監聽調用requestPermission()方法時發送的廣播。調用requestPermission()方法將會顯示一個向用戶申請權限的對話框。下列代碼爲創建broadcast receiver的示例:
private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(accessory != null){
//call method to set up accessory communication
}
}
else {
Log.d(TAG, "permission denied for accessory " + accessory);
}
}
}
}
};
在你的activity的onCreate()方法中註冊broadcast receiver:
UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
...
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
調用requestPermission()方法向用戶申請權限:
UsbAccessory accessory;
...
mUsbManager.requestPermission(accessory, mPermissionIntent);
申請權限的對話框顯示以後,用戶的響應會通過廣播發送給你的broadcast receiver,廣播的intent包含名爲“EXTRA_PERMISSION_GRANTED”的extra,其是一個boolean,表示用戶是否給予權限。
6.3 與配件通信
你可以通過UsbManager獲取file descriptor,然後通過輸入輸出流跟此descriptor通信。你應該在子線程跟配件通信,不要阻塞主線程。下面爲打開配件並通信的示例:
UsbAccessory mAccessory;
ParcelFileDescriptor mFileDescriptor;
FileInputStream mInputStream;
FileOutputStream mOutputStream;
...
private void openAccessory() {
Log.d(TAG, "openAccessory: " + accessory);
mFileDescriptor = mUsbManager.openAccessory(mAccessory);
if (mFileDescriptor != null) {
FileDescriptor fd = mFileDescriptor.getFileDescriptor();
mInputStream = new FileInputStream(fd);
mOutputStream = new FileOutputStream(fd);
Thread thread = new Thread(null, this, "AccessoryThread");
thread.start();
}
}
在“thread”的run()方法中,你可以用FileInputStream和FileOutputStream進行讀寫。當你用FileInputStream從配件中讀數據時,請確保你使用的buffer足夠存儲USB數據包。Android accessory protocol支持的buffer大小最大爲16384字節,所以你可以簡單的將你的buffer設置爲這個大小。
注意:在底層,USB full-speed配件的數據包爲64字節,USB high-speed配件的數據包爲512字節。Android accessory protocol會將兩者統一成大小合乎邏輯的包。
6.4 終止與配件的通信
當你的通信結束或者配件與Android設備分離時,你可以用close()方法關閉file descriptor。要監聽設備斷開事件,可以用如下代碼創建broadcast receiver:
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (accessory != null) {
// call your method that cleans up and closes communication with the accessory
}
}
}
};