摘要:最近一直使用到身份证刷卡器,M1刷卡器。都有相关的demo。很快的使用上了,不过在使用M1的时候,没有循环(类似公交卡可以一直刷卡)读卡的demo,自己随便的开一个线程去无限读。事与愿违,不没有成功,不能抽时间查相关的UsbManager相关的资料。在这里把其简单的记录一下,^_^, 毕竟口号就抄,抄,抄. .(侵删)
USB背景知识
USB是一种数据通信方式,也是一种数据总线,而且是最复杂的总线之一
硬件上,它是用插头连接。一边是公头(plug),一边是母头(receptacle)。例如:PC上的插头就是母头,USB设备使用公头与PC相连
Host
USB是由 Host端控制整个总线的数据传输。单个USB上,只能有一个Host
OTG
On The Go,这是在 USB2.0引入的一种mode,提出了一个新的概念叫主机协商协议(Host Negotiation Protocol),允许两个设备间商量谁去当Host
Android中的USB
Android中对USB支持是3.1开始,显然是加强Android平板的对外扩展能力。而对 USB使用更多的是Android在工业中的使用。Android功能板子一般都会提供多个USB和串口,它们是连接外设手段的桥梁。比如:现在市面上的一体机,Android的板子上面会有很多的USB和串口,但是其中Android板子除了一个系统什么都没有,没有任何的传感器,摄像头等等。。全部是外接,就等同于一台式电脑。
我们在使用这些外接设备的时候,我们需要来了解一下Android为我们提供的API。其具体在android.hardware.usb包中。我们需要了解一下UsbManager
、 UsbDevice
、 UsbDeviceConnection
, UsbEndpoint
, UsbInterface
UsbRequest
, UsbConstants
, 这几个类,只要使用到USB都要用到它们。
getDeviceList() | 获得设备列表,返回的是一个HashMap |
hasPermission(UsbDevice device) | 判断你的应用程序是否有接入此USB设备的权限,如果有则返回真,否则返回false |
openDevice(UsbDevice device) | 打开USB设备,以便向此USB设备发送和接受数据,返回一个关于此USB设备的连接 |
requestPermission(UsbDevice device, PendingIntent pi) | 向USB设备请求临时的接入权限 |
getDeviceClass() | 返回此USB设备的类别,用一个整型来表示 |
getDeviceId() | 返回唯一标识此设备的ID号,也用一个整型来表示 |
getDeviceName() | 返回此设备的名称,用一个字符串来表示 |
getDeviceProtocol() | 返回此设备的协议类别,用一个整型来表示 |
getDeviceSubclass() | 返回此设备的子类别,用一个整型来表示 |
getVendorId() | 返回生产商ID |
getProductId() | 返回产品ID |
getInterfaceCount() | 返回此设备的接口数量 |
getInterface(int index) | 得到此设备的一个接口,返回一个UsbInterface |
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, 则为读数据 |
getAddress() | 获得此节点的地址 |
getAttributes() | 获得此节点的属性 |
getDirection() | 获得此节点的数据传输方向 |
getId() | 得到给接口的id号。 |
getInterfaceClass() | 得到该接口的类别 |
getInterfaceSubclass() | 得到该接口的子类 |
getInterfaceProtocol() | 得到该接口的协议类别。 |
getEndpointCount() | 获得关于此接口的节点数量 |
getEndpoint(int index) | 对于指定的index获得此接口的一个节点,返回一个UsbEndpoint |
USB的广播
可以通过广播接接收器接收USB的插拔信息:当插入USB插头到一个USB端口或从一个USB端口移除一USB插头。都可以获取到
PendingIntent permissionIntent1 = PendingIntent.getBroadcast(this, 0,new Intent(ACTION_USB_PERMISSION), 0);
// Broadcast listen for new devices
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
this.registerReceiver(mUsbReceiver, filter);
/*
* BroadcastReceiver when insert/remove the device USB plug into/from a USB port
* 创建一个广播接收器接收USB插拔信息:当插入USB插头插到一个USB端口,或从一个USB端口,移除装置的USB插头
*/
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
//插入事件
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
//拔出事件
} else 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.getProductId() == 8211 && device.getVendorId() == 1305)
|| (device.getProductId() == 8213 && device
.getVendorId() == 1305))
{
if(mUsbDriver.openUsbDevice(device))
{
if(device.getProductId()==8211)
mUsbDev1 = device;
else
mUsbDev2 = device;
}
}
}
else {
Toast.makeText(MainActivity.this,"permission denied for device",
Toast.LENGTH_SHORT).show();
//Log.d(TAG, "permission denied for device " + device);
}
}
}
}
};
USB的启动程序和 pid的注册
时常在有多个视屏播放器,浏览器没有设置为默认的情况下都会弹出一个选择框。usb也一样,需要我们在AndroidManifest.xml进行注册。可以注册在activity标签里面,也可以注册在 application标签里面。不管在那里面,都需要在对应的activity标签里面注册
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
和添加权限
<uses-feature
android:name="android.hardware.usb.host"
android:required="true"/>
<uses-permission android:name="android.permission.MANAGE_USB"/>
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
进行筛选我们自己的 USB的厂家id和产品id。可以在res下新建一个xml。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 以下内容的 vendor-id、product-id就是USB的vid和pid了-->
<usb-device vendor-id="4292" product-id="33896"/>
<usb-device vendor-id="1024" product-id="50010"/>
<usb-device vendor-id="1155" product-id="19799"/>
<usb-device vendor-id="4292" product-id="33485"/>
</resources>
再在AndroidManifest.xml的activity或者application标签注册
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter"/>
在这样之后,在USB插入的时候,就会自行启动我们的activity。
USB的通信
把USB连接成功,就需要进行与它通信了。
bulkTransfer(this.h[this.g], var5, var4, 3000)
在做好准备之后,就这一个话。哈哈哈哈哈,啊嗝
这里摘抄两个前辈写的
前辈一:
private void initCommunication(UsbDevice device) {
tvInfo.append("initCommunication in\n");
if(1234 == device.getVendorId() && 5678 == device.getProductId()) {
tvInfo.append("initCommunication in right device\n");
int interfaceCount = device.getInterfaceCount();
for (int interfaceIndex = 0; interfaceIndex < interfaceCount; interfaceIndex++) {
UsbInterface usbInterface = device.getInterface(interfaceIndex);
if ((UsbConstants.USB_CLASS_CDC_DATA != usbInterface.getInterfaceClass())
&& (UsbConstants.USB_CLASS_COMM != usbInterface.getInterfaceClass())) {
continue;
}
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
UsbEndpoint ep = usbInterface.getEndpoint(i);
if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
mUsbEndpointIn = ep;
} else {
mUsbEndpointOut = ep;
}
}
}
if ((null == mUsbEndpointIn) || (null == mUsbEndpointOut)) {
tvInfo.append("endpoint is null\n");
mUsbEndpointIn = null;
mUsbEndpointOut = null;
mUsbInterface = null;
} else {
tvInfo.append("\nendpoint out: " + mUsbEndpointOut + ",endpoint in: " +
mUsbEndpointIn.getAddress()+"\n");
mUsbInterface = usbInterface;
mUsbDeviceConnection = mUsbManager.openDevice(device);
break;
}
}
}
}
前辈二 : (一个jar,这里只截取其获取getEndpoint)
private UsbInterface[] e = new UsbInterface[2];
private UsbDeviceConnection[] f = new UsbDeviceConnection[2];
private int g = -1;
private UsbEndpoint[] h = new UsbEndpoint[2];
private UsbEndpoint[] i = new UsbEndpoint[2];
int var2 = this.d[this.g].getInterfaceCount();
Log.i("UsbDriver", " m_Device[m_UsbDevIdx].getInterfaceCount():" + var2);
if(var2 == 0) {
return false;
} else {
if(var2 > 0) {
this.e[this.g] = this.d[this.g].getInterface(0);
}
if(this.e[this.g].getEndpoint(1) != null) {
this.i[this.g] = this.e[this.g].getEndpoint(1);
}
if(this.e[this.g].getEndpoint(0) != null) {
this.h[this.g] = this.e[this.g].getEndpoint(0);
}
这个指示一部分,可以看出里面获取的手法就简化了很多
上面的可以愉快的玩耍了,就讲一下把其转化成一直读卡遇到的问题。代码如下:
public boolean init(Context context) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> devices = manager.getDeviceList();
LoggerUtil.i(TAG, "device.size = " + devices.size());
if (devices.size() <= 0) {
return false;
}
UsbDevice myUsbDevice = null;
Iterator<UsbDevice> iterator = devices.values().iterator();
while (iterator.hasNext()) {
UsbDevice device = iterator.next();
LoggerUtil.i(TAG, "device vid = " + device.getVendorId() + " , pid = " + device.getProductId());
if (ReaderAndroidUsb.isSupported(device)) {
myUsbDevice = device;
break;
}
}
if (myUsbDevice == null) {
return false;
}
// 判断是否拥有该设备的连接权限
if (!manager.hasPermission(myUsbDevice)) {
PendingIntent pd = PendingIntent.getBroadcast(context, 0,
new Intent(Device_USB), PendingIntent.FLAG_UPDATE_CURRENT);
/*
* 展示征求用户同意连接这个设备的权限的对话框。 当用户回应这个对话框时,
* 广播接收器就会收到一个包含用一个boolean值来表示结果的EXTRA_PERMISSION_GRANTED字段的意图。
* 在连接设备之前检查这个字段的值是否为true和设备之间的“交流”
*/
manager.requestPermission(myUsbDevice, pd);
}
if (!manager.hasPermission(myUsbDevice)) {
return false;
}
// 如果已经拥有该设备的连接权限,直接对该设备操作
ReaderAndroidUsb readerAndroidUsb = new ReaderAndroidUsb(manager);
readerAndroidUsb.closeReader();
try {
/*
* 函数说明: 直接打开 USB 接口读写器
* 返回值:
* >=0 表示打开读写器成功
* <0 表示打开读写器失败。
*/
int st = readerAndroidUsb.openReader(myUsbDevice);
if (st >= 0) {
reader = readerAndroidUsb;
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
ReaderAndroidUsb 为我使用读卡器中的一个打开卡的方法。最初的时候我没有在 init( )方法里面传递 context,其中的 USBmanager也只在类的初始化的时候,进行初始化一次。后面的循环读卡中,刷卡一段时间就再也刷不起了。
后面的而修改办法就是,同上。初始化传入context,重新获取 UsbManager,一切重新开始。在刷卡成功后,关闭当前的usb的连接。等同于每一次都是单次刷卡,只是把单次刷卡需要调用的东西通过代码实时完成。