Khi thiết bị chạy Android của bạn ở chế độ hỗ trợ USB, thiết bị sẽ hoạt động như một thiết bị lưu trữ USB, cấp nguồn cho bus, và liệt kê các thiết bị USB được kết nối. Chế độ hỗ trợ USB được hỗ trợ trong Android 3.1 trở lên.
Tổng quan về API
Trước khi bắt đầu, điều quan trọng là bạn phải hiểu rõ các lớp mà bạn cần xử lý. Chiến lược phát hành đĩa đơn
bảng sau đây mô tả các API máy chủ USB trong gói android.hardware.usb
.
Lớp | Mô tả |
---|---|
UsbManager |
Cho phép bạn liệt kê và giao tiếp với các thiết bị USB được kết nối. |
UsbDevice |
Đại diện cho một thiết bị USB được kết nối và chứa các phương thức để truy cập vào thông tin nhận dạng thiết bị thông tin, giao diện và điểm cuối. |
UsbInterface |
Biểu thị một giao diện của thiết bị USB, xác định một tập hợp chức năng cho thiết bị. Một thiết bị có thể có một hoặc nhiều giao diện để giao tiếp. |
UsbEndpoint |
Đại diện cho điểm cuối của giao diện, là kênh liên lạc của giao diện này. Một giao diện có thể có một hoặc nhiều điểm cuối và thường có các điểm cuối đầu vào và đầu ra để giao tiếp hai chiều với thiết bị. |
UsbDeviceConnection |
Biểu thị một kết nối đến thiết bị có chức năng truyền dữ liệu trên các thiết bị đầu cuối. Lớp này cho phép bạn gửi dữ liệu qua lại một cách đồng bộ hoặc không đồng bộ. |
UsbRequest |
Biểu thị một yêu cầu không đồng bộ để giao tiếp với một thiết bị thông qua UsbDeviceConnection . |
UsbConstants |
Xác định các hằng số USB tương ứng với các định nghĩa trong linux/usb/ch9.h của Linux nhân hệ điều hành. |
Trong hầu hết trường hợp, bạn cần sử dụng tất cả các lớp này (chỉ bắt buộc phải sử dụng UsbRequest
nếu bạn đang giao tiếp không đồng bộ)
khi giao tiếp với thiết bị USB. Nhìn chung, bạn sẽ nhận được UsbManager
để truy xuất UsbDevice
mong muốn.
Khi có thiết bị, bạn cần tìm UsbInterface
thích hợp và UsbEndpoint
của thiết bị đó
để giao tiếp. Sau khi bạn có được đúng điểm cuối, hãy mở một UsbDeviceConnection
để giao tiếp với thiết bị USB.
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 làm việc với API máy chủ USB:
- Vì không phải tất cả thiết bị chạy Android đều
đảm bảo hỗ trợ API máy chủ 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ăngandroid.hardware.usb.host
. - Đặt SDK tối thiểu của ứng dụng thành API cấp 12 trở lên. API máy chủ USB không có trên các cấp độ API trước đó.
- Nếu bạn muốn ứng dụng của mình được thông báo về thiết bị USB đi kèm, hãy chỉ định
Cặp phần tử
<intent-filter>
và<meta-data>
cho phần tử Ý địnhandroid.hardware.usb.action.USB_DEVICE_ATTACHED
trong hoạt động chính của bạn. Chiến lược phát hành đĩa đơn Phần tử<meta-data>
trỏ đến một tệp tài nguyên XML bên ngoài có khai báo thông tin nhận dạng về thiết bị 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-device>
cho USB thiết bị mà bạn muốn lọc. Danh sách sau đây mô tả các thuộc tính của<usb-device>
. Nói chung, hãy sử dụng mã sản phẩm và nhà cung cấp nếu bạn muốn lọc cho một thiết bị cụ thể và sử dụng lớp, lớp con và giao thức nếu muốn lọc theo một nhóm các thiết bị USB, chẳng hạn như thiết bị lưu trữ dung lượng lớn hoặc máy ảnh kỹ thuật số. Bạn có thể chỉ định không có gì hoặc tất cả các thuộc tính này. Việc chỉ định không có thuộc tính nào phù hợp với mọi thiết bị USB, vì vậy, bạn chỉ nên làm việc này nếu ứng dụng của bạn yêu cầu:vendor-id
product-id
class
subclass
protocol
(thiết bị hoặc giao diện)
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 nằm 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.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>
Trong trường hợp này, tệp tài nguyên sau cần được lưu trong
res/xml/device_filter.xml
và chỉ định rằng mọi thiết bị USB có
các thuộc tính cần được lọc:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" /> </resources>
Hoạt động với thiết bị
Khi người dùng kết nối thiết bị USB với một thiết bị chạy Android, hệ thống Android có thể xác định ứng dụng của bạn có quan tâm đến thiết bị được kết nối hay không. Nếu có, bạn có thể thiết lập giao tiếp với thiết bị nếu muốn. Để làm được việc này, ứng dụng của bạn phải:
- Khám phá các thiết bị USB được kết nối bằng cách sử dụng bộ lọc ý định để nhận thông báo khi người dùng kết nối một thiết bị USB hoặc bằng cách liệt kê các thiết bị USB đã được kết nối.
- Yêu cầu người dùng cấp quyền kết nối với thiết bị USB nếu chưa được nhận.
- Giao tiếp với thiết bị USB 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 thiết bị
Ứng dụng của bạn có thể phát hiện các thiết bị USB 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 thiết bị hoặc bằng cách liệt kê các thiết bị USB đã được 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 thiết bị mong muốn. Việc liệt kê các thiết bị USB được kết nối rất hữu ích nếu bạn muốn có danh sách tất cả trên các thiết bị đã 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 phát hiện một thiết bị USB cụ thể, bạn có thể chỉ định bộ lọc ý định để
bộ lọc cho ý định android.hardware.usb.action.USB_DEVICE_ATTACHED
. Cùng với
bộ lọc ý định này, bạn cần chỉ định một tệp tài nguyên chỉ định các thuộc tính của USB
thiết bị, chẳng hạn như sản phẩm và mã nhà cung cấp. Khi người dùng kết nối một thiết bị với thiết bị đó
bộ lọc, hệ thống sẽ hiển thị cho họ một hộp thoại hỏi xem họ có muốn khởi động ứng dụng của bạn hay không.
Nếu người dùng chấp nhận, ứng dụng của bạn sẽ tự động có quyền truy cập vào thiết bị cho đến khi
thiết bị đã bị ngắt kết nối.
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_DEVICE_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_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 Các thiết bị USB mà bạn quan tâm:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="1234" product-id="5678" /> </resources>
Trong hoạt động, bạn có thể lấy UsbDevice
biểu thị
thiết bị đính kèm theo ý định như sau:
Kotlin
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
Java
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Liệt kê thiết bị
Nếu ứng dụng của bạn muốn kiểm tra tất cả thiết bị USB hiện được kết nối
trong khi ứng dụng đang chạy, ứng dụng có thể liệt kê các thiết bị trên xe buýt. Sử dụng phương thức getDeviceList()
để tải bản đồ băm của tất cả
thiết bị USB được kết nối. Sơ đồ băm được khoá theo tên của thiết bị USB nếu bạn muốn
lấy một thiết bị từ bản đồ.
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");
Nếu muốn, bạn cũng có thể chỉ cần lấy một biến lặp từ bản đồ băm và xử lý từng thiết bị một theo một:
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 }
Xin phép giao tiếp với một thiết bị
Trước khi kết nối với thiết bị 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 thiết bị USB khi chúng được kết nối, bộ lọc sẽ tự động nhận 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 trong ứng dụng của bạn một cách rõ ràng trước khi kết nối với thiết bị.
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 thiết bị USB đã đượ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 thiết bị trước khi cố gắng giao tiếp với thiết bị đó. 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 thiết bị.
Để 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 quyền kết nối với thiết bị. 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 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); } } } } };
Để đăng ký broadcast receiver, hãy thêm đoạn 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), 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);
Để hiện hộp thoại yêu cầu người dùng cấp quyền kết nối với thiết bị, hãy gọi phương thức requestPermission()
:
Kotlin
lateinit var device: UsbDevice ... usbManager.requestPermission(device, permissionIntent)
Java
UsbDevice device; ... usbManager.requestPermission(device, 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
thiết bị.
Giao tiếp với thiết bị
Hoạt động giao tiếp với thiết bị USB có thể đồng bộ hoặc không đồng bộ. Trong cả hai trường hợp, bạn
nên tạo một luồng mới để thực hiện tất cả quá trình truyền dữ liệu, như vậy bạn sẽ không chặn
Luồng giao diện người dùng. Để thiết lập giao tiếp đúng cách với một thiết bị, bạn cần có
UsbInterface
và UsbEndpoint
của
thiết bị mà bạn muốn giao tiếp và gửi yêu cầu trên điểm cuối này bằng UsbDeviceConnection
. Nói chung, mã của bạn phải:
- Kiểm tra các thuộc tính của đối tượng
UsbDevice
, chẳng hạn như mã sản phẩm, mã nhà cung cấp hoặc lớp thiết bị để tìm hiểu xem bạn có muốn giao tiếp với thiết bị. - Khi bạn chắc chắn rằng mình muốn giao tiếp với thiết bị, hãy tìm
UsbInterface
mà bạn muốn dùng để giao tiếp cùng vớiUsbEndpoint
thích hợp của giao diện đó. Giao diện có thể có một và thường sẽ có một điểm cuối đầu vào và đầu ra cho hai chiều giao tiếp. - Khi bạn tìm thấy điểm cuối chính xác, hãy mở một
UsbDeviceConnection
trên điểm cuối đó. - Cung cấp dữ liệu bạn muốn truyền trên điểm cuối bằng phương thức
bulkTransfer()
hoặccontrolTransfer()
. Bạn nên thực hiện bước này trong một luồng khác để ngăn chặn việc chặn luồng giao diện người dùng chính. Để biết thêm thông tin về cách sử dụng luồng trong Android, hãy xem phần Quy trình và Chuỗi cuộc trò chuyện.
Đoạn mã sau đây là một cách đơn giản để thực hiện quá trình chuyển dữ liệu đồng bộ. Mã của bạn cần có nhiều logic hơn để tìm chính xác giao diện và điểm cuối chính xác để giao tiếp đồng thời nên chuyển dữ liệu trong một luồng khác với luồng giao diện người dùng chính:
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
Để gửi dữ liệu không đồng bộ, hãy sử dụng lớp UsbRequest
cho initialize
và queue
một yêu cầu không đồng bộ, sau đó chờ kết quả
cùng với requestWait()
.
Chấm dứt hoạt động giao tiếp với thiết bị
Khi bạn kết nối xong với một thiết bị hoặc nếu thiết bị đã bị ngắt kết nối, hãy đóng UsbInterface
và UsbDeviceConnection
bằng
đang gọi releaseInterface()
và
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_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 } } } };
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.