安卓USB開發教程 USB Host

SoldierJazz2019發表於2017-06-27

USB Host(主機模式)

當 Android 裝置處於 USB 主機模式時,它充當 USB 主機,為匯流排供電,並列舉連線的 USB 裝置。Android 3.1 及更高版本支援 USB 主機模式。

API 概述

在開始之前,理解需要使用的類是很重要的。下表描述了 android.hardware.usb 包裡的 USB 主機 API 函式。

Table 1. USB Host APIs

Class Description
UsbManager 負責列舉和與連線的 USB 裝置通訊
UsbDevice 表示連線的 USB 裝置並且包含訪問其標識資訊,介面和端點的方法
UsbInterface 表示 USB 裝置的介面,它定義了裝置的一組功能。 裝置可以具有一個或多個介面進行通訊。
UsbEndpoint 表示介面端點,它是該介面的通訊通道。 介面可以有一個或多個端點,並且通常具有與裝置進行雙向通訊的輸入和輸出端點。
UsbDeviceConnection 表示與裝置的連線,該裝置在端點上傳輸資料。 該類允許您以同步方式或非同步方式來回傳送資料。
UsbRequest 表示通過UsbDeviceConnection與裝置通訊的非同步請求。
UsbConstants 定義與Linux核心的linux / usb / ch9.h中的定義對應的USB常量。

在大多數情況下,與 USB 裝置通訊時需要使用所有這些類(UsbRequest 只在非同步方式通訊的時候需要)。通常,會獲取一個 UsbManager 來檢索所需的 UsbDevice。當獲取到裝置時,需要查詢合適的 UsbInterface 與介面中用於通訊的UsbEndpoint。一旦獲取到正確的端點,開啟一個 UsbDeviceConnection 與 USB 裝置通訊。

Android Manifest 要求

下面的清單描述了在使用 USB host API 函式前需要新增到應用清單檔案的內容:

1. 由於並非所有安卓裝置被授權支援 USB host API 函式,因此需要包含一個 <uses-feature> 元素來宣告你的應用使用 android.hardware.usb.host功能

2. 設定應用的最低 SDK 版本為 API 級別 12 或更高。USB host API 函式在更早的 API 級別中不存在。

3. 如果你希望應用收到 USB 裝置插入的通知,請在主活動中為 android.hardware.usb.action.USB_DEVICE_ATTACHED 意圖指定<intent-filter>  <meta-data> 元素對。<meta-data> 元素指向一個外部 XML 資原始檔,它宣告瞭要檢測的裝置的資訊。

4. 在 XML 資原始檔中,為你想過濾的 USB 裝置宣告 <usb-device>元素。下表描述了 <usb-device> 的屬性。

通常,如果要過濾特定的裝置使用廠商和產品 ID,如果要過濾一組 USB 裝置使用 USB 類、子類和協議,如大容量儲存類和數位相機。

你可以指定這些屬性中的一個或者全部,不指定屬性匹配任何 USB 裝置,因此在應用需要時才進行指定:

vendor-id

product-id

class

subclass

protocol (device or interface)

將資原始檔儲存在 res/xml/ 目錄中。資原始檔名(不含 .xml 副檔名)必須與您在 <meta-data> 元素中指定的檔名相同。XML 資原始檔的格式在下面的示例中。

Manifest 與資原始檔示例

以下示例展示了 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>

使用裝置

在這種情況下,當使用者將 USB 裝置連線到 Android 裝置時,Android 系統可以確定應用程式是否對連線的裝置感興趣。如果是這樣,您可以根據需要建立與裝置的通訊。為此,您的應用程式必須:

1.通過使用 intent filter 在使用者連線裝置時收到通知或通過列舉已連線的 USB 裝置來發現 USB 裝置

2. 請求使用者連線 USB 裝置的許可權,如果尚未獲得。

3. 通過在相應的介面端點上讀寫資料與 USB 裝置進行通訊。

發現裝置

