> 文章列表 > 【Android车载系列】第8章 车载通信-USB协议代码实现

【Android车载系列】第8章 车载通信-USB协议代码实现

【Android车载系列】第8章 车载通信-USB协议代码实现

1 USB协议

1.1 USB协议分层

  上一篇已经简单介绍了USB协议的相关知识,其中的描述符较为重要,描述符成功返回,USB通信已经成功了一大半,具体描述符的知识点可以翻阅上一篇来了解。下面我们来看一下USB协议在的分层。
  USB协议用的地方非常多,比如U盘、麦克风、充电器等等。其中传输、事务层在USB协议中都通用,而包层则是软件层根据不同的用途做的区别实现,这一层才是我们软件开发要用到的。如下图所示:
在这里插入图片描述
在这里插入图片描述
接下来我们以U盘为例分析一下。

2 U盘协议

  在USB协议中,规定了一类大容量存储设备(mass storage device),U盘就属于大容量存储设备。大容量存储设备的接口类代码(bInterfaceClass字段)为0x08,接口子类代码(bInterfaceSubClass字段)有好几种,但大部分U盘都使用代码0x06,即SCSI透明命令集。协议代码(bInterfaceProtocol字段)有3种:0x00,0x01,0x50,前两种使用中断传输,最后一种仅使用批量传输。
  U盘通讯协议也有一套规则,详情参考官方文档:https://usb.org/sites/default/files/usbmassbulk_10.pdf0

2.1 类特殊请求

  在USB大容量存储设备的Bulk Only Transport协议中,规定了两个类特殊请求:Bulk-Only Mass Storage ResetGet Max LUN前者是复位到命令状态的请求,后者是获取最大逻辑单元请求。

2.2 Get Max LUN请求

  Get Max LUN请求的格式,由bmRequestType可知,它是发送到接口的类输入请求,bRequest值为0xFE,wIndex的值为请求的接口号,本实例中为接口0,传输的数据长度wLength 为1字节,设备将在数据过程返回1字节的数据。该字节表示设备由多少个逻辑单元,值为0时表示有一个逻辑单元,为1时表示有两个逻辑单元,以此类推,最大可以取15,Data为数据块。

BmRequestType bRequest wValue wIndex wLength Data
10100001b 11111110b 0000h Interface 0001h 1byte

2.3 Bulk-Only Mass Storeage Reset请求

  Bulk-Only Mass Storage Reset请求是通知设备接下来的批量传输端点输出数据为命令块封包CBW(Command Block Wrapper),其结构如表。在这个请求处理中,仅需设置一下状态,说明接下来的数据为CBW,然后返回一个0长度的状态数据包即可。

2.4 仅批量传输协议的数据流模型

  前面的工作完成之后,接下来就是通过批量端点传输数据。在仅批量传输协议中,规定了数据传输的结构和过程,共分成三个阶段:命令阶段数据阶段状态阶段。这有点类似控制传输,但又不完全相同。命令阶段由主机通过批量端点发送一个CBW(命令块封包)的结构,在CBW中定义了要操作的命令以及传输数据的方向和数量。数据阶段的传输方向由命令阶段决定,而状态阶段则总是由设备返回该命令完成的状态。
在这里插入图片描述

