以下內容翻譯自USB Host
一、usb host
當你的Android設備處於usb host模式時,它作爲usb host,驅動總線,可以枚舉已連接的usb設備。usb host模式從Android 3.1開始支持。
二、API簡介
下表描述了android.hardware.usb包中的usb host相關API:
類 | 描述 |
---|---|
UsbManager | 包含枚舉、通信接口 |
UsbDevice | 表示一個已連接設備,包含訪問已註冊信息、接口、endpoint的方法 |
UsbInterface | 表示usb設備的接口,定義了此設備支持的功能集合。一個設備可以有多個接口 |
UsbEndpoint | 表示接口endpoint,即接口的通信通道。一個接口可以有多個endpoint,通常有輸入、輸出兩個endpoint以支持雙向通信 |
UsbDeviceConnection | 表示一個連接,可以在endpoint上面傳輸數據。這個類中包含同步/異步讀寫數據的方法 |
UsbRequest | 表示通過UsbDeviceConnection進行的異步請求 |
UsbConstants | 一些usb相關常量 |
大多數情況下,當你與usb設備通信時,以上的類都會被用到(UsbRequest只在進行異步通信時需要)。通常情況下,建立連接的順序爲:通過UsbManager搜索目標UsbDevice,然後尋找適當的UsbInterface和對應的UsbEndpoint,找到endpoint以後,就可以打開一個UsbDeviceConnection進行通信了。
三、Android manifest設置
以下爲在使用usb host相關API前需要在manifest文件中添加的內容:
- 由於不是所有的Android設備都支持usb host API,所以需要用
<uses-feature>
指定需要android.hardware.usb.host - 設置最小SDK爲API 12。usb host API不支持更早的系統版本
如果你希望你的應用可以在usb設備接入時收到通知,可以爲activity指定
<intent-filter>
和<meta-data>
。其中filter的action爲android.hardware.usb.action.USB_DEVICE_ATTACHED
,<meta-data>
指定一個外部XML文件,此文件指定你關注的設備信息。
在這個XML文件中,需要添加<usb-device>
標籤。下面描述的是<usb-device>
支持的屬性。通常情況下,你可以使用vendor-id和product-id指定你想要的某個設備,使用class、subclass、protocol指定你想要的一組設備(如存儲設備或數碼相機)。你可以指定下列屬性中的一個或多個。不指定任何屬性將會匹配到所有usb設備。- vendor-id
- product-id
- class
- subclass
- protocol
將此XML文件保存在res/xml路徑。XML文件的名稱必須與
<meta-data>
中指定的(沒有.xml後綴)一致
3.1 示例
下面是manifest文件的示例:
<manifest ...>
<uses-feature android:name="android.hardware.usb.host" />
<uses-sdk android:minSdkVersion="12" />
...
<application>
<activity ...>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
</application>
</manifest>
在這個例子中,下面的xml文件將保存爲res/xml/device_filter.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
</resources>
四、與設備交互
當有usb設備連接到Android設備時,Android系統會判斷你的應用是否對此usb設備感興趣。如果是的話,你可以跟此usb設備通信。要實現此功能,你需要:
- 當usb設備接入時通過intent filter發現它,或者直接枚舉已連接的usb設備
- 向用戶申請權限
- 使用適當的endpoint接口與usb設備通信
4.1 發現設備
你可以通過兩種方式發現usb設備,一是用intent filter在usb接入時由系統通知,一是用枚舉接口直接獲取已連接設備。如果你希望應用可以自動獲取目標設備,可以使用intent filter的方式。如果你希望獲取所有已連接設備列表,可以使用枚舉接口。
4.1.1 使用intent filter
你可以在intent filter中指定action爲android.hardware.usb.action.USB_DEVICE_ATTACHED
來發現某個USB設備。在這個intent filter中,你還需要指定一個resource文件,此文件可以說明你要的usb設備的屬性,如product id和vendor id。當用戶將一個符合你要求的設備連接到Android設備時,系統會顯示一個對話框,由用戶決定是否啓動你的應用處理此連接。如果用戶選擇了是,那麼你的應用會自動獲取訪問設備的權限,直到設備連接斷開。
下面是intent filter的示例:
<activity ...>
...
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
下面是指定你想要的usb設備的resource文件的示例:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1234" product-id="5678" />
</resources>
在你的activity中,你可以通過以下代碼獲取UsbDevice:
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
4.1.2 枚舉設備
如果你的應用是對當前連接的所有usb設備感興趣,你可以使用枚舉接口。使用getDeviceList()方法可以獲取當前連接的usb設備的哈希表。這個哈希表的鍵值是usb設備的名稱。
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");
你也可以用iterator的方式訪問設備:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
UsbDevice device = deviceIterator.next()
//your code
}
4.2 獲取訪問設備的權限
在與usb設備通信前,你的應用需要獲取相關權限。
注意:如果你的應用使用intent filter方式發現usb設備,那麼在用戶允許你的應用處理該intent時你的應用會自動獲取相關權限。如果用戶不允許,那麼你在與usb設備建立連接前必須顯式的申請權限
在某些情況下顯式的申請權限是必要的,比如你的應用在枚舉所有已連接設備時。你必須在與usb設備通信前檢查是否有訪問該設備的權限。如果不檢查的話,可能會拋出運行時異常。
要顯式的申請權限,你首先需要創建一個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) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(device != null){
//call method to set up device communication
}
}
else {
Log.d(TAG, "permission denied for device " + device);
}
}
}
}
};
然後在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()方法顯示一個對話框以向用戶申請權限:
UsbDevice device;
...
mUsbManager.requestPermission(device, mPermissionIntent);
當用戶完成操作後,你的broadcast receiver會收到包含EXTRA_PERMISSION_GRANTED的intent,這個extra爲一個boolean,表示權限獲取結果。
4.3 與設備通信
與usb設備的通信有兩種方式,同步或異步。不論你用哪個方式,你都應該新開一個線程進行通信操作,以防阻塞UI線程。要與設備通信,你需要獲取適當的UsbInterface和UsbEndpoint,然後用UsbDeviceConnection向endpoint發送請求。通常,你的代碼應該:
- 檢查UsbDevice的屬性,如product ID,vendor ID,設備class
- 當你確定設備是你想要的後,需要找到你要對此UsbEndpoint通信的UsbInterface。每個interface可以對應多個endpoint,通常情況下需要輸入輸出兩個endpoint以支持雙向通信
- 當你找到正確的endpoint後,在此endpoint上打開UsbDeviceConnection
- 用bulkTransfer()或controlTransfer()方法提交你想要傳輸的數據。這些操作應該另起線程去做,不要阻塞UI線程
下面的代碼簡單介紹了同步數據傳輸的方法。你的代碼應該更復雜,應該添加尋找interface和endpoint的邏輯:
private Byte[] bytes;
private static int TIMEOUT = 0;
private boolean forceClaim = true;
...
UsbInterface intf = device.getInterface(0);
UsbEndpoint endpoint = intf.getEndpoint(0);
UsbDeviceConnection connection = mUsbManager.openDevice(device);
connection.claimInterface(intf, forceClaim);
connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
如果需要異步發送數據,可以使用UsbRequest類初始化異步請求並把它加入到執行隊列中,然後用requestWait()方法等待結果。
更多的信息請看AdbTest sample(此爲異步通信例子),或MissileLauncher sample(此爲異步監聽endpoint中斷的例子)。
4.4 終止通信
當你與設備通信結束後,可以使用releaseInterface()或close()方法關閉UsbInterface或UsbDeviceConnection。你可以用以下的broadcast receiver監聽設備分離事件:
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
// call your method that cleans up and closes communication with the device
}
}
}
};