USB 配件總覽

USB 配件模式可讓使用者連接專為 Android 裝置設計的 USB 主機硬體。配件必須遵循 Android 配件開發套件說明文件中所述的 Android 配件通訊協定。這樣一來,無法做為 USB 主機的 Android 裝置仍可與 USB 硬體互動。當 Android 裝置處於 USB 配件模式時,連接的 Android USB 配件可做為主機,為 USB 匯流排供電,並列舉連接的裝置。Android 3.1 (API 級別 12) 支援 USB 配件模式,而這項功能也向後移植至 Android 2.3.4 (API 級別 10),以便支援更多的裝置。

選擇合適的 USB 配件 API

雖然 Android 3.1 的平台已導入 USB 配件 API,但您也可以在 Android 2.3.4 版中,透過 Google API 外掛程式程式庫取得這些 API。由於這些 API 是使用外部程式庫向後移植,因此您可以匯入兩個套件以支援 USB 配件模式。根據您要支援的 Android 裝置,您可能需要使用其中一種裝置:

  • com.android.future.usb:如要在 Android 2.3.4 中支援 USB 配件模式,Google API 外掛程式程式庫含有向後移植的 USB 配件 API,而且這些 API 會包含在這個命名空間中。Android 3.1 也支援匯入及呼叫這個命名空間中的類別,以支援使用外掛程式程式庫編寫的應用程式。這個外掛程式程式庫是 android.hardware.usb 配件 API 周圍的精簡包裝函式,不支援 USB 主機模式。如果想支援多種支援 USB 配件模式的裝置,請使用外掛程式程式庫並匯入這個套件。請注意,並非所有 Android 2.3.4 裝置都必須支援 USB 配件功能。每家裝置製造商都會決定是否支援這項功能,因此您必須在資訊清單檔案中宣告這項功能。
  • android.hardware.usb:這個命名空間包含在 Android 3.1 版中支援 USB 配件模式的類別。這個套件包含在架構 API 中,因此 Android 3.1 支援 USB 配件模式,無須使用外掛程式程式庫。如果您只重視 Android 3.1 以上版本裝置支援 USB 配件模式 (可在資訊清單檔案中宣告),請使用這個套件。

安裝 Google API 外掛程式程式庫

如果您想安裝這個外掛程式,請透過 SDK Manager 安裝 Google API Android API 10 套件。如要進一步瞭解如何安裝外掛程式程式庫,請參閱「安裝 Google API 外掛程式」。

API 總覽

由於外掛程式程式庫是架構 API 的包裝函式,因此支援 USB 配件功能的類別相似。即使使用外掛程式程式庫,還是可以使用 android.hardware.usb 的參考說明文件。

注意:不過請注意,外掛程式程式庫與架構 API 之間有需要注意的用量差異

下表說明支援 USB 配件 API 的類別:

類別 說明
UsbManager 允許你列舉連接的 USB 配件並進行通訊。
UsbAccessory 代表 USB 配件,並包含存取其識別資訊的方法。

外掛程式程式庫和平台 API 之間的使用差異

使用 Google API 外掛程式程式庫與平台 API 共有兩種用途。

如果您使用的是外掛程式程式庫,必須透過下列方式取得 UsbManager 物件:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

如果您不是使用外掛程式程式庫,則必須透過下列方式取得 UsbManager 物件:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);

當您使用意圖篩選器篩選已連線的配件時,UsbAccessory 物件會包含在傳送至應用程式的意圖中。如果您使用的是外掛程式程式庫,必須透過下列方式取得 UsbAccessory 物件:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

如果您不是使用外掛程式程式庫,則必須透過下列方式取得 UsbAccessory 物件:

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Android 資訊清單規定

以下清單說明使用 USB 配件 API 之前,需要新增至應用程式的資訊清單檔案。您可以透過資訊清單和資源檔案範例瞭解如何宣告這些項目:

  • 由於並非所有 Android 裝置都支援 USB 配件 API,因此請加入 <uses-feature> 元素,宣告應用程式使用 android.hardware.usb.accessory 功能。
  • 如果您使用的是外掛程式程式庫,請新增 <uses-library> 元素,指定程式庫的 com.android.future.usb.accessory
  • 如果使用的是外掛程式程式庫,請將應用程式的最低 SDK 設為 API 級別 10;如果您使用 android.hardware.usb 套件,則請將應用程式最低 SDK 設為 12。
  • 如果您希望應用程式收到連接 USB 配件的通知,請在主要活動中為 android.hardware.usb.action.USB_ACCESSORY_ATTACHED 意圖指定 <intent-filter><meta-data> 元素配對。<meta-data> 元素會指向外部 XML 資源檔案,宣告要偵測的配件識別資訊。

    在 XML 資源檔案中,為您要篩選的配件宣告 <usb-accessory> 元素。每個 <usb-accessory> 都可以具有下列屬性:

    • manufacturer
    • model
    • version

    我們不建議篩選version。配件或裝置不一定會指定版本字串 (無論是有意或無意間指定版本字串)。如果應用程式宣告要篩選的版本屬性,且配件或裝置未指定版本字串,則在舊版 Android 中會導致 NullPointerException。這個問題在 Android 12 中已修正。

    將資源檔案儲存在 res/xml/ 目錄中。資源檔案名稱 (不含 .xml 副檔名) 必須與您在 <meta-data> 元素中指定的名稱相同。XML 資源檔案的格式也會顯示在下方範例

資訊清單和資源檔案範例

以下範例顯示資訊清單範例及其對應的資源檔案:

<manifest ...>
    <uses-feature android:name="android.hardware.usb.accessory" />
    
    <uses-sdk android:minSdkVersion="<version>" />
    ...
    <application>
      <uses-library android:name="com.android.future.usb.accessory" />
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter" />
        </activity>
    </application>
