USB 主機總覽

如果 Android 裝置處於 USB 主機模式,就能做為 USB 主機並供電流。 並列舉已連接的 USB 裝置。Android 3.1 以上版本支援 USB 主機模式。

API 總覽

在開始之前,請務必先瞭解您需要使用哪些課程。 下表說明 android.hardware.usb 套件中的 USB 主機 API。

表 1. USB Host API

類別 說明
UsbManager 可讓您列舉及通訊已連接的 USB 裝置。
UsbDevice 代表連接的 USB 裝置,其中包含存取裝置識別資訊的方法 資訊、介面和端點
UsbInterface 代表 USB 裝置的介面,可定義 裝置。裝置可具有一或多個通訊介面。
UsbEndpoint 代表介面端點,這是這個介面的通訊管道。一個 介面可以有一或多個端點,且通常會有輸入和輸出端點 與裝置進行雙向通訊
UsbDeviceConnection 代表與裝置之間的連線,該裝置會在端點上轉移資料。本課程 以同步或非同步的方式傳送資料。
UsbRequest 代表透過 UsbDeviceConnection 與裝置通訊的非同步要求。
UsbConstants 定義與 Linux/usb/ch9.h 中 linux/usb/ch9.h 定義的 USB 常數 核心。

在大部分情況下,您必須使用這些類別 (只有在進行非同步通訊時才需要 UsbRequest) 與 USB 裝置通訊一般來說,您會取得 UsbManager 來擷取所需的 UsbDevice。 拿到裝置後,您需要找出適當的 UsbInterface 和其 UsbEndpoint 進行通訊取得正確的端點後,請開啟 UsbDeviceConnection,與 USB 裝置進行通訊。

Android 資訊清單規定

下列清單說明您必須在應用程式的資訊清單檔案中,加入哪些內容 使用 USB 主機 API:

  • 因為不是所有 Android 裝置都保證支援 USB 主機 API, 加入 <uses-feature> 元素,以宣告應用程式使用 android.hardware.usb.host 功能。
  • 將應用程式的最低 SDK 設為 API 級別 12 以上。USB Host API 預測出的符記
  • 如果您希望應用程式在連接 USB 裝置時收到通知,請指定 <intent-filter><meta-data> 元素配對 主要活動中的 android.hardware.usb.action.USB_DEVICE_ATTACHED 意圖。 <meta-data> 元素指向宣告的外部 XML 資源檔案 與要偵測的裝置識別相關資訊。

    在 XML 資源檔案中,宣告 USB 的 <usb-device> 元素 。下表說明 <usb-device>。一般來說,如要篩選資料,請使用供應商和產品 ID ,若想篩選群組,請使用類別、子類別和通訊協定 例如大量儲存裝置或數位相機等可以不指定 所有這些屬性指定沒有符合每部 USB 裝置的屬性,因此只需指定 應用程式要求:

    • vendor-id
    • product-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,並指定具有指定 屬性:

<?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. 使用意圖篩選器尋找已連接的 USB 裝置,方法是使用意圖篩選器,讓系統在使用者收到通知時傳送通知 連接 USB 裝置,或列舉已連接的 USB 裝置。
  2. 如果尚未取得 USB 裝置,請要求使用者授予連接 USB 裝置的權限。
  3. 在適當介面上讀取及寫入資料,與 USB 裝置進行通訊 端點。

探索裝置

應用程式可以使用意圖篩選器偵測 USB 裝置,方法是使用 使用者連接裝置,或列舉已連接的 USB 裝置。使用 如果您想讓應用程式自動偵測輸出內容, 。如果您要取得所有已連接的 USB 裝置清單,請列舉已連接的 USB 裝置 連結的裝置,或是您的應用程式未篩選意圖。

使用意圖篩選器

如要讓應用程式探索特定 USB 裝置,您可以指定意圖篩選器 android.hardware.usb.action.USB_DEVICE_ATTACHED 意圖的篩選。此外, 這個意圖篩選器,需要指定用於指定 USB 屬性的資源檔案 例如產品和供應商 ID使用者連上與裝置相符的裝置時 篩選器,系統會顯示對話方塊,詢問使用者是否要啟動您的應用程式。 如果使用者接受,您的應用程式將自動取得裝置存取權限,直到 裝置的連線已中斷。

以下範例說明如何宣告意圖篩選器:

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

在活動中,您可以取得代表 UsbDevice 從意圖中附加的裝置:

Kotlin

val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)

Java

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

列舉裝置

如果應用程式想要檢查目前連接的所有 USB 裝置 在應用程式執行期間列舉裝置。使用 getDeviceList() 方法取得 讓連接的 USB 裝置雜湊對應會以 USB 裝置的名稱做為索引鍵,也可以用於 或從地圖取得裝置

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
...
val deviceList = manager.getDeviceList()
val device = deviceList.get("deviceName")

Java

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

