天天看點

安卓開發者官網: USB 主機模式概述USB 主機模式概述

USB 主機模式概述

當你的安卓裝置處于主機模式下,它就會充當主機,為總線提供電力支援,會枚舉出與此主機相連的安卓裝置。USB 主機模式支援 Android API 3.1或更高。

API 概述

在開始之前,很重要的是你要了解你即将使用到的那些類。下面的表格表述了在 android.hardware.usb 包下主機模式的API。

表1:主機模式API

描述
USBManager 允許你枚舉出已連接配接的裝置。 與裝置進行通信。
UsbDevice 表示一個已連接配接的USB裝置。 包含了通路該USB裝置的方法、裝置的識别資訊、接口、端點。
UsbInterface 描述了一個USB裝置的接口。該接口定義了這個裝置的一套行為。 裝置具有一個或多個,可在其上進行通信的接口。
UsbEndpoint 描述了與一個接口通信的通道。 一個接口可以有一個或多個端點。 通常為了與裝置進行雙向通信,會有一個input端點,一個output端點。
UsbDeviceConnection 表示一個USB的連接配接,此連接配接會在端點上給裝置傳輸資料。 這個類允許你通過異步或非異步的方式,來回傳輸資料。
UsbRequest 表示通過 UsbDeviceConnection 與進行異步通信的一個請求。
UsbConstants 定義了 USB 的常量。 與 Linux 核心中的 linux/usb/ch9.h 一緻。

* USBManager

* UsbDevice

* UsbInterface

* UsbEndpoint

* UsbDeviceConnection

* UsbRequest

* UsbConstants

大多數情況下,你要與一個 USB 裝置進行通信,需要用到所有的類(隻有要進行異步通訊時才需要使用到 UsbRequest )。通常會獲得一個 UsbManager 來檢索所需的 UsbDevice 。當你拿到裝置對象後(

UsbDevice

),你需要找到恰當的 UsbInterFace 以及該

UsbInterface

的 UsbEndpoint 來進行通信。當你獲得正确的

endpoint

後,你就打開了一個與該 USB 裝置進行通信的一個連接配接( UsbDeviceConnection )。

安卓清單要求(manifest檔案)

下面清單描述了在使用 USB 主機模式 API 之前,你需要向你應用程式的清單檔案中添加的内容。

  • 因為不是所有的安卓裝置都能夠支援 USB 主機模式 API ,是以導入一個 <uses-feature> 元素聲明你應用程式要使用

    android.hardware.usb.host

    功能。
  • 設定應用程式适配的最小 SDK 為 API 12 或更高。更早的 API 版本不支援 USB 主機模式。
  • 如果想讓你的應用程式可以被一個 USB 裝置依附,為

    MainActivity

    中的

    android.hardware.usb.action.USB_DEVICE_ATTACHED

    意圖指定一個 <intent-filter> 和 <meta-data> 的元素對。 <meta-data> 元素指向一個聲明了你想要找到的裝置的辨別資訊的外部 XML 資源檔案。

在這個 XML 資源檔案中,為了過濾你想要的 USB 裝置,聲明了 <usb-device> 元素。下面的清單描述了 <usb-device> 的屬性。通常,如果你想要篩選一個特殊的裝置,并使用類,子類和協定(如果要篩選一組 USB 裝置,比如大容量儲存設備或者數位相機)需要使用供應商和産品ID。那些屬性是非必要的,指不指定都可以(根據需要即可)。沒有屬性比對每個 USB 裝置(比如,有的 USB 裝置隻有産品id和供應商id,沒有 class 或者 subclass 什麼的),是以隻有在你的應用程式需要時才這樣做:

  • vendor-id

    :供應商id
  • product-id

    :産品id
  • class

    :類
  • subclass

    :子類
  • protocol

    (裝置或接口) :協定

将資源檔案儲存在

res/xml/

目錄中。資源檔案的名稱(沒有 .xml 擴充名)必須與你在 <meta-data> 元素中指定的某一個名稱相同。 這個 XML 資源檔案的格式 如下所示。

清單檔案和資源檔案示例

下面的例子展示了一個簡單的清單檔案和及其對應的資源檔案:

<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 裝置 (掃碼槍) 連接配接到 安卓裝置(超市用的收銀裝置) 時,安卓系統能夠決定你的應用程式是否對目前連接配接的裝置感興趣。如果是這樣,你可以設定與你渴望的裝置通信。要想這麼做,你的應用應該:

  1. 通過使用意圖過濾器(intent-filter) 來發現連接配接到的 USB 裝置,以便在使用者連接配接了 USB 裝置時收到通知,或者枚舉已連接配接的 USB 裝置。
  2. 如果還沒獲得連接配接 USB 裝置的權限,則向使用者請求權限。
  3. 通過在相同的接口端點中讀寫資料與 USB 裝置進行通信。

發現裝置

當使用者連接配接裝置時,你的應用程式的意圖過濾器會被通知,這樣便可以發現 USB 裝置,也可以枚舉已經連接配接的 USB 裝置。

  • 如果你想讓你的應用程式能夠自動發現想要的裝置,那麼使用意圖過濾器會很有用。
  • 如果你想獲得已連接配接裝置的清單,或者你的應用不過濾任何意圖,那麼枚舉已連接配接的裝置會很有用。

使用意圖過濾器

為了讓你的應用可以發現某一個特定的 USB 裝置,你可以指定一個意圖過濾器來篩選