2.5 命令封包CBW的结构

  每个CBW通过Bulk-Out端点进行传输,每个CBW的长度是31字节。CBW的传输是小端格式(注:大端小端格式例如:0x1234在内存中的写入顺序:大端模式:正序。先写0x12,再写0x34。小端模式:倒序,先写0x34,再写0x12)

  1. dCBWSignature 该字段为CBW的标志,为字符串USBC(即USB命令的缩写)。用ASCII码来
    表示就是0x55,0x53,0x42,0x43。如果用小端模式的4字节整数来表示,其值就是
    0x43425355。
  2. dCBWTag CBW的标签,由主机分配,设备在完成该命令返回状态时,需要在CSW(命令状
    态封包)中的dCSWTag字段中填入命令的dCBWTag。
  3. dCBWDataTransferLength 需要在数据阶段传输数据的字节数。小端结构,即低字节在先。
  4. bmCBWFlags CBW的标志。最高位(D7)表示数据传输的方向,0表示输出数据(从主机到
    设备),1表示输入数据。其他位为0。
  5. bCBWLUN 该字段为目标逻辑单元的编号。当有多个逻辑单元时,使用该字段来区分不同的
    目标单元。该字段仅使用低4位,高4位为保留值0。
  6. bCBWCBLength CBWCB的长度。该字段仅使用5位,有效的取值范围为1~16。不同的命令
    (由CBWCB决定),其长度可能是不一样的。如果命令的长度不足16字节,则后面的部分值
    为0。
  7. CBWCB 需要执行的命令,由选择的子类决定使用那些命令。

2.6 命令状态封包CSW的结构

  CSW(Command Status Wrapper)的结构表,用Bulk-In端点进行传输,其长度是13字节用于表示CBW传输的状态。
在这里插入图片描述

  1. dCSWSignature 该字段为CSW的标志,为字符串USBS(即USB状态的缩写)。用ASCII码来
    表示就是0x55、0x53、0x42、0x53。如果用小端模式的4字节整数表示,其值就是
    0x53425355。
  2. dCSWTag 该命令状态封包的标签,其值为CBW中的dCBWTag,响应哪个CBW,就设置为哪
    个CBW的dCBWTag。
  3. dCSWDataResidue 命令完成时的剩余字节数。它表示实际完成传输的字节数与主机在CBW
    中设置的长度dCBWDataTransferLength之间的差额。
  4. bCSWStatus 命令执行的状态。0x00表示命令执行成功,0x01表示命令执行失败,0x02表示
    阶段错误。其他值为保留值。
  5. 通常,命令都能够成功完成,这时只需要设置前面两个字段为相应的值,后面两个字段都设
    置为0即可。

2.7 对批量数据的处理

  第一次批量数据,肯定是CBW。定义一个缓冲区,用来接收命令封包CBW。然后进入到数据阶段,在数据阶段中,对CBW进行解码,返回或者接收相应的数据。数据发送或接收完毕后,进入到状态阶段,返回命令执行的情况。然后再次进入命令阶段,等待主机发送CBW命令块封包。

下面我们看下有哪些命令:

2.8 查询命令 INQUIRY

  INQUIRY命令请求目标设备的一些基本信息。其格式如表。操作代码为0x12。其中EVPD字段和页码字段只支持0。分配的缓冲区长度为主机接收该命令返回数据所分配的缓冲区,缓冲区长度通常为0x24 (即26字节)
在这里插入图片描述
  INQUIRY命令返回的数据为36字节。外设类型为0表示直接寻址设备(如磁盘)。
在这里插入图片描述
  RMB位表示存储媒介是否可以移除,0为不可移除,1为可移除。
  ISO、ECMA、ANSI等各种版本号规定为0,“响应数据格式”为0x01。附加数据长度是后面附加数据的长度,为31字节。厂商信息、产品信息、产品版本号可以根据自己的需要设置。

2.9 读格式化容量命令 READ FORMAT CAPACITIES

  READFORMAT CAPACITIES命令可让主机读取设备各种可能的格式化容量的列表,如果设备中没有存储媒介,则设备返回最大能够支持的格式化容量。
在这里插入图片描述
  下表是没有存储媒介时返回最大格式容量的数据格式。其中,容量列表长度为8字节(即后面的8字节),描述符代码为3,容量的计算方法为:容量 = 块数 * 每块字节数
  通常每块字节数为512字节(即0x200),块数可以设置得大一些,它表示该设备最大支持的格式化容量,实际的容量要由存储媒介来决定。
在这里插入图片描述