如有需要,您也可以從雜湊對應中取得疊代器,然後處理每個裝置 逐一:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
..
val deviceList: HashMap<String, UsbDevice> = manager.deviceList
deviceList.values.forEach { device ->
    // your code
}

Java

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 裝置通訊之前,您的應用程式必須取得 使用者。

注意:如果您的應用程式使用 意圖篩選器來尋找連接的 USB 裝置,且該裝置會自動接收 是否允許應用程式處理意圖。如果沒有,您就必須 明確授予其權限。

在某些情況下 (例如 應用程式列舉已連接,且想與 進行通訊的 USB 裝置。 第一項。您必須先確認裝置的存取權限,才能與裝置通訊。如果 如果使用者拒絕存取裝置,您會收到執行階段錯誤。

如要明確取得權限,請先建立廣播接收器。這個接收器監聽了以下事件: 您呼叫 requestPermission() 時接收廣播的意圖。呼叫 requestPermission() 會顯示對話方塊 使用者要求連接到裝置的權限。以下程式碼範例說明如何 建立廣播接收器:

Kotlin

private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"

private val usbReceiver = object : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (ACTION_USB_PERMISSION == intent.action) {
            synchronized(this) {
                val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    device?.apply {
                        // call method to set up device communication
                    }
                } else {
                    Log.d(TAG, "permission denied for device $device")
                }
            }
        }
    }
}

Java

private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
private final BroadcastReceiver usbReceiver = 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() 方法的 活動:

Kotlin

private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
...
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
...
permissionIntent = PendingIntent.getBroadcast(this, 0,
                  Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE)
val filter = IntentFilter(ACTION_USB_PERMISSION)
registerReceiver(usbReceiver, filter)

Java

UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
...
permissionIntent = PendingIntent.getBroadcast(this, 0,
              new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);

如要顯示要求使用者授予裝置連線權限的對話方塊,請呼叫 requestPermission() 方法:

Kotlin

lateinit var device: UsbDevice
...
usbManager.requestPermission(device, permissionIntent)

Java

UsbDevice device;
...
usbManager.requestPermission(device, permissionIntent);

當使用者回覆對話方塊時,您的廣播接收器會接收包含 額外 EXTRA_PERMISSION_GRANTED,為布林值 能代表答案請先檢查這個額外項目的值是否為 True,然後再連線至 裝置。

與裝置通訊

與 USB 裝置的通訊可以同步或非同步。無論是哪一種情況,您 應該建立一個新的執行緒來執行所有資料傳輸作業 使用者介面執行緒。如要正確設定與裝置的通訊,需取得適當的 UsbInterfaceUsbEndpoint 要透過 UsbDeviceConnection 在這個端點上通訊及傳送要求的裝置。一般來說,您的程式碼應:

  • 查看 UsbDevice 物件的屬性,例如產品 ID、 還是裝置類別,以判斷您是否要與 裝置。
  • 如果確定想要與裝置通訊,請尋找適當的 UsbInterface您要用來與 適當 UsbEndpoint。只有在一個介面下 通常會有雙向輸入和輸出端點 非常適用於內部微服務通訊
  • 找到正確的端點後,請開啟 UsbDeviceConnection 這個端點
  • 使用 bulkTransfer()controlTransfer() 方法在端點提供要傳輸的資料。請 執行這個步驟,以防阻斷主 UI 執行緒。如要 如要進一步瞭解如何在 Android 中使用執行緒,請參閱「處理程序和 執行緒

下列程式碼片段是執行同步資料移轉作業的簡易方式。你的代碼 應有更多邏輯,即可正確找到要通訊的正確介面和端點 且應在與主 UI 執行緒不同的執行緒中傳輸資料:

Kotlin

private lateinit var bytes: ByteArray
private val TIMEOUT = 0
private val forceClaim = true

...

device?.getInterface(0)?.also { intf ->
    intf.getEndpoint(0)?.also { endpoint ->
        usbManager.openDevice(device)?.apply {
            claimInterface(intf, forceClaim)
            bulkTransfer(endpoint, bytes, bytes.size, TIMEOUT) //do in another thread
        }
    }
}

Java

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

...

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

如要非同步傳送資料,請使用 UsbRequest 類別對 initializequeue 進行非同步要求,然後等待結果 搭配 requestWait()

終止與裝置的通訊

完成與裝置通訊或裝置已卸離後,請關閉 UsbInterfaceUsbDeviceConnection: 呼叫 releaseInterface()close()。如要監聽卸離的事件, 建立廣播接收器,如下所示:

Kotlin

var usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        if (UsbManager.ACTION_USB_DEVICE_DETACHED == intent.action) {
            val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
            device?.apply {
                // call your method that cleans up and closes communication with the device
            }
        }
    }
}

Java

BroadcastReceiver usbReceiver = 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
            }
        }
    }
};

在應用程式中 (而非資訊清單) 中建立廣播接收器,即可 應用程式,專門處理卸離的事件。如此一來 只會傳送至目前執行中的應用程式,不向所有應用程式廣播。