</manifest>

在這種情況下,下列資源檔案應儲存在 res/xml/accessory_filter.xml 中,並指定可篩選具有對應型號、製造商和版本的配件。配件會將下列屬性傳送給 Android 裝置:

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

<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

使用配件

使用者將 USB 配件連接到 Android 裝置時,Android 系統會判斷您的應用程式是否對已連結的配件感興趣。如果是的話,您可以視需求設定與配件之間的通訊。如要這麼做,您的應用程式必須:

  1. 使用意圖篩選器來篩選配件附加事件,或列出已連結的配件,然後尋找合適的配件,即可找出已連結的配件。
  2. 在尚未取得配件的情況下,要求使用者授予與配件通訊的權限。
  3. 在適當的介面端點上讀取及寫入資料,與配件進行通訊。

尋找配件

應用程式可透過意圖篩選器,在使用者連結配件或列舉已經連接的配件時收到通知,藉此尋找配件。如果您希望應用程式能自動偵測所需的配件,使用意圖篩選器就會很實用。如果您希望取得所有已連結配件的清單,或者應用程式未篩選意圖,列舉已連接的配件會很有用。

使用意圖篩選器

如要讓應用程式發現特定 USB 配件,您可以指定意圖篩選器來篩選出 android.hardware.usb.action.USB_ACCESSORY_ATTACHED 意圖。除了這個意圖篩選器之外,您還必須指定資源檔案,以指定 USB 配件的屬性,例如製造商、型號和版本。

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

<activity ...>
    ...
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
    </intent-filter>

    <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
        android:resource="@xml/accessory_filter" />
</activity>

以下範例說明瞭如何宣告指定所需 USB 配件的對應資源檔案:

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

<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

在您的活動中,您可以從意圖 (使用外掛程式程式庫) 取得代表附加配件的 UsbAccessory,如下所示:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

或如下所示 (透過平台 API):

Kotlin

val accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY) as UsbAccessory

Java

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

列舉配件

您可以讓應用程式列舉可在應用程式執行期間識別出自我的圖塊。

使用 getAccessoryList() 方法取得已連接的所有 USB 配件陣列:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val accessoryList: Array<out UsbAccessory> = manager.accessoryList

Java

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessoryList = manager.getAccessoryList();

注意: 一次只能支援一個連接的配件。

取得與配件通訊的權限

與 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 accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)

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

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) {
                UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if(accessory != null){
                        // call method to set up accessory communication
                    }
                }
                else {
                    Log.d(TAG, "permission denied for accessory " + accessory);
                }
            }
        }
    }
};

如要註冊廣播接收器,請將該指令加入活動的 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), 0)
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), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);

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

Kotlin

lateinit var accessory: UsbAccessory
...
usbManager.requestPermission(accessory, permissionIntent)

Java

UsbAccessory accessory;
...
usbManager.requestPermission(accessory, permissionIntent);

當使用者回覆對話方塊時,廣播接收器會收到包含 EXTRA_PERMISSION_GRANTED 額外項目的意圖,這是代表答案的布林值。連線至配件前,請先詳閱下方的額外項目來確認值是 true。

與配件通訊

您可以透過 UsbManager 與配件進行通訊,藉此取得檔案描述元,您可以設定輸入和輸出串流,以便將資料讀取和寫入到描述元。串流代表配件的輸入和輸出大量端點。您必須在其他執行緒中設定裝置和配件之間的通訊,以免鎖定主要 UI 執行緒。以下範例說明如何開啟配件,以便與裝置進行通訊:

Kotlin

private lateinit var accessory: UsbAccessory
private var fileDescriptor: ParcelFileDescriptor? = null
private var inputStream: FileInputStream? = null
private var outputStream: FileOutputStream? = null
...

private fun openAccessory() {
    Log.d(TAG, "openAccessory: $mAccessory")
    fileDescriptor = usbManager.openAccessory(accessory)
    fileDescriptor?.fileDescriptor?.also { fd ->
        inputStream = FileInputStream(fd)
        outputStream = FileOutputStream(fd)
        val thread = Thread(null, this, "AccessoryThread")
        thread.start()
    }
}

Java

UsbAccessory accessory;
ParcelFileDescriptor fileDescriptor;
FileInputStream inputStream;
FileOutputStream outputStream;
...

private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    fileDescriptor = usbManager.openAccessory(accessory);
    if (fileDescriptor != null) {
        FileDescriptor fd = fileDescriptor.getFileDescriptor();
        inputStream = new FileInputStream(fd);
        outputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}

在執行緒的 run() 方法中,您可以使用 FileInputStreamFileOutputStream 物件讀取及寫入配件。使用 FileInputStream 物件從配件讀取資料時,請確保您使用的緩衝區夠大,可以儲存 USB 封包資料。Android 配件通訊協定支援高達 16384 個位元組的封包緩衝區,因此為了方便起見,您可以選擇一律宣告緩衝區大小。

注意:在較低等級的封包是 64 個位元組 (用於 USB 全速配件),而 USB 高速配件的封包則是 512 個位元組。為求簡單,Android 配件通訊協定會將兩個速度的封包組合在一起,一個邏輯封包。

如果想進一步瞭解如何在 Android 中使用執行緒,請參閱「處理程序和執行緒」。

終止與配件之間的通訊

當您完成與配件通訊,或配件已卸離時,請呼叫 close() 來關閉您開啟的檔案描述元。如要監聽卸離的事件,請建立如下的廣播接收器:

Kotlin

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

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED == intent.action) {
            val accessory: UsbAccessory? = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY)
            accessory?.apply {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
}

Java

BroadcastReceiver usbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
            UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
            if (accessory != null) {
                // call your method that cleans up and closes communication with the accessory
            }
        }
    }
};

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