Tổng quan về phụ kiện USB

Chế độ phụ kiện USB cho phép người dùng kết nối Phần cứng lưu trữ USB được thiết kế riêng cho các thiết bị chạy Android. Các phụ kiện phải tuân thủ về giao thức phụ kiện Android nêu trong tài liệu về Bộ phát triển phụ kiện Android. Điều này cho phép các thiết bị chạy Android không thể hoạt động như một máy chủ USB vẫn tương tác với USB phần cứng. Khi một thiết bị chạy Android ở chế độ phụ kiện USB, USB Android đi kèm phụ kiện đóng vai trò là máy chủ, cung cấp nguồn cho bus USB và liệt kê các thiết bị được kết nối. Android 3.1 (API cấp 12) hỗ trợ chế độ phụ kiện USB và tính năng này cũng được điều chỉnh cho phiên bản cũ Android 2.3.4 (API cấp 10) cho phép hỗ trợ nhiều thiết bị hơn.

Chọn API phụ kiện USB phù hợp

Mặc dù API phụ kiện USB đã được đưa vào nền tảng trong Android 3.1, nhưng chúng cũng có sẵn trong Android 2.3.4 bằng cách sử dụng thư viện tiện ích bổ sung API của Google. Bởi vì các API này điều chỉnh cho phiên bản cũ bằng cách sử dụng thư viện bên ngoài, bạn có thể nhập hai gói để hỗ trợ USB chế độ phụ kiện. Tuỳ thuộc vào thiết bị chạy Android mà bạn muốn hỗ trợ, bạn có thể phải sử dụng từng cái với nhau:

  • com.android.future.usb: Để hỗ trợ chế độ phụ kiện USB trong Android 2.3.4, Tiện ích bổ sung API của Google thư viện bao gồm các API phụ kiện USB được điều chỉnh cho phiên bản cũ và các API này có trong thư viện này không gian tên. Android 3.1 cũng hỗ trợ việc nhập và gọi các lớp trong không gian tên này để hỗ trợ các ứng dụng được viết bằng thư viện tiện ích bổ sung. Thư viện tiện ích bổ sung này là một trình bao bọc mỏng xung quanh API phụ kiện android.hardware.usb và không hỗ trợ chế độ hỗ trợ USB. Nếu bạn muốn hỗ trợ nhiều thiết bị nhất có hỗ trợ chế độ phụ kiện USB, hãy sử dụng tiện ích bổ sung thư viện và nhập gói này. Điều quan trọng cần lưu ý là không phải tất cả các thiết bị Android 2.3.4 đều cần thiết để hỗ trợ tính năng phụ kiện USB. Mỗi nhà sản xuất thiết bị riêng lẻ sẽ quyết định liệu có hỗ trợ chức năng này hay không. Đó là lý do bạn phải khai báo tính năng này trong tệp kê khai .
  • android.hardware.usb: Không gian tên này chứa các lớp hỗ trợ USB chế độ phụ kiện trong Android 3.1. Gói này được đưa vào như một phần của API khung, vì vậy Android 3.1 hỗ trợ chế độ phụ kiện USB mà không cần sử dụng thư viện tiện ích bổ sung. Sử dụng gói này nếu bạn chỉ quan tâm đến các thiết bị Android 3.1 hoặc mới hơn có hỗ trợ phần cứng cho USB chế độ phụ kiện mà bạn có thể khai báo trong tệp kê khai.

Cài đặt thư viện tiện ích bổ sung API của Google

Nếu muốn cài đặt tiện ích bổ sung, bạn có thể thực hiện bằng cách cài đặt API Google Android API 10 bằng Trình quản lý SDK. Xem Cài đặt Google API Tiện ích bổ sung để biết thêm thông tin về cách cài đặt thư viện tiện ích bổ sung.

Tổng quan về API

Vì thư viện tiện ích bổ sung là một trình bao bọc cho các API khung, nên các lớp hỗ trợ Tính năng phụ kiện USB cũng tương tự. Bạn có thể sử dụng tài liệu tham khảo cho android.hardware.usb ngay cả khi đang sử dụng thư viện tiện ích bổ sung.