應用程式可以通過使用intent filter 在使用者連線裝置時收到通知或通過列舉已連線的 USB 裝置來發現 USB 裝置。如果您希望能夠讓應用程式自動檢測到所需的裝置,則使用 intent filter 非常有用。 如果要獲取所有連線的裝置列表,或者您的應用程式沒有為 intent 進行過濾,則列舉已連線的 USB 裝置的方法非常有用。

使用 intent filter(意圖過濾器)

要使您的應用程式發現一個特定的 USB 裝置,可以指定一個 intent filter 來過濾android.hardware.usb.action.USB_DEVICE_ATTACHED intent。 除了此 intent filter,您還需要指定一個資原始檔,該資原始檔指定USB裝置的屬性,如產品和供應商ID。 當使用者連線與 device filter 匹配的裝置時,系統會向他們顯示一個對話方塊,詢問他們是否要啟動應用程式。 如果使用者接受,應用程式自動獲得訪問裝置許可權,直到裝置斷開連線。

以下示例展示如何宣告 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 裝置。

<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>

在你的活動中,你可以像這樣從 intent 中獲取表示連線裝置的 UsbDevice :

UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

列舉裝置

當應用程式執行時,如果應用程式有興趣檢查當前連線的所有 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
}

獲取與裝置進行通訊的許可權

在與USB裝置進行通訊之前,應用程式必須獲得使用者的許可。

Note:如果應用程式使用 intent filter 來發現連線時的 USB 裝置,則如果使用者允許您的應用程式處理 intent,則它將自動接收權限。如果沒有,您必須在連線到裝置之前在應用程式中明確請求許可權。

在某些情況下,顯式請求許可可能是必需的,例如當您的應用程式列舉已連線的USB裝置,然後要與其進行通訊時。在嘗試與之通信之前,您必須檢查訪問裝置的許可權。如果沒有,使用者拒絕訪問裝置的許可權時,您將收到 runtime 錯誤。

要明確獲得許可,首先建立一個廣播接收器。該接收器偵聽當您呼叫 requestPermission() 時獲得廣播的意圖。對 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) {
                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);
                }
            }
        }
    }
};

要註冊廣播接收器,在活動的 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 的 intent,這是表示答案的布林值。 在連線裝置之前,請檢查這個額外值是否為 true。

與裝置通訊

與USB裝置的通訊可以是同步或非同步的。在任一情況下,您應該建立一個新執行緒來執行所有資料傳輸,才不會阻塞UI執行緒。要正確建立與裝置的通訊,您需要獲得要進行通訊的裝置的相應的UsbInterface UsbEndpoint,並使用UsbDeviceConnection 在此端點上發送請求。一般來說,您的程式碼應該:

1. 檢查UsbDevice 物件的屬性,如產品 ID,供應商 ID或裝置類,以確定是否要與裝置進行通訊;

2. 當您確定要與裝置通訊時,請找到與合適的UsbEndpoint 進行通訊的相應UsbInterface。介面可以具有一個或多個端點,並且通常會具有用於雙向通訊的輸入和輸出端點;

3. 找到正確的端點時,在該端點上開啟一個UsbDeviceConnection

4. 使用bulkTransfer() controlTransfer() 方法提供在端點上傳輸的資料。您應該在另一個執行緒中執行此步驟,以防止阻塞主 UI 執行緒。有關在 Android 中使用執行緒的更多資訊,請參閱 Processes and Threads

以下程式碼片段是進行同步資料傳輸的簡單方法。您的程式碼應該有更多的邏輯來正確找到正確的介面和端點進行通訊,並且還應該在與主UI執行緒不同的執行緒中進行資料傳輸:

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() 等待結果。

終止與裝置通訊

當你與裝置通訊完成或者裝置拔出時,呼叫 releaseInterface()  close() 方法關閉 UsbInterface  UsbDeviceConnection為了監聽拔除事件,如下所示建立廣播接收器:

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 中建立廣播接收器允許應用在執行時只處理拔除事件。通過這種方式,廣播事件只會傳送到當前正在執行的應用程式而不是廣播到所有應用。

原文連結:https://developer.android.com/guide/topics/connectivity/usb/host.html





相關文章