android.hardware.usb.action.USB_DEVIEC_ACTION

意圖。與這個過濾器一起,你還需要指定一個目标 USB 裝置所具有的屬性的資源檔案,例如:供應商id或産品id。 當使用者連接配接了一個意圖過濾器能夠比對的裝置時,如果系統想要開啟你的應用,那麼會彈出一個對話框(dialog)向使用者請求。如果使用者同意,你的應用能夠自動獲得通路該裝置的權限,直到該裝置斷開連接配接。

下面的例子展示了這麼聲明意圖過濾器:

<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>
           

在你的activity中,你可以從

intent

中獲得表示依附裝置的 UsbDevice 對象:

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

枚舉裝置

當你的應用程式正在運作時,如果該程式對目前已連接配接的所有 USB 裝置都感興趣,那麼可以在總線中枚舉出所有裝置。使用 getDeviceList() 方法來獲得存儲所有已連接配接裝置的

HashMap

。如果你想從這個

HashMap

中獲得一個裝置的話,這個

HashMap

是以裝置的名字作為鍵(key) 的。

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");
           

如果需要,你也可以從

HashMap

中獲得一個疊代器(

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 裝置,如果使用者允許你的程式獲得這個意圖(intent),那麼它(應用程式)會自動獲得權限。如果沒有,你必須在該裝置連接配接之前之前,就已經為你的應用請求好權限。

明确的請求權限在某些場景下是有必要的,比如當你的應用程式枚舉已經連接配接好的 USB 裝置時然後想要與其中一個進行通信。你必須在嘗試與其通信前檢查是否獲得了通路裝置的權限。如果沒有沒這麼做,并且使用者拒絕你通路裝置的話,那你就會得到一個運作時的錯誤。

為了明确獲得權限,首先要建立一個廣播接收器。該廣播接收器監聽你調用 requestPermission 時發出的廣播。調用 requestPermission 會展示一個對話框(

dialog

) 向使用者請求連接配接 USB 裝置的權限。下面簡單的代碼展示了怎麼建立這個廣播接收器:

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, , new Intent(ACTION_USB_PERMISSION), );
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
           

要展示請求連接配接 USB 裝置權限的對話框,調用如下的 requestPermission 方法:

UsbDevice device;
...
mUsbManager.requestPermission(device, mPermissionIntent);
           

當使用者回應了該對話框,你的廣播接收器會接受到一個額外包含了 EXTRA_PERMISSION_GRANTED 的意圖(intent),這個 EXTRA_PERMISSION_GRANTED 就是使用者操作對話框的結果,是一個

boolean

類型的值。在連接配接裝置前,檢查此額外值是否為 true 。

與裝置通信

與 USB 裝置通信可以使用同步的方式也可以使用異步的方式。在任意場景中,你需要建立一個進行所有資料傳輸的新線程,這樣你才不會鎖住 UI 線程 (安卓不推薦在 UI 線程中進行 I/O 操作)。要正确的與一個裝置進行通信,你需要獲得合适的

UsbInterface

UsbEndpoint

(你要使用

UsbDeviceConnection

在此

UsbEndpoint

進行發請求操作)。通常,你的代碼應該:

  • 檢查

    UsbDevice

    對象的屬性,例如 産品id,供應商id,或裝置類 來确定你是否要與這個裝置通信。
  • 當你确定你就是要連接配接這個裝置的時候,找到你要用來與之通信的

    UsbInterface

    和對應該

    UsbInterface

    UsbEndpoint

    。接口可以有一個或多個端點,通常會輸入端點和輸出端點來支援兩種通信方式。
  • 當你找到了正确的端點,在該端點上打開一個

    UsbDeviceConnection

    連接配接。
  • 你想在一個

    endpoint

    進行資料傳輸需要使用 bulkTransfer() 方法或者 controlTransfer() 方法。 不要在主線程中執行上述操作,防止造成 UI 線程阻塞。 要想檢視關于安卓中使用線程的更多資訊,請檢視 Processes and Threads。

如下代碼段是執行同步資料傳輸的一個簡單的方法。你的代碼應該具有更多的邏輯來正确地找到正确的接口和端點來進行通信, 并且應該在非 UI 線程中進行資料的傳輸 :

private Byte[] bytes;
private static int TIMEOUT = ;
private boolean forceClaim = true;

...

UsbInterface intf = device.getInterface();
UsbEndpoint endpoint = intf.getEndpoint();
UsbDeviceConnection connection = mUsbManager.openDevice(device);
connection.claimInterface(intf, forceClaim);
//do in another thread
connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); 
           

要異步發送資料,使用

UsbRequest

類 來初始化并排隊異步請求,然後等待 requestWait() 方法的傳回結果。

要獲得更多資訊,檢視 ADBTest sample ,這個示例展示了怎麼實作異步的批量傳輸,還有 MissileLauncher sample ,這個示例展示了如何異步地監聽中斷端點。

結束與裝置的通信

當你與裝置的通信已經完成,或者裝置已經斷開連接配接,通過調研 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
            }
        }
    }
};
           

在應用程式中建立這個動态的廣播接受者(不用在清單檔案中注冊,那是靜态廣播接受者),運作你的應用程式僅僅在運作時監聽分離事件。這種途徑,分離事件僅僅發送到目前正在運作的應用程式中,而不是發送給了所有的應用程式。

Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.

上次更新日期:五月 24, 2018