Lưu ý: Tuy nhiên, có một trường hợp sử dụng nhỏ điểm khác biệt giữa thư viện tiện ích bổ sung và các API khung mà bạn cần lưu ý.

Bảng sau đây mô tả các lớp hỗ trợ API phụ kiện USB:

Lớp Mô tả
UsbManager Cho phép bạn liệt kê và giao tiếp với các phụ kiện USB đã kết nối.
UsbAccessory Đại diện cho một phụ kiện USB và chứa các phương thức để truy cập vào thông tin nhận dạng phụ kiện của bạn.

Sự khác biệt về cách sử dụng giữa API thư viện tiện ích bổ sung và API nền tảng

Có hai sự khác biệt về cách sử dụng giữa việc sử dụng thư viện tiện ích bổ sung API của Google và nền tảng API.

Nếu đang sử dụng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbManager theo cách sau:

Kotlin

val manager = UsbManager.getInstance(this)

Java

UsbManager manager = UsbManager.getInstance(this);

Nếu không dùng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbManager theo cách sau:

Kotlin

val manager = getSystemService(Context.USB_SERVICE) as UsbManager

Java

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

Khi bạn lọc tìm một phụ kiện đã kết nối có bộ lọc ý định, đối tượng UsbAccessory sẽ nằm trong ý định được truyền đến . Nếu đang sử dụng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbAccessory theo cách sau:

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

Nếu không dùng thư viện tiện ích bổ sung, bạn phải lấy đối tượng UsbAccessory theo cách sau:

Kotlin

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

Java

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

Yêu cầu về tệp kê khai Android

Danh sách sau đây mô tả những gì bạn cần thêm vào tệp kê khai của ứng dụng trước khi hoạt động với API phụ kiện USB. Tệp kê khai và tệp tài nguyên ví dụ minh hoạ cách khai báo các mục này:

  • Vì không phải tất cả thiết bị chạy Android đều được đảm bảo hỗ trợ API phụ kiện USB, thêm một phần tử <uses-feature> khai báo rằng ứng dụng của bạn sử dụng tính năng android.hardware.usb.accessory.
  • Nếu bạn đang sử dụng thư viện tiện ích bổ sung, thêm phần tử <uses-library> để chỉ định com.android.future.usb.accessory cho thư viện.
  • Đặt SDK tối thiểu của ứng dụng thành API cấp 10 nếu bạn đang dùng thư viện tiện ích bổ sung hoặc 12 nếu bạn đang dùng gói android.hardware.usb.
  • Nếu bạn muốn ứng dụng của mình được thông báo về phụ kiện USB đi kèm, hãy chỉ định Cặp phần tử <intent-filter><meta-data> cho phần tử Ý định android.hardware.usb.action.USB_ACCESSORY_ATTACHED trong hoạt động chính của bạn. Phần tử <meta-data> trỏ đến một tệp tài nguyên XML bên ngoài khai báo thông tin nhận dạng về phụ kiện mà bạn muốn phát hiện.

    Trong tệp tài nguyên XML, hãy khai báo các phần tử <usb-accessory> cho phần tử phụ kiện mà bạn muốn lọc. Mỗi <usb-accessory> có thể có các thuộc tính sau:

    • manufacturer
    • model
    • version

    Bạn không nên lọc trên version. Một phụ kiện hoặc thiết bị không phải lúc nào cũng chỉ định chuỗi phiên bản (cố ý hoặc vô tình). Khi ứng dụng của bạn khai báo thuộc tính phiên bản để lọc và phụ kiện hoặc thiết bị không chỉ định chuỗi phiên bản, điều này sẽ dẫn đến NullPointerException trên các phiên bản Android cũ hơn. Vấn đề này đã được khắc phục trong Android 12.

    Lưu tệp tài nguyên trong thư mục res/xml/. Tên tệp tài nguyên (không có đuôi .xml) phải giống với đuôi mà bạn đã chỉ định trong Phần tử <meta-data>. Định dạng của tệp tài nguyên XML cũng được thể hiện trong ví dụ bên dưới.

Ví dụ về tệp kê khai và tệp tài nguyên