2.10 读容量命令 READ CAPACITY

  READ CAPACITY命令可以让主机读取到当前存储媒介的容量,其格式如表。操作代码为0x25.该命令除了操作代码为0x25之外,其他各字段的值都为0。
在这里插入图片描述
READ CAPACITY命令返回数据格式
在这里插入图片描述

2.11 READ(10)命令

  主机通常使用READ(10)命令读取实际的磁盘数据,其命令格式如表,操作代码为0x28。另外还有一个READ(12)命令(操作代码为0xA8),它的格式跟READ(10)命令差不多,仅传输字段不一样,READ(12)的字节6-9都为传输长度,而READ(10)命令只有7-8为传输长度
  其中DPO、FUA、RelAdr等字段都为0值。
  逻辑块地址字段的值为需要读取数据的起始块的地址。在磁盘设备中,读、写通常都是按照块来操作的(所以叫块设备)。一般来说,一个逻辑块就是一个扇区,大小为512字节。当然也有其他大小的逻辑块,例如在光盘文件系统中一个块就是2048字节。
  传输长度字段的值为需要传输的逻辑块的数量,实际传输的字节数为传输长度值乘以每块大小。
  设备根据命令中指定的逻辑块地址,从存储媒介中读取数据并通过批量端点返回。当全部数据都返回后,在返回命令执行状态CSW。
在这里插入图片描述

2.12 WRITE(10)命令

  主机通常使用WRITE(10)命令往设备写入实际的磁盘数据,其命令格式如表。操作代码为0x2A。另外还有一个WRITE(12)命令(操作代码为0xAA),它的格式跟命令WRITE(10)差不多,仅传输长度字段不一样,它的WRITE(12)字节6-9都为传输长度,而WRITE(10)命令只有字节7-8为传输长度。
  该命令的各参数跟READ(10)命令类似。
  主机在发送此命令之后,接着就会发出实际要传送的数据,设备在收到全部数据后,返回命令执行的情况CSW。
在这里插入图片描述

2.13 REQUEST SENSE命令

  REQUEST SENSE命令用来探测上一个命令执行失败的原因,主机可在每个命令之后使用该命令来读取命令执行的情况。其命令代码为0x03,
在这里插入图片描述
  请求返回的数据格式如表。其中Valid字段指示信息字段是否符合UFI规范,Valid为1时,说明信息字段符合UFI规范。在UFI协议中,信息字段通常用来返回那个逻辑块地址出现了错误。Sense Key、Additional Sense Code(ASC)、Additional Sense Code Qualifier(ASCQ)表示出错的代码,可以在UFI协议的最后找到。
  本实例中,仅返回一种错误原因:无效的命令操作码,其Sense Key为0x05,ASC为0x20,ASCQ为0。
在这里插入图片描述

2.14 TEST UNIT READY命令

  TEST UNIT READY命令用来测试设备的某个逻辑单元是否准备好,操作代码为0x00,其格式如表。该命令的响应比较简单,如果设备已经准备好,则在状态阶段返回命令执行成功;否则返回命令执行失败。
  当主机使用REQUEST SENSE命令来探测错误原因时,设置Sense Key为NOT READY。
在这里插入图片描述

2.15 MODE SENSE命令

  MODE SENSE命令允许UFI设备向主机报告媒体或设备的参数。它是MODE SELSET命令的补充。
在这里插入图片描述

DBD:禁用的块描述符被设置为0
PC:页面控制字段指定要返回的模式参数的类型

在这里插入图片描述

2.16 VERIFY命令

  Verify命令要求UFI设备对媒体中的数据进行校验
在这里插入图片描述

DPO:该位应该设置为0
ByteChk:该位应该设置为0;USB-FDU只检查媒体上的CRC数据,不进行数据比较。
RelAdr:该位应该设置为0;
Logical block address:逻辑块地址,指定验证操作开始的逻辑块。
Verification length:校验长度:指定要验证的连续逻辑块的数量。“Verification length”校验长度
为0表示不校验逻辑块。这不会被视为错误,也不会校验任何数据,任何其他值都表示要验证的逻
辑块的数量。

