編輯:關於Android編程
翻譯Android USB HOST API
源碼地址:http://developer.android.com/guide/topics/connectivity/usb/host.html
譯者注:翻譯的好不好不是太重要,重點是在翻譯的過程中會把每句話都看認真看一遍,或者說是抱著翻譯的思想來完成一個讀懂的目的。
USB Host通信
當你的可供電Android設備處理USB host模式時,它擔任著為USB總線供電,枚舉連接的USB從設備等等一個主設備應用的工作。Android 3.1及以後版本開始支持USB host模式。
API概述
開始之前,有必要弄明白以後要用到的類。下表中描述了包含在android.hardware.usb包中的USB host APIs。
表1. USB host APIs.
Class
Description
UsbManger
允許你枚舉USB從設備以及和其通信
UsbDevice
代表一個USB從設備,獲取其信息。如接口,端點等等。
UsbInterface
代表一個USB從設備的接口,一個設備可以擁有多個接口用於通信。
UsbEndpoint
代表接口的一個端點,它是該端點一個通信通道。一個接口可以擁有一個或者多個端點,通常輸入輸出端點用於和設備進行雙向通信。
UsbDeviceConnection
代表一個設備的連接,通過端點傳輸數據,該類允許你發送接收數據,通信方式可以是同步或者異步。
UsbRequest
代表通過UsbDeviceConnection通信的異步請求。
UsbConstants
定義USB常量對應Linux內核中linux/usb/ch9.h中的宏。
多數情況下,在和USB設備進行通信的時候,你需要使用這裡所有的類(UsbRequest只用在異步通信一個請求)。一般是這樣的,使用UsbManager來獲取UsbDevice。確定了設備,需要查找用於通信接口的UsbInterface和UsbEndpoint。一旦獲取了恰當的端點,打開UsbDeviceConnection與USB設備進行通信。
下列表中描述了使用USB host APIs之前需要添加到應用程序的manifest文件中的內容:
由於並不是所有的Android帶供電設備確保支持USB host APIs,添加
SDK最低版本設置為12或者更高。USB host APIs不支持更老版本的API。
如果你想讓你的應用檢測一個USB設備的話,在主Activity指定
在這個XML資源文件中,為你想要過濾的USB設備聲明
vendor-id
product-id
class
subclass
protocol
(device or interface)
保存資源文件到res/xml/目錄下。資源文件名(不包含.xml後綴)必須要和
... ...
在本例中,以下資源文件應該保存在res/xml/device_filter.xml中,指定需要過濾USB設備的屬性:
設備工作
當USB設備連接到Android上時,無論你的應用程序是否對此USB設備感興趣Android系統都會檢測到。你可以和設備建立通信。你的應用程序需要做如下事情:
1.探測USB設備通過意圖過濾器檢測用戶連接的設備,或者枚舉已經連接的USB設備。
2.如果之前沒有請求過,向用戶請求權限以連接USB設備。
3.和USB設備進行通信,通過接口端點讀寫數據。
探測設備
應用程序可以通過兩種方式進行USB設備的探測,使用意圖過濾器監聽用戶連接設備或者枚舉已經連接的USB設備。前者可以方便的實現自動檢測設備。如果想列出所有的已連接設備或者沒有使用意圖過濾器過濾的設備,可以使用枚舉設備來完成。
使用意圖過濾器
探測設備可以指定意圖過濾器來過濾android.hardware.usb.action.USB_DEVICE_ATTACHED意圖。此外需要指定一個資源文件描述USB設備,比如product and vendor ID. 當用戶連接上和你探測的設備匹配時,系統會彈出一個對話框詢問用戶是否允許啟動的你的應用程序。如果允許,你的應用程序將會自動被賦予權限和該設備通信,直到設備被移除。
以下示例顯示如何描述意圖過濾器:
...
以下示例顯示如何描述資源文件,指定你感興趣的USB設備:
在你的Activity中,你要獲得UsbDevice,它代表使用意圖探測到的設備。如:
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
枚舉設備
在你的應用程序運行時,如果對所以當前正在連接的USB設備感興趣,可以枚舉設備。枚舉設備使用的是getDeviceList()方法獲取所有連接設備的哈希表。如果你想獲取一個設備可以根據USB設備的名字為關鍵字進行索引。
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMapdeviceList = manager.getDeviceList(); UsbDevice device = deviceList.get("deviceName");
必要時,你從哈希表中迭代設備依次處理每個設備:
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMapdeviceList = manager.getDeviceList(); Iterator deviceIterator = deviceList.values().iterator(); while(deviceIterator.hasNext()){ UsbDevice device = deviceIterator.next() //your code }
獲取權限與設備進行通信
與設備通信之前,你的應用程序必須從用戶那裡獲取權限。
| 注:如果你的應用程序使用意圖過濾器去探測連接的USB設備,如果用戶允許處理意圖,那麼你的應用程序自動會獲取權限。反之,通信之前你必須向用戶請求權限。
在一些情況下請求權限是必須的,例如你的應用程序枚舉已經連接的USB設備以及與設備通信。通信之前你需要檢查權限,否則如果用戶權限拒絕你將會收到一個運行錯誤。
申請權限,首先創建創建一個廣播接收器。調用requestPermission()時,接收器監聽廣播意圖。調用requestRermission()向給用戶顯示一個對話框請求連接該設備的權限。
下列代碼顯示如何創建這樣一個廣播接收器:
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()方法中注冊廣播接收器:
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);
當用戶回復對話框,你的廣播接收器會收到的內容為EXTRA_PERMISSION_GRANTED意圖,它是一個布爾代表答案。通信之前檢查這個額外值是否為真。
與設備通信
通信方式可以是同步或者異步。在這兩種情況下,你應該創建一個線程執行所有數據的傳輸,不要阻塞界面線程。建立一個通信,你需要獲取需要通信設備的UsbInterface和UsbEndpoint,以及使用UsbDeviceConnection申請端點。一般地,你的代碼應該是:
檢查UsbDevice對象的屬性,比如 product ID, vendor ID或者設備類別。以識別是不是需要通信的設備。如果確定了是要通信的設備,檢索出你需要通信設備的接口,以及該接口的端點。接口可以有一個或者多個端點,一般會有輸入輸出各一的雙向通信的端點。如果確定了端點,在該端點上打開 UsbDeviceConnection。 使用bulkTransfer()或者controlTransfer()方法將需要傳輸的數據提供到端點上。你需要將上述操作實現在其它線程以免阻塞當前主界面線程。更多關於Android中線程的使用,見Processes and Threads.
下面的代碼片段簡單的實現了同步數據傳輸。你的代碼應該使用更多邏輯上的來查找出正確的通信接口和端點,同樣需要在主界面以外的線程中進行數據的傳輸:
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類初始化(initialize)和排隊(queue)等候異步請求,然後使用requestWait()等待結果。
更多信息見AdbTest例子,它顯示了如何進行塊傳輸的方法異步通信,以及MissleLauncher sample它顯示了如何監聽中斷端點異步。
終止通信
當完成了通信或者設備被移除了,調用releaseInterface()和close()來關閉UsbInterface和UsbDeviceConection。監聽設備移除事件,創建一個廣播接收器如下:
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 } } } };
創建廣播接收器是在應用程序內部,而非manifest中。僅在應用程序程序運行時處理設備移除事件。如此,移除事件只會發送給正在運行的應用而不是所有的應用。
感悟:
與原文無關純屬個人感想,USB通信越向下越復雜。Android封裝好一些,其次是libusb,再然後就是Linux內核中進行的USB通信。依次需要了解的知識多少都不一樣,比如這個完整的通信過程中並沒有用到urb等等。目前測試的Android4.2.2上通信的最大塊為:16KiB。所以數據要分割為16KiB大小進行傳輸。文件中提到的那個例子AdbTest和MissleLauncher的源碼位於:development/samples/USB/中。前者功能是讀取usb設備(同樣也是Android系統)的LOG,後者功能是控制一個火箭發射器玩具。Android這套API並不能檢測所有的USB設備,有些設備檢測不到。比如無線數據的適配器。目前測試成功的有打印機。關於對其了解的程度,我一直有個毛病就是了解一個東西沒有上限。類似寫文章沒有主線,想到哪裡就深入到哪裡。我要逐漸改點這個習慣。我對這個的練習是將數據發送打印機,完成打印。當我剛剛實現的時候,我就又開始幻想我是不是可以做一個很牛叉的USB打印APP了。這個是可以實現的,但並不是目前的主要任務,如果我堅持做,很多東西都需要去實現,這也違背了我當前僅僅是了解Android USB host API的初衷。還好,這次我剎住了閘,畢竟我這次是真實的了解了,之前只知道在Linux內核中的通信以及基於Libusb通信,直接使用java通信的好處是解析xml等等在C語言中相對復雜的東西到java中都不顯示復雜了。l 測試APK和上文提到的那兩個例子打包上傳到Here,方便那些沒有Android系統源碼的同志。本文實例實現了兩個模擬器之間短信的發送功能,分享給大家供大家參考,具體實現內容如下1.編輯String.xml文件內容為:<?xml version=”1.
效果 (關於gif怎麼生成的,我先錄手機的屏幕得到mp4文件,然後用這個網址:https://cloudconvert.com/mp4-to-gif 進行的mp4轉
MPAndroidChart是實現圖表功能的優秀控件, 可以完成大多數繪制需求. 對於修改第三方庫而言, 優秀的架構是繼承開發, 而不是把源碼拆分出去. MP在顯示標記控
本部分代碼在《Android應用開發揭秘》中提到,但是在eclipse環境下調試時出現異常,幾番糾結,代碼終於可以播放器音樂、並成功移植到手機上...... p