Ví dụ sau đây cho thấy một tệp kê khai mẫu và tệp tài nguyên tương ứng của tệp kê khai đó:

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

Trong trường hợp này, tệp tài nguyên sau cần được lưu trong res/xml/accessory_filter.xml và quy định rằng mọi phụ kiện có kiểu máy, nhà sản xuất và phiên bản tương ứng sẽ được lọc. Phụ kiện sẽ gửi những thông tin này cho biết thiết bị chạy Android:

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

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

Tương thích với các phụ kiện

Khi người dùng kết nối phụ kiện USB với một thiết bị chạy Android, hệ thống Android có thể xác định xem ứng dụng của bạn có quan tâm đến phụ kiện đã kết nối hay không. Nếu có, bạn có thể đặt kết nối với phụ kiện nếu muốn. Để làm được việc này, ứng dụng của bạn phải:

  1. Khám phá các phụ kiện đã kết nối bằng cách dùng bộ lọc ý định để lọc phụ kiện các sự kiện đính kèm hoặc bằng cách liệt kê các phụ kiện được kết nối và tìm phụ kiện phù hợp.
  2. Hãy yêu cầu người dùng cấp quyền để giao tiếp với phụ kiện (nếu chưa) thu được.
  3. Giao tiếp với phụ kiện bằng cách đọc và ghi dữ liệu trên giao diện thích hợp điểm cuối.

Khám phá một phụ kiện

Ứng dụng của bạn có thể phát hiện các phụ kiện bằng cách sử dụng bộ lọc ý định để nhận được thông báo khi người dùng kết nối một phụ kiện hoặc bằng cách liệt kê các phụ kiện đã kết nối. Sử dụng rất hữu ích nếu bạn muốn ứng dụng của bạn tự động phát hiện phụ kiện mong muốn. Việc liệt kê các phụ kiện được kết nối rất hữu ích nếu bạn muốn có danh sách tất cả các phụ kiện các phụ kiện đã kết nối hoặc nếu ứng dụng của bạn không lọc ý định.

Sử dụng bộ lọc ý định

Để ứng dụng của bạn khám phá một phụ kiện USB cụ thể, bạn có thể chỉ định một bộ lọc ý định để lọc ý định android.hardware.usb.action.USB_ACCESSORY_ATTACHED. Dọc theo thì với bộ lọc ý định này, bạn cần chỉ định tệp tài nguyên chỉ định các thuộc tính của USB phụ kiện, chẳng hạn như nhà sản xuất, kiểu máy và phiên bản.

Ví dụ sau đây cho thấy cách khai báo bộ lọc ý định:

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

Ví dụ sau đây trình bày cách khai báo tệp tài nguyên tương ứng chỉ định Phụ kiện USB mà bạn quan tâm:

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

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

Trong hoạt động, bạn có thể lấy UsbAccessory biểu thị phụ kiện đi kèm theo ý định như sau (với thư viện tiện ích bổ sung):

Kotlin

val accessory = UsbManager.getAccessory(intent)

Java

UsbAccessory accessory = UsbManager.getAccessory(intent);

hoặc như thế này (với API nền tảng):

Kotlin

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

Java

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

Liệt kê phụ kiện

Bạn có thể để ứng dụng liệt kê các phụ kiện tự xác định trong khi ứng dụng của bạn đang chạy.

Sử dụng phương thức getAccessoryList() để lấy một mảng cho tất cả phụ kiện USB được kết nối:

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();

Lưu ý: Chỉ hỗ trợ một phụ kiện đã kết nối tại một lúc.

Xin phép để giao tiếp với một phụ kiện

Trước khi giao tiếp với phụ kiện USB, ứng dụng của bạn phải được người dùng.

Lưu ý: Nếu ứng dụng của bạn sử dụng bộ lọc ý định để khám phá các phụ kiện khi chúng được kết nối, bộ lọc này sẽ tự động nhận được nếu người dùng cho phép ứng dụng của bạn xử lý ý định. Nếu không, bạn phải yêu cầu bạn cấp quyền một cách rõ ràng trong ứng dụng của mình trước khi kết nối với phụ kiện.