Result values 返回值
1.如果VERIFY命令成功,UFI设备将检查数据设置为NO sense。
2.如果VERIFY命令因为USB位填充错误或者CRC错误而中止,UFI设备应该将检查数据设置为USB to HOST SYSTEM INTERFACE FAILURE

上面的理论知识了解后,我们大致知道这些U盘读写的命令协议。那么重点来了,下面我们在Android设备上操作U盘的增删改查操作。

3 U盘在Android的代码实现

3.1 AndroidManifest.xml声明

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><!-- 声明使用usb --><uses-featureandroid:name="android.hardware.usb.host"android:required="true" /><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.USB"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
<!--            很多厂家 --><intent-filter><action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /></intent-filter><meta-dataandroid:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"android:resource="@xml/device_filter" /><meta-dataandroid:name="android.app.lib_name"android:value="" /></activity></application></manifest>

device_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><usb-device vendor-id="10473" product-id="649"/><usb-device vendor-id="1336" product-id="0" />
</resources>

3.2 U盘协议的封装

  我们在上一篇USB协议的Android代码实现的基础上,继续完成通过USB对U盘进行读写操作。下面我们先简单介绍一下代码的结构,
CommandBlockWrapperNative:固定的命令拼装封装基类,然后通过继承的方式对不同的操作进行对应的指令拼装发生;
InQueryNative:查询命令拼装类;
USBHelper:USB的检测工具类;
UsbConnection:连接设备的管理及数据发送的操作;

下面我们看一下代码:

3.2.1 基类封装

CommandBlockWrapperNative

