雖然我只有使用了UsbHost系列的功能,UsbAccessory的部分還沒用過,但經驗相信是差不多,而且UsbHost部分的元件複雜多了。
要在這裡附上我使用的程式做範例是不太可能的,我只能盡量做到引用官方API,然後每個細節補充
要在Android上使用USB、或稱為OTG,需要使用Android建立在
特別注意一下:
這些屬性/數值是每一台Usb裝置的「身分證」,我不是這方面的專家,無法非常精準正確的講述其中的細節,但是基本上......同一家公司生產的同一系列商品,基本上都有相同的vendor-id和product-id。
設定這個device-filter和Manifest,這樣裝置插上手機後,系統才會自動偵測「要不要啟動你的APP」。(也有不自動偵測,改走別的路徑的方式,就是自己設定BroadCastReciever去獲得裝置連線的Permission,但這方面的使用經驗還不多,改天再講吧。)
如果你/妳的APP要針對特定Usb裝置,就盡量把device_filter定義的詳細一點。如果不針對特定程式,就乾脆什麼都不要寫。
Android的Usb連線溝通方式,是以UsbDevice類別為基礎。每一個類別的物件代表一台Usb裝置。
獲得UsbDevice物件的方式,最簡單的就是......
所以我並沒有用它。
我用的是下面這段方法...(幾乎是官方API照抄。)
官方API教學程式碼如下。注意最後一行程式碼中的那個「"deviceName"」,這個地方並不是要使用者輸入「"deviceName"」,而是要輸入上一面的DeviceName!
菜鳥、初學Android的人可能會被API這種寫法給害死,就傻傻的一直輸入「"deviceName"」,然後好奇為何HashMap永遠要不到資料。因為直覺沒有敏銳到發現「我幹嘛在一個HashMap的get()中輸入deviceName這個字?想也知道這地方的內容是彈性的!
雖然Key值可以直接請HashMap吐給你/妳,可是用OTG時,是沒有adb功能的,(因為Usb接頭接著Usb裝置而不是電腦,)又某種原因,wifi-adb根本靠不住,(官方推薦改用wifi輸出Log資訊到電腦上,)所以吐出來的Key值...作夢想想就好。(或是自己在自己的APP中設置背景資訊的輸出功能,等到開發完畢在關閉這個功能就好。)
(因為Manifest中加了intent-filter,所以不需要經由BroadCastReciever去獲得Permission,因此我這裡不說明官方API中那段的使用方式。)
接下來是直接跟Usb機器「溝通」的部分。
跟機器「溝通」實在是個很模糊的概念,因為最終都是「送出Byte陣列資料」。
譬如...這裡有段Byte矩陣要送進特殊的隨身碟中存起來,只要矩陣開頭和結尾的Byte值是「100,101」,隨身碟就會把(「100,101」捨棄後)中間的值存起來。
(或是個喇叭,會把中間的值轉成音效,然後發出噪音。)
搞不懂的人可能要去研究一下網路傳輸或I/O原理,或就單純的自己用Java架個簡單的測試伺服器,然後練習用手機跟這個伺服器溝通看看。
就先......假設看到這裡的人都懂怎麼作,(遙遠的未來,或許我也來寫寫I/O相關的心得文吧。)
這段程式碼就是傳輸和接收資料的方法。(注意!Android的Usb元件能夠傳送和接收的資料有長度限制。上限是16K。)
關鍵在於每個UsbInterface中都會有兩組UsbEndpoint,一組為傳輸、一組為接收。(哪組在先、哪組在後,似乎並沒有絕對的標準和規則。所以我在上面的程式碼中補註明了「又是個API害死人的例子」。)
UsbConstants(這是一個UsbHost的元件)中可以看到相關的參數UsbConstants.USB_DIR_IN/UsbConstants.USB_DIR_OUT。
去判讀每組Endpoint的Direction,(使用指令getDirection(),可以得到參數,判讀參數是IN或OUT,就可以知道Endpoint的屬性和功能。)把傳輸的丟進去就會傳輸資料給Usb機器,把接收的丟進去就會從Usb機器接收資料。
最後,官方文件輕輕帶過、但困擾了我整整一個月的部分.......
UsbDeviceConnection和UsbInterface元件使用完,要進行「釋放」和「關閉」的動作。
為何要特別強調?
因為這段方式沒有「標準作法」!
一般人會以為「我不再需要使用Usb功能了,就把它關閉,」.......錯!這些元件雖然基本上是ParceLable介面的實作,(跟Bundle一樣,)但其實它們都混合了JNI,所以經常資料傳輸到一半,這兩個元件(其中一個?或兩個同時發生?)會變成「Null」!(我的解釋是「它自己把自己回收掉了。)
碰到這種情況時,就要重作一次獲取的動作。
可是隨著這種情況發生、多次獲取後,經常會連獲取都失敗!(UsbManager的openDevice指令回傳Null?UsbDevice的getInterface也回傳Null?)
因為UsbManager用一組「很奇特」的HashMap來存放這些元件的資料,這組HashMap功能異常(資料不見?資料錯誤?存放資料總數超過上限?)就會回傳Null。(可以用IDE打開UsbManager,但對於理解HashMap會異常的原因還是沒有幫助,而且修改SDK的程式碼對未來APP在實機上的運作其實沒什麼幫助。)
所以不能等到整個功能都用完了才去作釋放動作,必須要定時的釋放。
「每次傳輸/接收完,都釋放一次?」......還是錯!
這樣只是把「獲取失敗」發生的時間往後推遲而以。(我清清楚楚的記得...從不釋放,程式會在第540~550之間轉為「獲取失敗」。每次釋放,程式會在第1050~1100之間轉為「獲取失敗」。非常穩定、且準時的兩組數字。)
我的設定是「傳輸+接收」的次數「每十次」就進行一次釋放、再獲取的動作。
但這個數字是我碰運氣、靠靈感隨便決定的,(當然也在平版實機上進行了無數次的測試。)
如果你/妳要使用Android的Usb連線功能,而且不是用在最常見、制式標準的隨身碟這類儲存工具上,最後這個步驟記得多花點時間測試。
要在這裡附上我使用的程式做範例是不太可能的,我只能盡量做到引用官方API,然後每個細節補充
要在Android上使用USB、或稱為OTG,需要使用Android建立在
android.hardware.usb
這個package下的一系列元件,還有在APP的AndroidManifest檔中寫下類似下面這樣的設定:<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>(希望進來看到這篇文的人看得懂怎麼修改Manifest,因為我討厭寫基本教學。)
特別注意一下:
android:resource="@xml/device_filter"這行是要另外搭配一個XML檔,(在res目錄下直接新增一個目錄「xml」,然後在裡頭新增檔案,──有興趣的人也可以取不同的名字,只要不會造成混亂就好。)檔案的名稱就取為device_filter:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" /> </resources>要特別注意的是這個範例有點「過於」詳盡,其實vendor-id/product-id/class/subclass/protocol....並不全是必須的。(像下面這段也是可以的。這些XML檔都是直接引用自官方API的教學內容。)
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" /> </resources>
這些屬性/數值是每一台Usb裝置的「身分證」,我不是這方面的專家,無法非常精準正確的講述其中的細節,但是基本上......同一家公司生產的同一系列商品,基本上都有相同的vendor-id和product-id。
設定這個device-filter和Manifest,這樣裝置插上手機後,系統才會自動偵測「要不要啟動你的APP」。(也有不自動偵測,改走別的路徑的方式,就是自己設定BroadCastReciever去獲得裝置連線的Permission,但這方面的使用經驗還不多,改天再講吧。)
如果你/妳的APP要針對特定Usb裝置,就盡量把device_filter定義的詳細一點。如果不針對特定程式,就乾脆什麼都不要寫。
Android的Usb連線溝通方式,是以UsbDevice類別為基礎。每一個類別的物件代表一台Usb裝置。
獲得UsbDevice物件的方式,最簡單的就是......
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);但是這個方法在官網中的說明很模糊、很「曖昧」,像最基本的...這個intent是什麼?怎麼獲取?(根據後來的BroadCastReciever範例,這個Intent根本不是在Activity中獲得。)
所以我並沒有用它。
我用的是下面這段方法...(幾乎是官方API照抄。)
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... //Android HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while(deviceIterator.hasNext()){ UsbDevice device = deviceIterator.next() //your code }但其實這段的作法是有很多彈性的...deviceList中的Key值其實就是UsbDevice的DeviceName。這段程式碼中獲得UsbDevice後,可以將DeviceName存起來,然後下次跑到這段時,直接使用DeviceName去獲得UsbDevice,就不用跑while迴圈了。
官方API教學程式碼如下。注意最後一行程式碼中的那個「"deviceName"」,這個地方並不是要使用者輸入「"deviceName"」,而是要輸入上一面的DeviceName!
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); UsbDevice device = deviceList.get("deviceName");
菜鳥、初學Android的人可能會被API這種寫法給害死,就傻傻的一直輸入「"deviceName"」,然後好奇為何HashMap永遠要不到資料。因為直覺沒有敏銳到發現「我幹嘛在一個HashMap的get()中輸入deviceName這個字?想也知道這地方的內容是彈性的!
雖然Key值可以直接請HashMap吐給你/妳,可是用OTG時,是沒有adb功能的,(因為Usb接頭接著Usb裝置而不是電腦,)又某種原因,wifi-adb根本靠不住,(官方推薦改用wifi輸出Log資訊到電腦上,)所以吐出來的Key值...作夢想想就好。(或是自己在自己的APP中設置背景資訊的輸出功能,等到開發完畢在關閉這個功能就好。)
(因為Manifest中加了intent-filter,所以不需要經由BroadCastReciever去獲得Permission,因此我這裡不說明官方API中那段的使用方式。)
接下來是直接跟Usb機器「溝通」的部分。
跟機器「溝通」實在是個很模糊的概念,因為最終都是「送出Byte陣列資料」。
譬如...這裡有段Byte矩陣要送進特殊的隨身碟中存起來,只要矩陣開頭和結尾的Byte值是「100,101」,隨身碟就會把(「100,101」捨棄後)中間的值存起來。
(或是個喇叭,會把中間的值轉成音效,然後發出噪音。)
搞不懂的人可能要去研究一下網路傳輸或I/O原理,或就單純的自己用Java架個簡單的測試伺服器,然後練習用手機跟這個伺服器溝通看看。
就先......假設看到這裡的人都懂怎麼作,(遙遠的未來,或許我也來寫寫I/O相關的心得文吧。)
這段程式碼就是傳輸和接收資料的方法。(注意!Android的Usb元件能夠傳送和接收的資料有長度限制。上限是16K。)
private Byte[] bytes //要傳輸的資料 特別注意的是這個bytes的長度不能超過16K 也就是 16 * 1024 因為這是它每次能傳輸的資料量極限 private static int TIMEOUT = 0; //每次傳輸的等待時間 (如果Usb機器超過這個時間沒回傳 就視為傳輸失敗) private boolean forceClaim = true; ... UsbInterface intf = device.getInterface(0); UsbEndpoint endpoint = intf.getEndpoint(0);//又是個API害死人的例子 UsbDeviceConnection connection = mUsbManager.openDevice(device); connection.claimInterface(intf, forceClaim); connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread最後一行connection.bulkTransfer中,這個用法跟使用File元件讀取檔案的使用方法很像。(所以細節我也不說明了。)
關鍵在於每個UsbInterface中都會有兩組UsbEndpoint,一組為傳輸、一組為接收。(哪組在先、哪組在後,似乎並沒有絕對的標準和規則。所以我在上面的程式碼中補註明了「又是個API害死人的例子」。)
UsbConstants(這是一個UsbHost的元件)中可以看到相關的參數UsbConstants.USB_DIR_IN/UsbConstants.USB_DIR_OUT。
去判讀每組Endpoint的Direction,(使用指令getDirection(),可以得到參數,判讀參數是IN或OUT,就可以知道Endpoint的屬性和功能。)把傳輸的丟進去就會傳輸資料給Usb機器,把接收的丟進去就會從Usb機器接收資料。
最後,官方文件輕輕帶過、但困擾了我整整一個月的部分.......
UsbDeviceConnection和UsbInterface元件使用完,要進行「釋放」和「關閉」的動作。
為何要特別強調?
因為這段方式沒有「標準作法」!
一般人會以為「我不再需要使用Usb功能了,就把它關閉,」.......錯!這些元件雖然基本上是ParceLable介面的實作,(跟Bundle一樣,)但其實它們都混合了JNI,所以經常資料傳輸到一半,這兩個元件(其中一個?或兩個同時發生?)會變成「Null」!(我的解釋是「它自己把自己回收掉了。)
碰到這種情況時,就要重作一次獲取的動作。
可是隨著這種情況發生、多次獲取後,經常會連獲取都失敗!(UsbManager的openDevice指令回傳Null?UsbDevice的getInterface也回傳Null?)
因為UsbManager用一組「很奇特」的HashMap來存放這些元件的資料,這組HashMap功能異常(資料不見?資料錯誤?存放資料總數超過上限?)就會回傳Null。(可以用IDE打開UsbManager,但對於理解HashMap會異常的原因還是沒有幫助,而且修改SDK的程式碼對未來APP在實機上的運作其實沒什麼幫助。)
所以不能等到整個功能都用完了才去作釋放動作,必須要定時的釋放。
「每次傳輸/接收完,都釋放一次?」......還是錯!
這樣只是把「獲取失敗」發生的時間往後推遲而以。(我清清楚楚的記得...從不釋放,程式會在第540~550之間轉為「獲取失敗」。每次釋放,程式會在第1050~1100之間轉為「獲取失敗」。非常穩定、且準時的兩組數字。)
我的設定是「傳輸+接收」的次數「每十次」就進行一次釋放、再獲取的動作。
但這個數字是我碰運氣、靠靈感隨便決定的,(當然也在平版實機上進行了無數次的測試。)
如果你/妳要使用Android的Usb連線功能,而且不是用在最常見、制式標準的隨身碟這類儲存工具上,最後這個步驟記得多花點時間測試。
沒有留言:
張貼留言