安卓USB 开发相关内容整理
基本概念和术语
USB是一个用于数据交换的总线(Bus),发起(initiate)数据交换的一方称为主机(host),另一方称为外设(peripheral),两者通过总线实现通信,由主机负责供电描述符(descriptor)里.一个外设就是一个单独的物理实体,但它却可能有多个逻辑上的设备功能(device function),比如一个网络摄像头,除了有照相机,还可能有内置的麦克风,这种外接设备也被称为复合设备(composite device)
主机通过管道(pipe)连接到外设的端点(endpoint),端点是指外设某一项功能的逻辑实体(logic entity),一条管道对应一个端点.一个外设最多可以有32个端点(16个进,16个出),主机会对端点初始化,给其分配相应的序号和功能,一经分配,不论管道是否关闭都不会改变.除了用于进行设备配置的0号端点,其他端点都被划分到不同的接口(interface)**,每一个接口对应一项外设功能.
管道有两种类型:
消息(message):用来进行双向(bi-directional)控制与状态获取,对传输的数据有结构上的要求.
流(stream):用来进行单向(uni-directional)数据传输,不会对传输的数据进行结构上的改动.
因为通信是由主机发启的,所以input和output都是相对主机而言的,输入就是外设传数据给主机,输出就是主机传数据给外设.
主机有一个关键任务称为枚举(enumeration),通过枚举主机获取所有外设的描述符(descriptor),给每一个外设分配一个地址(address)**并完成其他配置,比如前文所说对端点的初始化.经过枚举之后,外设就可以使用了,如果主机重启,就会对所有连接的外设重置并再次进行枚举.
USB的数据传输模式主要有四种:
control:提供无损传输,用来传输配置/状态/命令等通信,所有的USB设备都必须支持这种传输模式.
interrupt有限延迟下保证设备的快速反应,主要用于键盘等输入设备.
bulk:用于大量数据的无损传输,但不保证带宽和延迟,比如用U盘拷贝文件.
isochronous:保证尽可能快的传输速度,但可能会有数据丢失,比如实时的音视频传输.
常用类
Android 3.1(API级别12)以上原生提供了 USB 开发的 API,在android.hardware.usb包下提供了开发的相关类。
UsbManager 获得 USB 管理器,与连接的 USB 设备通信。
# 获取 UsbManager
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
- UsbDevice USB 设备的抽象,每个UsbDevice 都代表一个 USB 设备。
该类的方法主要有
getDeviceClass() 返回此USB设备的类别,用一个整型来表示
getDeviceId() 返回唯一标识此设备的ID号,也用一个整型来表示
getDeviceName() 返回此设备的名称,用一个字符串来表示
getDeviceProtocol() 返回此设备的协议类别,用一个整型来表示
getDeviceSubclass () 返回此设备的子类别,用一个整型来表示
getVendorId() 返回生产商ID
getProductId() 返回产品ID
getInterfaceCount() 返回此设备的接口数量
getInterface(int index) 得到此设备的一个接口,返回一个
# 获取 USB 设备列表
public List<UsbDevice> getDeviceList() {
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
List<UsbDevice> usbDevices = new ArrayList<>();
while (deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();
usbDevices.add(device);
}
return usbDevices;
}
#获取特定的设备
/**
* mVendorId=1137,mProductId=85 佳博 3150T 标签打印机
*
* @param vendorId 厂商ID
* @param productId 产品ID
* @return device
*/
public UsbDevice getUsbDevice(int vendorId, int productId) {
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while (deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();
if (device.getVendorId() == vendorId && device.getProductId() == productId) {
Log.e("USBUtil", "getDeviceList: " + device.getDeviceName());
return device;
}
}
Toast.makeText(context, "没有对应的设备", Toast.LENGTH_SHORT).show();
return null;
}
UsbInterface 表示一个为该设备定义了一个函数集的USB设备接口。 一个 UsbDevice 可能包含一个或多个UsbInterface,每个 Interface 都是独立的,一个接口,有两个端点,分别接受和发送数据
本身是一个类,并不是一个接口。此类的主要方法有:
getId() 得到给接口的ID 号
getInterfaceClass() 得到该接口的类别
getInterfaceSubclass () 得到该接口的子类
getInterfaceProtocol() 得到该接口的协议类别
getEndpointCount() 得到关于此接口的节点数量
getEndpoint(int index) 对于指定的index获得此接口的一个节点,返回一个
#一个usbDevice有多个UsbInterface,我们需要的一般是第一个
usbInterface=usbDevice.getInterface(0);
UsbEndpoint UsbEndpoint 是 interface 的通信通道。 代表一个接口的某个节点的类,该类主要提供了以下方法:
getAddress() 获得此节点的地址
getAttributes ( ) 获得此节点的属性
getDirection() 获得此节点的数据传输方向
UsbDeviceConnection 代表USB 连接的一个类。用此连接可以向USB 设备发送和接收数据,可以通过调用该方法openDevice(UsbDevice usbDevice)来得到该类的一个实例。该类提供了以下方法使用:
bulkTransfer(UsbEndpoint endpoint , byte[] buffer , int length , int timeout)
通过给定的endpoint 来进行大量的数据传输,传输的方向取决于该节点的方向,buffer是要发送或接收的字节数组,length是该字节数组的长度。失败则返回负数
controlTransfer( int requestType, int request , int value , int index , byte[] buffer , int length , int timeout)
该方法通过0节点向此设备传输数据,传输的方向取决于请求的类别,如果requestType 为 USB_DIR_OUT 则为写数据 , USB _DIR_IN ,则为读数据
UsbRequest USB 请求包。
UsbConstants USB 常量的定义
两种开发模式
Android设备最早都是以PC的USB外设形式而存在,主要功能就是Android fastboot 或Android Debug Bridge (adb),都是基于bulk传输模式的.
USB Host Mode
顾名思义,Android设备作为主机,需要安卓设备支持OTG接头.常见应用场景诸如连接数码相机,键盘,鼠标,游戏手柄等硬件.需要注意的是,通常安卓设备的电量作为主机是捉襟见肘的.
USB Accessory Mode
这种模式下Android设备承担外设的角色.应用场景诸如连接机器人控制器,音响,医疗器材等,当然前提是这些设备支持与Android设备连接并且遵守Android accessory communication protocol.这种模式可以让不具有host能力的Android设备与其他硬件交互.
注意:在谷歌的话语体系里,Host模式里与Android设备相连的硬件被称为USB device,Accessory模式里,与Android设备相连的硬件称为USB accessory.尽管是accessory是"附件"的意思,其承担的却是逻辑上主机(host)的角色.
Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB附件和USB主机。USB开发需 Android 3.1(API级别12)以上。
配置AndroidManifest.xml文件
配置好清单文件后当用户连接与您的设备过滤器匹配的设备时,系统会向他们显示一个对话框,询问他们是否要启动您的应用程序。如果用户接受,则应用程序将自动具有访问设备的权限,直到设备断开连接。如果给了默认,那么这个 USB 设备插入后会自动启动这个 Activity
在android系统中,默认是关闭usb模式的,当我们将usb外部设备插入android设备时,android系统不回出任何响应。如果我们要打开系统usb模式,需要在android清单文件里使用标签声明响应的usb模式
#Host模式
<uses-feature android:name="android.hardware.usb.host" />
#accessory模式
<uses-permission android:name="android.hardware.usb.accessory" />
配置usb设备插入的通知和相关usb设备的过滤
若我们希望在插入usb设备时,android系统能接收到提示性通知,则需要在android组件中配置和标签配置相关信息,中配置我们要接收的相关类型的信息通知,配置我们需要检测的指定的设备,标签内容对应一个xml资源文件,里面配置我们指定的usb设备。如下:
<activity >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!--host模式下通知的过滤-->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<!--host模式下指定过滤设备的配置文件-->
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
<!--accessory模式下通知的过滤-->
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<!--accessory模式下指定过滤设备的配置文件-->
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
配置 xml资源文件(用于过滤设备) :
在XML资源文件中,声明要过滤的USB设备的元素。以下列表描述了属性 。通常,如果要过滤特定设备并使用类,子类和协议(如果要过滤一组USB设备(如大容量存储设备或数码相机)),请使用供应商(vendor-id)和产品(product-id)ID,在开发中这些过滤ID一般可以在文档中找到,或者自己连上看也行。你可以指定部分或全部这些属性。
将资源文件保存在res/xml/目录中。资源文件名(不带.xml扩展名)必须与您在元素中指定的文件名相同 。对于XML资源文件格式的 例子如下:
<?xml version="1.0" encoding="utf-8"?>
<rescources xmlns:android="http://schemas.android.com/apk/res/android">
<!--host模式-->
<usb-device
class=""
vendor-id=""
product-id=""
protoclo=""
subclass="" />
<!--accessory模式-->
<usb-accessory
model="xxx"
manufacturer="xxx" />
建立通信
无论是host模式还是accessory模式,在进行检测获取usb设备时,都需要用到UsbManager类,只是调用不同的方法而已。如下:
host模式
```
#UsbManager
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
#获取设备列表
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
#遍历设备,找到指定的productId和vendorId 设备
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while (deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();
if (device.getVendorId() == vendorId && device.getProductId() == productId) {
#获取当前UsbDevice
mUsbDevice = device;
# 检查mUsbDevice 否有对应权限,若无则请求权限,通过广播接收权限信息获取情况。
if (!usbManager.hasPermission(mUsbDevice)) {//检测是否有usb权限
requestPermission(mUsbDevice);
} else {
#与usb设备进行通信
connect(mUsbDevice);
}
}
/**
* 请求usb权限
* @param usbManager
* @param parcelable
*/
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
public void requestPermission(UsbDevice usbDevice,UsbManager usbManager){
IntentFilter intentFilter = new IntentFilter(ACTION_USB_PERMISSION );
registerReceiver(mMyBroadcastReceiver, intentFilter);
PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION ), 0);
usbManager.requestPermission(usbDevice, broadcast);
}
/**
* 权限广播接收器
*/
private BroadcastReceiver mMyBroadcastReceiver=new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION .equals(action)) {
synchronized (this) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (usbDevice != null) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
connect(usbDevice);
Toast.makeText(context, “success get permission”, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, “failure get permission”, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(context, “the usbDevice be null”, Toast.LENGTH_SHORT).show();
}
}
}
}
};
public boolean connect(UsbDevice mUsbDevice) {
if (mUsbDevice != null) {
UsbInterface anInterface = mUsbDevice.getInterface(0);
mUsbDeviceConnection = mUsbManager.openDevice(mUsbDevice);//打开设备,获取 UsbDeviceConnection 对象,连接设备,用于后面的通讯
if (mUsbDeviceConnection == null) {
Toast.makeText(mContext, “mUsbDeviceConnection can’t be null”, Toast.LENGTH_SHORT).show();
return false;
}
if (mUsbDeviceConnection.claimInterface(anInterface, true)) {
Toast.makeText(mContext, “找到USB接口”, Toast.LENGTH_SHORT).show();
int endpointCount = anInterface.getEndpointCount();
for (int i = 0; i < endpointCount; i++) {
UsbEndpoint endpoint = anInterface.getEndpoint(i);
if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (UsbConstants.USB_DIR_IN == endpoint.getDirection()) {
mUsbEndpoint_in = endpoint;//获取读数据通道
} else {
mUsbEndpoint_out = endpoint;//获取写数据通道
}
}
}
return true;
} else {
mUsbDeviceConnection.close();//关闭连接
Toast.makeText(mContext, “找不到USB接口”, Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(mContext, “mUsbDevice can’t be null”, Toast.LENGTH_SHORT).show();
}
return false;
}
/**
* 从usb通信设备中读取数据
* @return
*/
public byte[] readData() {
int inMax = mUsbEndpoint_in.getMaxPacketSize();
byte[] bytes = new byte[inMax];
ByteBuffer byteBuffer = ByteBuffer.allocate(inMax);
UsbRequest usbRequest = new UsbRequest();
usbRequest.initialize(mUsbDeviceConnection, mUsbEndpoint_in);
usbRequest.queue(byteBuffer, inMax);
if (mUsbDeviceConnection.requestWait() == usbRequest) {
bytes = byteBuffer.array();
}
return bytes;
}
/**
* 将数据写入到usb设备中
* @param bytes
*/
public void sendData(byte[] bytes) {
if (mUsbDeviceConnection == null) {
Toast.makeText(mContext, "mUsbDeviceConnection can't be null", Toast.LENGTH_SHORT).show();
return;
}
if (mUsbEndpoint_out == null) {
Toast.makeText(mContext, "mUsbEndpoint_out can't be null", Toast.LENGTH_SHORT).show();
return;
}
int i = mUsbDeviceConnection.bulkTransfer(mUsbEndpoint_out, bytes, bytes.length, 1000);
if (i < 0) {
Toast.makeText(mContext, "failure to write", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "success to write", Toast.LENGTH_SHORT).show();
}
}
/**
* 筛选出我们想要的usb设备
* @param name
* @param usbDevice
* @return
*/
public boolean filterDevice(String name, UsbDevice usbDevice) {
// TODO: 2019/3/13 对应判断设备是否是我们要连接的设备
return false;
}
accessory模式(和host模式差不多,大致步骤一样,只是相关类不同而已如下)
1)获取UsbManager
2)通过usbManager获取连接到的usb外部设备UsbAccessory
3)获取usb设备引用,通过usbManager检测是否有对应权限,若无则请求权限,通过广播接收权限信息获取情况。
4)与UsbAccessory设备进行通信
public static final String action_usb_permission = "org.zhuhailong.myviewproject.permission";
private UsbManager usbManager;
public void hostAccessory(Context context) {
//获取UsbManager
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
//获取usb设备列表
UsbAccessory[] accessoryList = usbManager.getAccessoryList();
if (accessoryList == null || accessoryList.length <= = 0) {
Toast.makeText(context, "未查询到对应设备", Toast.LENGTH_SHORT).show();
return;
}
UsbAccessory mUsbAccessory = null;
for (UsbAccessory usbAccessory : accessoryList) {
if (filterDevice(usbAccessory)) {//判断是否使我们要连接的usb设备
break;
}
}
if (usbManager.hasPermission(mUsbAccessory)) {//检测是否有usb权限
requestPermission(mUsbAccessory);//请求usb权限
} else {
connect(mUsbAccessory);//连接usb设备
}
}
/**
* 请求usb权限
*
* @param usbManager
* @param parcelable
*/
public void requestPermission(Parcelable parcelable, UsbManager usbManager) {
UsbAccessory usbAccessory = (UsbAccessory) parcelable;
if (!usbManager.hasPermission(usbAccessory)) {
IntentFilter intentFilter = new IntentFilter(action_usb_permission);
registerReceiver(mMyBroadcastReceiver, intentFilter);
PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, new Intent(action_usb_permission), 0);
usbManager.requestPermission(usbAccessory, broadcast);
} else {
Toast.makeText(context, "already have permission", Toast.LENGTH_SHORT).show();
}
}
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
/**
* 连接设备
* @param usbAccessory
* @return
*/
public boolean connect(UsbAccessory usbAccessory) {
ParcelFileDescriptor parcelFileDescriptor = usbManager.openAccessory(usbAccessory);
if (parcelFileDescriptor != null) {
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
mFileInputStream = new FileInputStream(fileDescriptor);
mFileOutputStream = new FileOutputStream(fileDescriptor);
}
return false;
}
/**
* 写入数据
* @param data
*/
public void write(byte[] data) {
try {
mFileOutputStream.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取数据
*/
public void read() {
int max;//字节数据根据协议来确定
byte[] data = new byte[max];
mFileInputStream.read(data);
}
/**
* 权限广播接收器
*/
private BroadcastReceiver mMyBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action_usb_permission.equals(action)) {
synchronized (this) {
UsbAccessory usbAccessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (usbAccessory != null) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
connect(usbAccessory);
Toast.makeText(context, "success get permission", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "failure get permission", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(context, "the usbDevice be null", Toast.LENGTH_SHORT).show();
}
}
}
}
};
/**
* 筛选出我们想要的usb设备
*
* @param usbAccessory
* @return
*/
public boolean filterDevice(UsbAccessory usbAccessory) {
// TODO: 2019/3/13 对应判断设备是否是我们要连接的设备
return false;
}
二、USB 设备的连接和使用
2.USB 设备的插入
Android 系统中,USB 设备的插入和拔出是以系统广播的形式发送的,我们只要注册监听这个广播就好
public class USBReceiver extends BroadcastReceiver {
public static final String ACTION_USB_PERMISSION = “com.android.example.USB_PERMISSION”;
@Override
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 (device != null) {
//call method to set up device communication
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
Log.e("USBReceiver", "获取权限成功:" + device.getDeviceName());
} else {
Log.e("USBReceiver", "获取权限失败:" + device.getDeviceName());
}
}
}
}else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
// 有新的设备插入了,在这里一般会判断这个设备是不是我们想要的,是的话就去请求权限
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
// 有设备拔出了
}
}
}
/**
* 请求获取指定 USB 设备的权限
*/
public void requestPermission(UsbDevice device) {
if (device != null) {
if (usbManager.hasPermission(device)) {
Toast.makeText(context, "已经获取到权限", Toast.LENGTH_SHORT).show();
} else {
if (mPermissionIntent != null) {
usbManager.requestPermission(device, mPermissionIntent);
Toast.makeText(context, "请求USB权限", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "请注册USB广播", Toast.LENGTH_LONG).show();
}
}
}
}
注册广播:
public void registerReceiver(Activity context) {
mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
context.registerReceiver(usbReceiver, filter);
}
7.通信
与 USB 设备的通信可以是同步的也可以是异步的。无论哪种情况,你都应该创建一个新线程来执行所有数据传输,避免阻塞UI线程。
第一步,打开通信端口
public boolean openPort(UsbDevice device) {
//获取设备接口,一般只有一个,多个的自己研究去
usbInterface = device.getInterface(0);
// 判断是否有权限
if (hasPermission(device)) {
// 打开设备,获取 UsbDeviceConnection 对象,连接设备,用于后面的通讯
usbConnection = usbManager.openDevice(device);
if (usbConnection == null) {
return false;
}
if (usbConnection.claimInterface(usbInterface, true)) {
Toast.makeText(Utils.getContext(), "找到 USB 设备接口", Toast.LENGTH_SHORT).show();
} else {
usbConnection.close();
Toast.makeText(Utils.getContext(), "没有找到 USB 设备接口", Toast.LENGTH_SHORT).show();
return false;
}
} else {
Toast.makeText(Utils.getContext(), "没有 USB 权限", Toast.LENGTH_SHORT).show();
return false;
}
//获取接口上的两个端点,分别对应 OUT 和 IN
for (int i = 0; i < usbInterface.getEndpointCount(); ++i) {
UsbEndpoint end = usbInterface.getEndpoint(i);
if (end.getDirection() == UsbConstants.USB_DIR_IN) {
usbEndpointIn = end;
} else {
usbEndpointOut = end;
}
}
return true;
}
第二步,发送数据
usbConnection.bulkTransfer(usbEndpointOut, bytes, bytes.length, 500);
相关 开源库
链接: [usb-serial-for-android](https://github.com/mik3y/usb-serial-for-android).