Когда ваше устройство под управлением Android находится в режиме USB-хоста, оно действует как USB-хост, питает шину и перечисляет подключенные USB-устройства. Режим USB-хоста поддерживается в Android 3.1 и более поздних версиях.
Обзор API
Прежде чем начать, важно понять, с какими классами вам нужно работать. В следующей таблице описаны API-интерфейсы USB-хоста в пакете android.hardware.usb
.
Таблица 1. API-интерфейсы USB-хоста
Сорт | Описание |
---|---|
UsbManager | Позволяет перечислять и обмениваться данными с подключенными USB-устройствами. |
UsbDevice | Представляет подключенное USB-устройство и содержит методы для доступа к его идентификационной информации, интерфейсам и конечным точкам. |
UsbInterface | Представляет интерфейс USB-устройства, определяющий набор функций устройства. Устройство может иметь один или несколько интерфейсов для связи. |
UsbEndpoint | Представляет конечную точку интерфейса, которая является каналом связи для этого интерфейса. Интерфейс может иметь одну или несколько конечных точек и обычно имеет конечные точки ввода и вывода для двусторонней связи с устройством. |
UsbDeviceConnection | Представляет подключение к устройству, которое передает данные на конечные точки. Этот класс позволяет отправлять данные туда и обратно синхронно или асинхронно. |
UsbRequest | Представляет асинхронный запрос на связь с устройством через UsbDeviceConnection . |
UsbConstants | Определяет константы USB, которые соответствуют определениям в linux/usb/ch9.h ядра Linux. |
В большинстве ситуаций вам необходимо использовать все эти классы ( UsbRequest
требуется только в том случае, если вы используете асинхронную связь) при обмене данными с USB-устройством. Обычно вы получаете UsbManager
для получения нужного UsbDevice
. Если у вас есть устройство, вам нужно найти соответствующий UsbInterface
и UsbEndpoint
этого интерфейса для связи. Получив правильную конечную точку, откройте UsbDeviceConnection
для связи с USB-устройством.
Требования к манифесту Android
В следующем списке описано, что необходимо добавить в файл манифеста вашего приложения перед работой с API-интерфейсами USB-хоста:
- Поскольку не все устройства под управлением Android гарантированно поддерживают API-интерфейсы USB-хоста, включите элемент
<uses-feature>
, который объявляет, что ваше приложение использует функциюandroid.hardware.usb.host
. - Установите минимальный SDK приложения на уровень API 12 или выше. API-интерфейсы хоста USB отсутствуют на более ранних уровнях API.
- Если вы хотите, чтобы ваше приложение уведомлялось о подключенном USB-устройстве, укажите пару элементов
<intent-filter>
и<meta-data>
для намеренияandroid.hardware.usb.action.USB_DEVICE_ATTACHED
в своем основном действии. Элемент<meta-data>
указывает на внешний XML-файл ресурсов, в котором объявляется идентификационная информация об устройстве, которое вы хотите обнаружить.В файле ресурсов XML объявите элементы
<usb-device>
для USB-устройств, которые вы хотите фильтровать. В следующем списке описаны атрибуты<usb-device>
. Как правило, используйте идентификатор поставщика и продукта, если вы хотите фильтровать конкретное устройство, и используйте класс, подкласс и протокол, если вы хотите фильтровать группу USB-устройств, например устройства хранения данных или цифровые камеры. Вы можете указать ни один из этих атрибутов или все из них. Отсутствие атрибутов соответствует каждому 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
и указывает, что любое 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-устройства к устройству под управлением Android, система Android может определить, заинтересовано ли ваше приложение в подключенном устройстве. Если да, то при желании вы можете настроить связь с устройством. Для этого ваше приложение должно:
- Обнаруживайте подключенные USB-устройства с помощью фильтра намерений, который будет уведомлять вас, когда пользователь подключает USB-устройство, или путем перечисления уже подключенных USB-устройств.
- Попросите у пользователя разрешение на подключение к USB-устройству, если оно еще не получено.
- Общайтесь с USB-устройством, считывая и записывая данные на соответствующих конечных точках интерфейса.
Откройте для себя устройство
Ваше приложение может обнаруживать USB-устройства либо с помощью фильтра намерений, чтобы получать уведомления, когда пользователь подключает устройство, либо путем перечисления уже подключенных USB-устройств. Использование фильтра намерений полезно, если вы хотите, чтобы ваше приложение автоматически обнаруживало нужное устройство. Перечисление подключенных USB-устройств полезно, если вы хотите получить список всех подключенных устройств или если ваше приложение не фильтровало намерение.
Используйте фильтр намерений
Чтобы ваше приложение обнаружило определенное USB-устройство, вы можете указать фильтр намерений для фильтрации намерения android.hardware.usb.action.USB_DEVICE_ATTACHED
. Наряду с этим фильтром намерений вам необходимо указать файл ресурсов, который определяет свойства USB-устройства, такие как идентификатор продукта и поставщика. Когда пользователи подключают устройство, соответствующее вашему фильтру устройств, система отображает им диалоговое окно с вопросом, хотят ли они запустить ваше приложение. Если пользователи соглашаются, ваше приложение автоматически получает разрешение на доступ к устройству до тех пор, пока устройство не будет отключено.
В следующем примере показано, как объявить фильтр намерений:
<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
, представляющий подключенное устройство, следующим образом:
Котлин
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
Ява
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Перечислить устройства
Если ваше приложение заинтересовано в проверке всех USB-устройств, подключенных в данный момент во время работы вашего приложения, оно может перечислить устройства на шине. Используйте метод getDeviceList()
, чтобы получить хеш-карту всех подключенных USB-устройств. Хэш-карта включается по имени USB-устройства, если вы хотите получить устройство из карты.
Котлин
val manager = getSystemService(Context.USB_SERVICE) as UsbManager ... val deviceList = manager.getDeviceList() val device = deviceList.get("deviceName")
Ява
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); ... HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); UsbDevice device = deviceList.get("deviceName");
При желании вы также можете просто получить итератор из хеш-карты и обработать каждое устройство по одному:
Котлин
val manager = getSystemService(Context.USB_SERVICE) as UsbManager .. val deviceList: HashMap<String, UsbDevice> = manager.deviceList deviceList.values.forEach { device -> // your code }
Ява
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()
отображает пользователю диалоговое окно с запросом разрешения на подключение к устройству. В следующем примере кода показано, как создать широковещательный приемник:
Котлин
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") } } } } }
Ява
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()
в своей деятельности:
Котлин
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)
Ява
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()
:
Котлин
lateinit var device: UsbDevice ... usbManager.requestPermission(device, permissionIntent)
Ява
UsbDevice device; ... usbManager.requestPermission(device, permissionIntent);
Когда пользователи отвечают на диалог, ваш широковещательный приемник получает намерение, содержащее дополнительное значение EXTRA_PERMISSION_GRANTED
, которое представляет собой логическое значение, представляющее ответ. Проверьте это дополнение на значение true перед подключением к устройству.
Общайтесь с устройством
Связь с USB-устройством может быть синхронной или асинхронной. В любом случае вам следует создать новый поток, в котором будут осуществляться все передачи данных, чтобы не блокировать поток пользовательского интерфейса. Чтобы правильно настроить связь с устройством, вам необходимо получить соответствующие UsbInterface
и UsbEndpoint
устройства, с которым вы хотите обмениваться данными, и отправлять запросы на эту конечную точку с помощью UsbDeviceConnection
. В общем, ваш код должен:
- Проверьте атрибуты объекта
UsbDevice
, такие как идентификатор продукта, идентификатор поставщика или класс устройства, чтобы выяснить, хотите ли вы взаимодействовать с устройством. - Если вы уверены, что хотите установить связь с устройством, найдите соответствующий
UsbInterface
, который вы хотите использовать для связи, вместе с соответствующейUsbEndpoint
этого интерфейса. Интерфейсы могут иметь одну или несколько конечных точек и обычно имеют конечные точки ввода и вывода для двусторонней связи. - Когда вы найдете правильную конечную точку, откройте
UsbDeviceConnection
в этой конечной точке. - Предоставьте данные, которые вы хотите передать в конечную точку, с помощью метода
bulkTransfer()
илиcontrolTransfer()
. Вам следует выполнить этот шаг в другом потоке, чтобы предотвратить блокировку основного потока пользовательского интерфейса. Дополнительные сведения об использовании потоков в Android см. в разделе Процессы и потоки .
Следующий фрагмент кода представляет собой тривиальный способ синхронной передачи данных. Ваш код должен иметь больше логики, чтобы правильно найти правильный интерфейс и конечные точки для связи, а также должен выполнять любую передачу данных в потоке, отличном от основного потока пользовательского интерфейса:
Котлин
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 } } }
Ява
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
для initialize
и постановки асинхронного запроса queue
, а затем дождитесь результата с помощью requestWait()
.
Прекращение связи с устройством
Когда вы закончите связь с устройством или если устройство было отключено, закройте UsbInterface
и UsbDeviceConnection
, вызвав releaseInterface()
и close()
. Чтобы прослушивать отдельные события, создайте широковещательный приемник, как показано ниже:
Котлин
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 } } } }
Ява
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 } } } };
Создание приемника широковещательной рассылки внутри приложения, а не манифеста, позволяет вашему приложению обрабатывать только отдельные события во время его работы. Таким образом, отдельные события отправляются только в приложение, которое работает в данный момент, а не транслируются всем приложениям.