Bạn có thể cần phải yêu cầu cấp quyền rõ ràng trong một số trường hợp như khi ứng dụng liệt kê các phụ kiện đã được kết nối và sau đó muốn giao tiếp với một. Bạn phải kiểm tra quyền truy cập vào một phụ kiện rồi mới có thể giao tiếp với phụ kiện đó. Nếu không, bạn sẽ gặp lỗi thời gian chạy nếu người dùng từ chối cấp quyền truy cập vào phụ kiện.

Để có được quyền rõ ràng, trước tiên, hãy tạo một broadcast receiver. Bộ thu này lắng nghe ý định được truyền tin khi bạn gọi requestPermission(). Lệnh gọi đến requestPermission() sẽ hiển thị một hộp thoại cho lệnh gọi đến người dùng yêu cầu cấp quyền kết nối với phụ kiện. Mã mẫu sau đây cho biết cách tạo broadcast receiver:

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);
                }
            }
        }
    }
};

Để đăng ký broadcast receiver, hãy đặt mã này vào phương thức onCreate() trong hoạt động:

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

Để hiển thị hộp thoại yêu cầu người dùng cấp quyền kết nối với phụ kiện, hãy gọi Phương thức requestPermission():

Kotlin

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

Java

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

Khi người dùng trả lời hộp thoại, broadcast receiver của bạn sẽ nhận được ý định chứa EXTRA_PERMISSION_GRANTED bổ sung, là một giá trị boolean cho câu trả lời. Kiểm tra phần bổ sung này để biết giá trị true trước khi kết nối với phụ kiện.

Kết nối với một phụ kiện

Bạn có thể giao tiếp với phụ kiện bằng cách dùng UsbManager để lấy chỉ số mô tả tệp mà bạn có thể thiết lập luồng đầu vào và đầu ra để đọc và ghi dữ liệu mã mô tả. Các luồng này biểu thị các điểm cuối hàng loạt đối với đầu vào và đầu ra của phụ kiện. Bạn nên đặt tăng khả năng giao tiếp giữa thiết bị và phụ kiện trong một chuỗi khác để bạn không khoá luồng giao diện người dùng chính. Ví dụ sau đây cho biết cách mở một phụ kiện để giao tiếp:

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();
    }
}

Trong phương thức run() của chuỗi, bạn có thể đọc và ghi vào phụ kiện bằng cách sử dụng các đối tượng FileInputStream hoặc FileOutputStream. Khi đọc dữ liệu từ một phụ kiện có đối tượng FileInputStream, hãy đảm bảo rằng vùng đệm mà bạn sử dụng đủ lớn để lưu trữ dữ liệu gói USB. Giao thức phụ kiện Android hỗ trợ vùng đệm gói lên đến 16384 byte, vì vậy bạn có thể chọn luôn khai báo vùng đệm của mình là vùng đệm này kích thước lớn hơn để đơn giản hoá.

Lưu ý: Ở cấp độ thấp hơn, các gói có kích thước 64 byte cho USB phụ kiện tốc độ tối đa và 512 byte đối với phụ kiện tốc độ cao USB. Phụ kiện Android giao thức gói các gói lại với nhau cho cả hai tốc độ thành một gói logic để đơn giản hoá.

Để biết thêm thông tin về cách sử dụng luồng trong Android, hãy xem bài viết Quy trình và Chuỗi cuộc trò chuyện.

Chấm dứt giao tiếp với phụ kiện

Khi bạn kết nối xong với một phụ kiện hoặc nếu phụ kiện đó đã bị tháo, hãy đóng chỉ số mô tả tệp mà bạn đã mở bằng cách gọi close(). Để theo dõi các sự kiện đã tách rời, hãy tạo một broadcast receiver như dưới đây:

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

Việc tạo broadcast receiver trong ứng dụng chứ không phải trong tệp kê khai cho phép để chỉ xử lý các sự kiện được tách ra khi đang chạy. Bằng cách này, các sự kiện được tách chỉ gửi tới ứng dụng hiện đang chạy và không truyền phát tới tất cả ứng dụng.