public class CommandBlockWrapperNative {/*** 1  CBW固定标志:USBC,0x43425355* 2、标签 相当于请求ID  dCBWTag* 3、U盘要返回数据的字节数  dCBWDataTransferLength* 4、 bmCBWFlags  最高位 0输出:主机要写出数据到设备 1输入:主机要读取设备数据      80    主机 到 设备  1000 0000    0000 0000  设备到主机* 5、目标逻辑单元编号,仅使用低四位* 6、cbwcb 长度* 7、CBWCB 设备将执行的命令块*/public final static byte IN = (byte) 0x80;public final static byte OUT = 0x00;public final static int dCBWSignature = 0x43425355;int dCBWTag;int dCBWDataTransferLength;byte bmCBWFlags;byte bCBWLUN = 0;public CommandBlockWrapperNative(int dCBWTag, int dCBWDataTransferLength,byte bmCBWFlags) {this.dCBWTag = dCBWTag;this.dCBWDataTransferLength = dCBWDataTransferLength;this.bmCBWFlags = bmCBWFlags;}public void serialize(ByteBuffer buffer) {buffer.order(ByteOrder.LITTLE_ENDIAN).putInt(dCBWSignature)// usb// 0x 64   100.putInt(dCBWTag)// 长度 36  0x25.putInt(dCBWDataTransferLength)// 0x80  读   00  是写.put(bmCBWFlags)// 写 00000.put(bCBWLUN);}public ByteBuffer dataStage(UsbDeviceConnection deviceConnection,UsbEndpoint endpoint) {ByteBuffer buffer = ByteBuffer.allocate(dCBWDataTransferLength);buffer.clear();deviceConnection.bulkTransfer(endpoint, buffer.array(), 0, dCBWDataTransferLength,5000);return buffer;}public void commandStage(UsbDeviceConnection deviceConnection,UsbEndpoint endpoint) {ByteBuffer buffer = ByteBuffer.allocate(31);buffer.clear();serialize(buffer);deviceConnection.bulkTransfer(endpoint, buffer.array(), 0,31, 5000);}public ByteBuffer execute(UsbConnection usbConnection) {UsbDeviceConnection deviceConnection = usbConnection.getDeviceConnection();UsbEndpoint inEndpoint=usbConnection.getInEndpoint();UsbEndpoint outEndpoint = usbConnection.getOutEndpoint();// 命令阶段commandStage(deviceConnection, outEndpoint);// 数据阶段// 查询1  写入2ByteBuffer buffer = dataStage(deviceConnection, inEndpoint);// 状态阶段// 状态阶段stateStage(deviceConnection, inEndpoint);return buffer;}public void stateStage(UsbDeviceConnection deviceConnection,UsbEndpoint endpoint) {ByteBuffer buffer = ByteBuffer.allocate(13).order(ByteOrder.LITTLE_ENDIAN);buffer.clear();deviceConnection.bulkTransfer(endpoint, buffer.array(), 0, 13, 5000);// dCBWSignatureint dCSWSignature = buffer.getInt();// dCSWTagint dCSWTag = buffer.getInt();// dCSWDataResidue 实际数据的字节与主机在CBW中设置的dCBWDataTransferLength差额int dCSWDataResidue = buffer.getInt();// bCSWStatus 状态 0x00表示成功,0x01 失败,0x02阶段错误byte bCSWStatus = buffer.get();Log.i("david", "状态: " + String.format("0x%x", dCSWSignature) + " TAG:" + dCSWTag +" 差额:" + dCSWDataResidue + " 状态:" + bCSWStatus);}
}

3.2.2 查询命令拼装类

InQueryNative.java

public class InQueryNative  extends  CommandBlockWrapperNative{private static final String TAG = "InQueryNative";public InQueryNative(int dCBWTag) {super(dCBWTag, 0x24, IN);}@Overridepublic void serialize(ByteBuffer buffer) {super.serialize(buffer);buffer.put((byte) 6)//因为都是一个字节,转不转大端都无所谓.put((byte) 0x12) //命令码.put((byte) 0x00).put((byte) 0x00).put((byte) 0x00).put((byte) 0x24)// 返回数据长度.put((byte) 0x00);}@Overridepublic ByteBuffer dataStage(UsbDeviceConnection deviceConnection, UsbEndpoint endpoint) {ByteBuffer buffer = super.dataStage(deviceConnection, endpoint);// 0-4 位:外设类型 0为磁盘buffer.get();// ram:  最高位 0为不可移除,1为可移除buffer.get();// ISO(7-6位) ECMA(5-3位) ANSI(2-0位)版本号,规定为0buffer.get();// 响应数据格式buffer.get();//附加数据长度 后面数据长度:31,但是读出来是0x41不知道为啥buffer.get();//保留buffer.get();buffer.get();buffer.get();// 厂商信息byte[] manufacturer = new byte[8];buffer.get(manufacturer);Log.i(TAG, "INQUERY libusb 厂商信息:" + Bytes.toString(manufacturer));// 产品信息byte[] product = new byte[16];buffer.get(product);Log.i(TAG, "INQUERY libusb 产品信息:" + Bytes.toString(product));// 产品版本信息byte[] version = new byte[4];buffer.get(version);Log.i(TAG, "INQUERY libusb 产品版本信息:" + Bytes.toString(version));buffer.clear();return buffer;}public static String toString(byte[] buffer) {int i = 0;for (; i < buffer.length; i++) {if ((buffer[i] & 0xff) == 0x20) break;}return new String(buffer, 0, i);}public static byte getCheckSum(byte[] data) {int sum = 0;for (int i = 0; i < 11; i++) {sum = ((sum & 1) == 1 ? 0x80 : 0) + ((sum & 0xff) >> 1) + data[i];}byte checkSum = (byte) (sum & 0xff);return checkSum;}
}

3.2.3 USB连接设备的管理及数据发送

UsbConnection.java

public class UsbConnection {private static final String TAG = "UsbConnection";// deviceprivate UsbDevice usbDevice;private UsbInterface usbInterface;// 读   端private UsbEndpoint inEndpoint;private UsbEndpoint outEndpoint;public UsbEndpoint getInEndpoint() {return inEndpoint;}public UsbEndpoint getOutEndpoint() {return outEndpoint;}public UsbDeviceConnection getDeviceConnection() {return deviceConnection;}//    USB链接private UsbDeviceConnection deviceConnection;public UsbConnection(UsbDevice usbDevice,UsbInterface usbInterface, UsbEndpoint inEndpoint, UsbEndpoint outEndpoint) {this.usbDevice = usbDevice;this.usbInterface = usbInterface;this.inEndpoint = inEndpoint;this.outEndpoint = outEndpoint;}public void connect(UsbManager usbManager) {// 连接deviceConnection =usbManager.openDevice(usbDevice);deviceConnection.claimInterface(usbInterface, true);}public void close() {deviceConnection.releaseInterface(usbInterface);deviceConnection.close();}// 发送数据public boolean sendMessageToPoint(byte[] buffer) {boolean state;if (deviceConnection==null){return false;}if (outEndpoint==null){return false;}int res = deviceConnection.bulkTransfer(outEndpoint, buffer, buffer.length, 2000);if (res < 0) {System.out.println("bulkOut返回输出为  负数");state = false;} else {System.out.println("-----------发送数据成功!");state=true;}return state;}public byte[] receiveMessageFromPoint() {if (deviceConnection == null){return null;}if (inEndpoint == null){return null;}int max = inEndpoint.getMaxPacketSize();byte[] buffer = new byte[max];if (deviceConnection.bulkTransfer(inEndpoint, buffer, buffer.length,2000) < 0) {System.out.println("------bulkIn返回输出为  负数");return null;}StringBuilder dataSb = new StringBuilder();for (byte b : buffer) {dataSb.append(Integer.toHexString(b & 0xff));dataSb.append(" ");}return buffer;}
}

3.2.4 USB的检测工具类

USBHelper.java

public class USBHelper {private static USBHelper util;private static Context mContext;private USBHelper(Context _context) { }public static USBHelper getInstance(Context _context) {if (util == null) util = new USBHelper(_context);mContext = _context;return util;}public static void findDevices(UsbManager usbManager, List<UsbDevice> devices) {// 连接N个设备Map<String, UsbDevice> deviceMap =   usbManager.getDeviceList();Collection<UsbDevice> values = deviceMap.values();Iterator<UsbDevice> iterator = values.iterator();while (iterator.hasNext()) {devices.add(iterator.next());}}public static UsbConnection getConnection(UsbDevice usbDevice) {for (int i = 0; i < usbDevice.getInterfaceCount(); i++) {UsbInterface usbInterface = usbDevice.getInterface(i);//android  U盘    类型 U盘 usbInterface  对应一个if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_MASS_STORAGE&& usbInterface.getInterfaceSubclass() == 0x06&& usbInterface.getInterfaceProtocol() == 0x50) {//每个存储设备一定有两个端点:in 和 outUsbEndpoint outEndpoint = null, inEndpoint = null;for (int j = 0; j < usbInterface.getEndpointCount(); j++) {UsbEndpoint endpoint = usbInterface.getEndpoint(j);if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {outEndpoint = endpoint;} else {inEndpoint = endpoint;}}return new UsbConnection(usbDevice, usbInterface, inEndpoint, outEndpoint);}}return null;}}

3.3 页面展示与交互

好了,下面我们在页面展示出来,写一个RecyclerView,展示在MainActivity页面

3.3.1 RecyclerView的U盘列表展示:

public class UsbListAdapter extends BaseAdapter<UsbListAdapter.UsbListViewHolder> {private List<UsbDevice> usbDevices;public UsbListAdapter(Context context, List<UsbDevice> usbDevices) {super(context);this.usbDevices = usbDevices;}@Overridepublic int getLayoutId(int viewType) {return R.layout.item_usb_info;}@Overrideprotected UsbListViewHolder onCreateViewHolder(View view) {return new UsbListViewHolder(view);}@Overrideprotected void onBindVH(UsbListViewHolder holder, int position) {UsbDevice usbDevice = usbDevices.get(position);holder.device_name.setText(usbDevice.getDeviceName());holder.manufacturer_name.setText("制造商:" + usbDevice.getManufacturerName());holder.product_name.setText("产品:" + usbDevice.getProductName());}@Overridepublic int getItemCount() {return usbDevices.size();}static class UsbListViewHolder extends RecyclerView.ViewHolder {TextView device_name, manufacturer_name, product_name;public UsbListViewHolder(@NonNull View itemView) {super(itemView);device_name = itemView.findViewById(R.id.device_name);manufacturer_name = itemView.findViewById(R.id.manufacturer_name);product_name = itemView.findViewById(R.id.product_name);}}}
public abstract class BaseAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {private LayoutInflater mInflater;private OnItemClickListener mListener;public BaseAdapter(Context context) {mInflater = LayoutInflater.from(context);}public void setOnItemClickListener(OnItemClickListener listener) {mListener = listener;notifyDataSetChanged();}public abstract int getLayoutId(int viewType);@NonNull@Overridepublic VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = mInflater.inflate(getLayoutId(viewType), parent, false);return onCreateViewHolder(view);}@Overridepublic void onBindViewHolder(@NonNull VH holder, int position) {//创建点击事件holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mListener.onItemClick(v, position);}});onBindVH(holder, position);}protected abstract VH onCreateViewHolder(View view);protected abstract void onBindVH(VH holder, int position);public interface OnItemClickListener {public abstract void onItemClick(View view, int position);}
}

item_usb_info.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_vertical"android:orientation="horizontal"android:padding="8dp"><TextViewandroid:id="@+id/device_name"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="/dev/bus/usb/001/002" /><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"><TextViewandroid:id="@+id/manufacturer_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="制造商:SanDisk" /><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="@android:color/darker_gray" /><TextViewandroid:id="@+id/product_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="产品:Ultra USB 3.0" /></LinearLayout>
</LinearLayout>

3.3.2 MainActivity页面展示

MainActivity.java

public class MainActivity extends AppCompatActivity implements BaseAdapter.OnItemClickListener {private UsbManager usbManager;private UsbListAdapter usbRVAdapter;private List<UsbDevice> devices;private UsbReceiver usbReceiver;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);RecyclerView recyclerView = findViewById(R.id.recyclerView);recyclerView.setLayoutManager(new LinearLayoutManager(this));devices = new ArrayList<>();usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);USBHelper.findDevices(usbManager, devices);System.out.println(devices);usbRVAdapter = new UsbListAdapter(this,devices);usbRVAdapter.setOnItemClickListener(this);recyclerView.setAdapter(usbRVAdapter);}@Overridepublic void onItemClick(View view, int position) {UsbDevice usbDevice = devices.get(position);if (!usbManager.hasPermission(usbDevice)) {// todo:申请权限Log.i("MainActivity", "无权限,需要申请权限!")return;}UsbConnection usbConnection= USBHelper.getConnection(usbDevice);usbConnection.connect(usbManager);InQueryNative inQueryNative = new InQueryNative(100);inQueryNative.execute(usbConnection);}
}

4 总结

  USB协议我们从理论到代码过了一遍,相信聪明的小伙伴应该有所收获。最后做下总结吧,USB协议是一套完整的传输协议,使用其传输的两端必须按照一致的指令协议来实现通讯,实现读写操作。
  对于Android开发来说,能够掌握基本的USB读写操作就可以了,具体协议的详细在需要用到的时候再查找文档对应实现。日常开发和工作中的数据读写操作,大部分都需要自定义协议,可以参考U盘等这些USB设备的协议来自定义一套适合自己的协议。

摘抄录