本文譯自:http://developer.android.com/guide/topics/connectivity/usb/accessory.html
USB的從屬模式允許用戶連接到專門給Android設備設計的USB主機硬件。這些附件必須遵循在Android從屬模式開發工具文檔中描述的Android從屬模式協議。這個協議允許Android設備即使不作爲USB主機依然能夠跟USB硬件進行交互。當Android設備處於USB從屬模式時,被連接的Android USB附件會作爲主機,提供用於供電的USB總線,並列舉被連接的設備。Android3.1(API Level 12)開始支持USB從屬模式,並且這個功能也能夠被移植到Android2.3.4(API Level 10)中,以便能夠支持更廣泛的設備。
一,選擇正確的USB從屬模式API
儘管USB從屬模式API是在Android3.1中被引入的,但是它們也可以作爲附加類庫在Android2.3.4中使用。因爲這些API是使用一個外部類庫來移植的,所以要導入兩個包才能支持USB從屬模式。根據你想要支持的Android設備,你要使用以下其中一種類庫:
1. com.android.future.usb(兼容性更強):要在Android2.3.4中支持USB從屬模式,就要使用包含USB從屬模式的Google API的附加類庫,這些類庫被包含在這個命名空間中。
Android3.1也支持對這個命名空間的類的引入和調用,以便支持用這個附加類來編寫的應用程序。這個附加類庫是圍繞android.hardware.usb從屬模式API的一個簡單封裝,並且不支持USB主機模式。如果你希望設備能夠最大範圍的支持USB從屬模式,就要使用這個附加類庫,並要導入這個包。重點要注意的是,不是所有的Android2.3.4設備都需要支持USB從屬功能。每個獨立的設備製造商會決定是否支持這種能力,這就是爲什麼要在你的清單文件中聲明的原因 。
2. android.hardware.usb(支持Android 3.1及更新設備):這個命名空間包含了Android3.1中所支持的USB從屬模式的類。這個包被包含在框架API中,因此Android3.1不使用附加類庫來支持USB從屬模式。如果你只關注Android3.1或更新的支持USB從屬模式的硬件設備,那麼就可以在你的清單文件中聲明使用這個包。
二,安裝Google API附加類庫
如果你要安裝這個附加類庫,使用SDK管理器,通過安裝Google APIs Android API 10來完成。關於安裝附加類庫的更多信息,請看“安裝Google APIs附加類庫”。
API概要
因爲該附加的類庫是一個針對框架API的封裝,所以支持USB從屬模式的類都是類似的。即使你正在使用這個附加類庫,你也能夠使用android.hardware.usb的參考文檔。
注意:但是,在附加類庫和框架API之間有一點使用上的差異,你應該注意。
下表介紹了支持USB從屬模式API的類:
類 |
介紹 |
允許你列舉被連接的USB附件,並跟它們通信。 |
|
代表一個USB附件,幷包含了訪問該附件標識信息的方法。 |
Add-on 類庫和平臺API之間在使用上的差異
在使用Google APIs (Google API)add-on類庫 和 平臺API(Android API)之間有兩種使用上的差異。
如果使用add-on類庫,必須用下列方式獲取UsbManager對象:
UsbManager manager=UsbManager.getInstance(this);、
否則,必須用下列方式獲取UsbManager對象:
UsbManager manager=(UsbManager) getSystemService(Context.USB_SERVICE);
在使用Intent過濾器過濾一個被連接的附件時,在傳遞給你的應用程序的Intent對象內部包含了UsbAccessory對象。如果你正在使用add-on類庫,就必須使用以下方式來獲取UsbAccessory對象:
UsbAccessory accessory=UsbManager.getAccessory(intent);
否則,必須使用以下方式來獲取UsbAccessory對象:
UsbAccessory accessory=(UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
三,Android清單要求
下面列出了在使用USB從屬模式API工作之前,需要添加到你的應用程序清單文件中的內容:
1. 因爲不是所有的Android設備都保證支持USB從屬模式API,所以要在<uses-feature>元素中聲明使用android.hardware.usb.accessory功能;
2. 如果你是使用add-on類庫,還要添加<uses-library>元素來指定要使用的com.android.future.usb.accessory類庫;
3. 如果你是使用add-on類庫,則要設置該應用程序的SDK的最小版本號是API Level 10,如果使用的是android.hardware.usb包,則最小版本號是API Level 12.
4. 如果你希望獲得連接USB附件的通知,就要在你的主Activity中給<intent-filter>和<meta-data>元素指定android.hardware.usb.action.USB_ACCESSORY_ATTACHED類型的Intent。<meta-data>元素要指向一個外部的XML資源文件,該文件中聲明瞭有關你想要檢測的附件的標識信息。
在這個XML資源文件中,用<usb-accessory>元素來聲明你想要過濾的附件。每個<usb-accessory>元素能夠有以下屬性:
manufacturer
model
version
該資源文件保存在res/xml目錄中。資源文件名稱(不含.xml擴展名)必須跟<meta-data>元素中指定的名稱相同。下例顯示該XML資源文件的格式:
清單和資源文件的示例
下例顯示了清單和它對應的資源文件:
<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>
在這種情況下,以下資源文件應該被保存在res/xml/accessory_filter.xml文件中,並且指定了要過濾的附件所對應的模式、製造商和版本號。USB附件會把這些屬性發送給Android設備:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessorymodel="DemoKit" manufacturer="Google" version="1.0"/> //發送命令的時候一定要對應
</resources>
四,跟附件一起工作
當用戶把USB附件連接到Android設備時,Android系統能夠判斷你的應用程序是否對接入的附件感興趣。如果感興趣,你能夠跟期望的附件建立通信。以下是你的應用程序要做的事情:
1. 通過使用過濾附件設備接入事件的Intent過濾器或列舉已經接入的附件設備來發現對應的附件設備;
2. 如果不是已知的附件設備,就要詢問用戶是否允許跟該附件設備通信;
3. 通過讀寫對應接口端點上的數據來跟附件設備通信。
發現附件設備
你的應用程序既可以通過用戶接入附件設備時的Intent通知,也可以通過列舉已經接入的附件設備來發現你想要的附件設備。
如果你想要你的應用能夠自動檢測到期望的附件設備,那麼使用Intent過濾器是有用的。
如果你想要獲取所有已連接的附件設備列表,或者你的應用程序並不過濾特定的Intent,那麼使用列舉的方法是有用的。
使用Intent過濾器
你可以指定一個android.hardware.usb.action.USB_ACCESSORY_ATTACHED類型的Intent過濾器,以便你的應用程序能夠發現特殊的USB附件設備。跟這個Intent過濾器一起,你還需要指定一個包含USB附件設備屬性的資源文件,這些屬性包括:製造商、模式、版本。
下例顯示瞭如何聲明一個Intent過濾器:
<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對象.
使用add-on類庫的情況:
UsbAccessory accessory=UsbManager.getAccessory(intent);
使用平臺API的情況:
UsbAccessory accessory=(UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
列舉附件設備
在應用程序運行時,你能夠讓你的應用程序列舉出已經識別出的所有附件設備。
使用getAccessoryList()方法來獲取所有已連接的USB附件設備:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAcccessoryList();
注意:當前,一次只支持連接一個附件設備,但是在未來,該API被設計成要支持多個附件設備。
獲得跟附件通信的權限
在跟USB附件設備進行通信之前,你的應用程序必須要從用戶那裏獲得許可。
注意:如果你的應用程序使用Intent過濾器來發現那些被接入的附件設備,而且用戶允許你的應用程序處理該Intent對象,那麼會自動的接收許可。否則,在連接附件設備之前,你必須明確的申請接入許可。
明確的申請接入許可,在有些情況下是必須的,如在你的應用程序列舉出了已經接入的附件設備,並要跟其中之一進行通信時。在試圖跟其通信之前,你必須檢查訪問該附件設備的權限。否則,如果用戶拒絕了你訪問該附件設備的請求,你會收到一個運行時錯誤。
要明確的獲得許可,首先就要創建一個廣播接收器。這個接收器會監聽在調用requestPermission()方法時獲得的用於廣播的Intent對象。調用requestPermission()方法時會顯示一個對話框,向用戶請求連接附件設備的權限。以下代碼顯示瞭如何創建這個廣播接收器:
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()方法中註冊該廣播接收器:
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);
當用戶應答了該對話框,你的廣播接收器會收到一個包含了EXTRA_PERMISSION_GRANTED類型附件的Intent對象,它用一個布爾值來代表回答結果。在連接該附件設備之前,要檢查該附件的值是否是true。
跟一個附件設備通信
通過使用UsbManagerd對象獲取一個文件描述符來跟附件設備通信,你能夠使用這個文件描述符建立讀寫數據的輸入和輸出流。該流代表了附件設備的輸入和數據塊端點。你應該在另外一個線程中建立設備和附件之間的通信,以便不至於阻塞主UI線程。下例顯示瞭如何打開跟附件設備的通信:
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();
}
}
在線程的run()方法中,你可以使用FileInputStream和FileOutputStream對象來讀寫附件設備。在使用FileInputStream對象從附件設備中讀取數據時,要確保有足夠大的緩存來存儲USB包數據。Android從屬模式協議支持的最大包緩存是16384個字節,因此你可以簡單的始終把你的緩存聲明成這個尺寸。
注意:在底層,對於全速USB附件設備的64字節包和高速USB附件設備的512字節包,Android從屬模式協議會簡單的把這個兩個數據打包成一個邏輯包。
終止跟附件設備的通信
在完成跟附件設備的通信或附件設備被拔出時,你要調用close()方法來關閉被打開的文件描述符。創建以下廣播接收器,來監聽分離事件:
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
}
}
}
};
在應用程序中創建的廣播接收器,而且不在清單文件中,這樣的廣播接收器允許你的應用程序只在運行時處理分離事件。這種方式,分離事件只發送給當前正在運行的應用程序,並不會廣播給所有的應用程序。