序言:
當你的Android裝置是在USB host模式時,
它運作的就如USB host,供應匯流排電力,
並且枚舉連結的USB裝置。
USB host模式在3.1以上的版本被支援。
API Overview:
在開始之前,了解有那些class你需要一起處理是很重要的。
下面的表格描述了在
android.hardware.usb
package裡的USB host的APIs。Class | Description |
---|---|
UsbManager | 允許你列舉以及與連接的USB裝置溝通。 |
UsbDevice | 代表一個連結的USB裝置,且含有methods來存取其資料,介面及終端。 |
UsbInterface | 代表一個USB裝置的介面,其定義了一套的裝置的功能。 一個裝置可以有一或多個介面開放溝通。 |
UsbEndpoint | 代表介面的終端,是一個介面溝通的頻道。一個介面可以有一或多個終端, 且通常有輸入輸出的終端來和裝置作雙向溝通。 |
UsbDeviceConnection | 代表一個和裝置的連結,用來在終端上傳出資料。 這個class允許你同步或非同步地往復傳輸資料。 |
UsbRequest | 代表一個不同步的request, 來和裝置透過一個 UsbDeviceConnection 來作溝通。 |
UsbConstants | 定義USB的常數,其依照Linux kernel的linux/usb/ch9.h裡的定義。 |
你需要使用所有的classes。(UsbRequest只在進行不同步的溝通時需要)。
大體上,你取得一個
UsbManager
來取回想要的UsbDevice
。當你有了裝置,你需要去找適當的
UsbInterface
以及UsbEndpoint
的介面去進行溝通。依但你取得了正確的終端,開啟一個UsbDeviceConnection來和USB裝置溝通。
Android Manifest Requirements:
下面的列表描述了在使用USB host APIs之前,
你需要去加到你的app的manifest檔的東西:
1. 因為並不是所有的Android裝置都保證會支援USB host APIs,
所以將<uses-feature>元素包進來,
以聲明你的app使用android.hardware.usb.host的功能。
2. 設定最小的SDK版本到API Level 12或更高。
USB host APIs並不在早期版本出現。
3. 如果你希望你的app能夠在USB裝置連結時被提醒的話,
在你的主activity裡,
指定
<intent-filter>
及<meta-data>的元件配對(element pair)給android.hardware.usb.action.USB_DEVICE_ATTACHED的intent。
<meta-data>的元件指到一個外部的XML資源檔,
其聲明了你想要去偵測的裝置資訊。
在XML資源檔裡,聲明<uses-feature>元素給你想篩的USB裝置。
下面的列表描述了<usb-device>的屬性。
大體上如果你想要篩特別的裝置且使用class,subclass,
就用vendor和product ID;
如果你要篩一個群組的USB裝置(如大型儲存裝置、數位相機等),
就使用protocol吧!
(編按:這句的子句也硬塞太多= =)
你也可以什麼屬性都不指定。
什麼屬性都不指定的話就代表符合所有的USB裝置,
所以只有在你的app需要的時候才這麼做。
元素有下列幾種:
1. vendor-id
2. product-id
3. class
4. subclass
5. protocol(裝置或介面)
將資源檔存在 目錄底下。資源檔的名稱(不含.xml)必須跟你在 元素中指定的一樣。
XML資源檔的格式在下面的範例會展示。
Manifest and resource file examples:
下面展示一個範例的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>
在這個狀況下接下來資源檔應該存成res/xml/device_filter.xml,
且擁有特定屬性的USB裝置的要指定它被篩到:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" /> </resources>
Working with Devices:
當使用者連結USB裝置到一個Android的裝置時,
Android系統可以決定你的app是否對連結的裝置有興趣。
要能夠設定成和有興趣的裝置溝通的話,你的app必須做:
1. 探索連接的USB裝置(透過intent filter),
或者枚舉。
2. 跟使用者要授權(如果還沒包含的話)。
3. 跟USB裝置透過在適當的終端介面讀寫溝通。
(這幾點跟前一篇的一樣)
Discovering a device:
你的app可以探索accessories,
透過使用intent filter來被提醒使用者連接,
或者透過枚舉已連接的accessories。
如果你想要能夠去讓你的app自動去偵測到想要的accessory,
用intent filter很有用。
如果你想要拿到所有連接的list且你的app並不作intent的filter的話,
枚舉連接的accessories很有用。
Using an intent filter:
要讓你的app去探索一個特定的USB accessory,
你可以指定intent filter,
來篩android.hardware.usb.action.USB_ACCESSORY_ATTACHED intent。
隨著這個intent filter,你需要指名指定USB accessory的資源檔,
像是product和vendor ID。
當使用者連接一個符合你的accessory filter的accessory,
下面的範例展示如何聲明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>
(前面那段大同小異= =)
下面的範例展示,
如何去聲明那個指名你感興趣的accessories的相對應的資源檔:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" /> </resources>
在你的activity裡,你可以從intent取得代表
UsbDevice
的連結裝置,如:
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Enumerating devices:
如果你的app在執行時,對於偵測所有的現有連結中的USB裝置有興趣,
它可以枚舉在匯流排(bus)上的裝置。
使用的
getDeviceList()
method來取得對所有連結的裝置的hash map。如果你想要從map取得一個裝置的話,hash map是由USB裝置的名稱作為鍵值。
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); UsbDevice device = deviceList.get("deviceName");
如果想要的話,你也可以只從hash map取得一個走訪器(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 }
Obtaining permission to communicate with a device:
在和USB裝置溝通之前,你的app必須從你的使用者那要到許可權。
注意:
如果你的app是用intent filter來篩連接的accessories的話,
會自動接收權限(如果使用者有允許你的app來處理intent的話)
如果並不是如此的話,
你必須要在連接前,明確地在你的app裡取得授權。
明確地去詢問授權可能在某些狀況是必要的,
像是當你的app枚舉出所有已連結的accessories以後想要和一個溝通。
如果沒這麼做的話,
你會收到一個執行期錯誤,如果使用者拒絕授權通過的話。
要明確地去取得授權,首先要作一個廣播接收器,
接收器監聽那種當你呼叫
requestPermission()
時會廣播的intent。呼叫
requestPermission()
會顯示一個對話框給使用者,請求連接的授權。
下面的sample程式碼展示了如何做一個廣播接受器:
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); } } } } };
(除了code以外又完全一樣了是怎樣= =)
要註冊廣播接收器,將其放在onCreate() method(在你的activity裡):
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() method:
UsbDevice device; ... mUsbManager.requestPermission(device, mPermissionIntent);
當使用者回應這個對話框時,
你的廣播接收器接收包含
EXTRA_PERMISSION_GRANTED
extra的intent,(它是boolean值,代表回應的答案)
在連結之前,檢查這個extra的value是否為真。
(真的只有code有改= =+++++++++++++++!)
Communicating with a device:
和USB裝置的溝通可以是同步或非同步地,
你應該要在另一個thread裡設定好裝置和accessory的溝通,
這樣你才不會鎖住主UI thread。
要好正確地跟裝置溝通的話,你需要從你想溝通通的裝置,
取得適當的UsbInterface以及
UsbEndpoint
,並且在這個終端上用UsbDeviceConnection傳送請求。
大體上你的code應該要:
1. 檢查
UsbDevice
物件的屬性,比如product ID, vendor ID,或者裝置的class來弄清楚你到底想不想跟這個裝置溝通。
2. 當你確定你要跟裝置溝通時,
找到你想要和適當的
UsbEndpoint
的操作介面用來溝通的UsbInterface
。介面可以有一或多個終端點,
而且通常會有一個input和output的終端點作為雙向溝通。
3. 當你找到正確的終端點時,在那個終端點上打開
UsbDeviceConnection
。4. 在那個終端點上,使用bulkTransfer()或者controlTransfer(),
來支援你想傳送的資料。你應該把這步放在別的thread來做,
以避免擋到主程式的UI thread運行。
下面的code片段是一個trivial的方法來做同步資料的傳輸。
你的code應該要有更多邏輯來正確地找到正確的介面及終端點,
來進行溝通,且也應在一個不同的thread做任何資料傳輸,
而非在主程式。
private Byte[] bytesprivate 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的class來初始化並queue成同步的request,
然後使用
requestWait()
來等候結果。要找尋更多的資訊,請看AdbTest sample,
展示了如何去做不同步的bulk傳輸。
還有可以參考MissleLauncher sample,
它展示了如何不同步地監聽一個中斷終端點。
Terminating communication with a device:
當你做完和裝置地溝通,或者裝置被拔除,
透過呼叫
releaseInterface()
和close()
,關閉
UsbInterface
還有UsbDeviceConnection
。要監聽拔除的events,
做一個廣播接收器如下:
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 } } } };
在app裡作廣播接收器而不在manifest裡作,
讓你的app只會在執行時去處理移除連結的events。
這樣的話,移除連結的events只會被送到現在正在跑的app,
而不會廣播到所有的apps。
沒有留言:
張貼留言