欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

安卓系统通过控制传输实现与 USB_HID 设备的通信

最编程 2024-04-13 09:27:53
...

前言:世上几百年旧家,无非积德;天下第一件好事,还是读书。

引言

  这次记录一下安卓如何实现与USB_HID设备通过控制传输通信,以及说明一些我在开发过程中遇到的坑以及注意点,如果对USB的知识没有多少了解的话,建议小伙伴可以先看看我的上一篇文章:安卓实现与USB_HID设备实现控制传输通信所需要具备的USB知识,上一篇文章讲了作为安卓工程师,如果要开发USB_HID设备的话需要了解的一些USB基础知识。写这篇文章的目的是以此作为一个记录,也希望可以帮到有需要的小伙伴们,话不多说,我们开始。

目录

一、用到的与USB相关API
  1.1 UsbManager
  1.2 UsbDevice
  1.3 UsbInterface
  1.4 UsbEndpoint
二、实现安卓与USB_HID设备通信的步骤
  2.1 准备工作
    2.1.1 定义通讯接口
    2.1.2 定义usb连接开启成功或失败的回调接口
    2.1.3 定义usb接收数据成功或失败的回调接口
    2.1.4 定义usb发送数据成功或失败的回调接口
    2.1.5 定义一个usb设备连接成功的广播
    2.1.6 定义一个usb设备连断开连接的广播
    2.1.7编写接口的实现类UsbCommunication
  2.2 找到设备
    2.2.1 添加权限
    2.2.2 检查手机是否支持USB_HID
    2.2.3初始化USB_HID连接对象
    2.2.4找到USB_HID连接
  2.3 获取权限
  2.4 使用控制传输发送数据
  2.5 使用控制传输接收数据
三、总结
  3.1 MainActivity.java中的代码
  3.2 activity_main.xml中的代码

一、用到的与USB相关API

  1.1 UsbManager
  该类允许我们访问USB的状态与USB设备通信。
  UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
  其中有几个方法是需要了解的:
  ①getDeviceList():
  获取设备列表,返回HashMap;一般来说设备只有一个,因为手机就一个usb口,如果没有连接设备,或者USB主机模式处于非活动状态或不受支持,结果将为空。
  ②hasPermission():
  如果调用者有权访问附件,则返回true。权限可能已通过requestPermission(UsbAccessory,PendingIntent)或用户选择主叫方作为附件的默认应用暂时授予。就是判断app是否有接入usb的权限,一般都要在工程的AndroidManifest.xml添加usb通信权限,给予权限则函数返回true,否则返回false。
  ③openDevice(UsbDevice device):
  打开usb设备,后续使其可以使用UsbRequest进行数据发送和接收。
  ④requestPermission(UsbDevice device,PendingIntent pi):
  请求临时接入权限访问设备。如果尚未授予权限,则可能会导致系统对话框显示给用户。成功或失败通过PendingIntent pi返回,如果成功,则允许主叫方访问设备,直到设备断开连接。
  1.2 UsbDevice
  这个类代表一个USB设备,安卓设备作为USB主机。每个设备包含一个或多个UsbInterface,其中的每一个包含若干UsbEndpoint。
  方法:
  ①getDeviceClass():
  返回usb设备的类别,返回类型为整型。
  ②getDeviceId():
  返回设备的唯一整数ID。
  ③getDeviceName():
  返回一个字符串类型的设备名称。
  ④getDeviceProtocol():
  返回一个整型类型的协议类别。
  ⑤getDeviceSubclass():
  返回一个整型类型的设备子类别。
  ⑥getVendorId():
  返回一个整型类型的生产商ID。
  ⑦getProductId():
  返回一个整型类型的产品ID。
  ⑧getInterfaceCount():
  返回一个整型类型的接口数量值。
  ⑨getInterface(int index):
  得到一个UsbInterface类型的接口
  1.3 UsbInterface
  代表UsbDevice上的接口,USB设备可以具有一个或多个接口,每个接口提供与其他接口分离的不同功能块。一个接口可以有一个或多个UsbEndpoint,这是主机通过设备传输数据的通道。
  方法:
  ①getId():
  返回一个整型类型接口的bInterfaceNumber字段。
  ②getInterfaceClass():
  返回一个整型类型接口的类字段。
  ③getInterfaceSubclass():
  返回一个整型类型接口的子类字段。
  ④getEndpointCount():
  返回一个整型类型此接口包含的UsbEndpoint(端点)数量。
  ⑤getEndpoint(int index):
  返回一个UsbEndpoint(类型)类型给定索引处的端点。
  1.4 UsbEndpoint
  表示一个接口某个端点的类,USB_HID主要就是通过端点进行通信的。端点相当于USB发送和接收数据的通道。一般情况下,批量端点用于发送不重要的数据量;中断端点用于与主数据流分开发送少量数据,通常是事件;端点0是从主机发送到设备的控制消息的特殊端点;同步端点当前不受支持。
  方法:
  ①getAddress():
  返回一个整型类型的端点地址。地址是一个包含端点号码和端点数据方向的位域。
  ②getAttributes():
  返回一个整型类型的端点属性字段。
  ③getDirection():
  返回一个整型类型的端点数据传输方向。USB_DIR_OUT—方向是主机设备;USB_DIR_IN—方向是设备到主机。
  1.5 UsbDeviceConnection
  Usb连接类,用这个连接类和USB设备进行数据发送和接收。这个类的实例由openDevice(UsbDevice)创建。
  方法:
  ①bulkTransfer(UsbEndpoint endpoint,byte[] buffer,int length,int timeout):
  通过给定的endpoint节点进行大量数据传输,传输方向取决于端点方向,一般自己获取输入输出的方向,buffer是发送或接收的数组,length是数组长度,失败会返回负数(-1)。
  ②controlTransfer(int requestType,int request,int value,int index,byte[] buffer,int length,int timeout):
  通过端点0向设备传输和接收数据,如果使用控制传输用的就是这个方法,这个方法的具体参数很重要,后面代码中会特别指出通过使用SET_FEATURE和GET_FEATURE通信时的参数选择,至于其他选择,方法是一样的,会了这两个请求的参数设置,别的也都一样,我代码中将会使用的是以下格式:
  SET_FEATURE如下图:
  
  意思是使用SET_FEATURE请求(0x21),HID类的请求描述符为0x09,ReportID为03(value的低位),报告类型为特征报告(03—即value的高位),HID的接口索引值为0(要特别注意这个,要知道设备接口的具体值。安卓设备是不会去自动寻找的),后面三个参数分别是byte[]类型的数据,数据长度(length),通信超时时间(timeout),该方法通过端点0向HID设备发送数据。
  GET_FEATURE如下图:
  
  意思是使用GET_FEATURE请求(0xA1),HID类的请求描述符为0x01,ReportID为03(value的低位),报告类型为特征报告(03—即value的高位),HID的接口索引值为0(要特别注意这个,要知道设备接口的具体值。安卓设备是不会去自动寻找的),后面三个参数分别是byte[]类型的数据,数据长度(length),通信超时时间(timeout),其中的receiveData为事先定义好的一个byte数组,这个数组长度和参数length一样。当HID设备向APP发送数据,APP有数据接收时会把数据存储在receiveData数组里,该方法通过端点0向HID设备请求数据。
  ③claimInterface(UsbInterface intf,boolean force):
  声明独家访问UsbInterface。这必须在发送或接收属于接口的任何UsbEndpoint的数据之前完成。

二、实现安卓与USB_HID设备通信的步骤

  2.1准备工作
    2.1.1 定义通讯接口

public interface BaseCommunication {
    /**
     * 开启通讯接口
     * @param listener
     */
    public void openCommunication(CommunicationListener listener,int VID,int PID);
    /**
     * 关闭通讯接口
     */
    public void closeCommunication();
}

    2.1.2 定义usb连接开启成功或失败的回调接口

public interface CommunicationListener {
    void onSuccess(int code,String msg);
    void onFaild(String msg);
}

    2.1.3 定义usb接收数据成功或失败的回调接口

public interface ReciverMessageListener {
    void onSuccess(byte[] bytes);
    void onFaild(String msg);
}

    2.1.4 定义usb发送数据成功或失败的回调接口

public interface SendMessageListener {
    void onSuccess();
    void onFaild(String msg);
}

    2.1.5 定义一个usb设备连接成功的广播
    usb设备连接成功时这个广播就能监听到。

public class OpenDevicesReceiver extends BroadcastReceiver {
    private OpenDevicesListener mOpenDevicesListener;//usb设备连接的回调接口
    public OpenDevicesReceiver(OpenDevicesListener openDevicesListener) {
        mOpenDevicesListener = openDevicesListener;
    }
    private CommunicationListener listener;//sdk中用户打开usb连接成功的回调
    public void setCommunicationListener(CommunicationListener listener) {
        this.listener = listener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);//获取附件设备
        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,false)) {//判断设备是否有usb
            if (usbDevice != null) {
                mOpenDevicesListener.openAccessoryModel(usbDevice);
                listener.onSuccess(2,"usb权限开启成功");
                Toast.makeText(context,"usb权限开启成功",Toast.LENGTH_SHORT).show();
            }else {
                mOpenDevicesListener.openDevicesError();
                listener.onFaild("USB设备连接异常");
            }
        }else {
            //打开权限失败
            mOpenDevicesListener.openDevicesError();
            listener.onFaild("用户未授权USB权限");
        }
    }

    public interface OpenDevicesListener {
        /**
         * 打开Accessory模式
         */
        void openAccessoryModel(UsbDevice usbDevice);
        /**
         * 打开设备(手机)失败
         */
        void openDevicesError();
    }
}

    2.1.6 定义一个usb设备连断开连接的广播
    usb设备连接断开时这个广播就能监听到。

public class UsbDetacheReceiver extends BroadcastReceiver {
    private UsbDetachedListener mUsbDetachedListener;//usb连接断开的回调
    /**
     * 构造方法中添加回调参数
     * @param UsbDetachedListener
     */
    public UsbDetacheReceiver(UsbDetachedListener UsbDetachedListener) {
        mUsbDetachedListener = UsbDetachedListener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        mUsbDetachedListener.usbDetached();
    }

    public interface UsbDetachedListener{
        /**
         * usb断开连接
         */
        void usbDetached();
    }
}

    2.1.7编写接口的实现类UsbCommunication

public class UsbCommunication implements BaseCommunication, UsbDetacheReceiver.UsbDetachedListener,
OpenDevicesReceiver.OpenDevicesListener{
  //usb设备连接广播
  private OpenDevicesReceiver mOpenDevicesReceiver;
  //usb连接断开广播
  private UsbDetacheReceiver mUsbDetacheReceiver;
  //需要使用context初始化相关usb资源
  private Context mContext;
  //usb设备管理器
  private UsbManager mUsbManager;
  //自定义usb权限action
  private static final String USB_ACTION = "com.wiseasy.communication.usb.hostchart";

  /**
   *Handler相关判断的变量,对应:usb连接初始化成功、失败,usb连接接收消息成功、失败,usb连接发送消息成功、失败
   */
  private static final int RECEIVER_MESSAGE_SUCCESS = 1;
  private static final int SEND_MESSAGE_SUCCESS = 2;
  private static final int RECEIVER_MESSAGE_FAILED = 3;
  private static final int SEND_MESSAGE_FAILED = 4;
  private static final int INIT_FAILED = 5;
  private static final int INIT_SUCCESS = 6;

  private int devicePid = 0;
  private int deviceVid = 0;

  //接收消息监听回调
  private ReciverMessageListener reciverMessageListener;
  //发送消息监听回调
  private SendMessageListener sendMessageListener;

  @SuppressLint("HandlerLeak")
  private Handler mHandler = new Handler() {
      @Override
      public void handleMessage(@NonNull Message msg) {
          super.handleMessage(msg);
          switch (msg.what) {
              case RECEIVER_MESSAGE_SUCCESS://成功接收到数据
                  reciverMessageListener.onSuccess((byte[]) msg.obj);
                  break;
              case RECEIVER_MESSAGE_FAILED://接收消息失败
                  reciverMessageListener.onFaild("接收消息失败");
                  break;
              case SEND_MESSAGE_SUCCESS://发送数据成功
                  sendMessageListener.onSuccess();
                  break;
              case SEND_MESSAGE_FAILED://发送数据失败
                  sendMessageListener.onFaild("发送消息失败");
                  break;
              case INIT_SUCCESS://usb主附设备连接成功
                  communicationListener.onSuccess(1,"初始化成功");
                  break;
              case INIT_FAILED://usb主附设备连接失败
                  communicationListener.onFaild("初始化失败");
                  break;
              default:
                  break;
          }
      }
  };

  /**
   * 单例模式 初始化
   * @param context
   */
  private UsbCommunication(Context context) {
      /**
       * 为了避免在单利模式下的内存泄露,这里将context统一转换为ApplicationContext
       */
      this.mContext = context.getApplicationContext();
      //注册usb连接断开的广播
      mUsbDetacheReceiver = new UsbDetacheReceiver(this);
      IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
      mContext.registerReceiver(mUsbDetacheReceiver,intentFilter);
      //通过context获取到当前系统的USB管理器
      mUsbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
  }

  //单例变量
  private static volatile UsbCommunication Instance;

  /**
   * 单例模式双重检查
   * @param context
   * @return
   */
  public static UsbCommunication getInstance(Context context) {
      if (Instance == null) {
          synchronized (UsbCommunication.class) {
              if (Instance == null) {
                  Instance = new UsbCommunication(context);
              }
          }
      }
      return Instance;
  }

  //usb连接开启成功或失败的回调
  private CommunicationListener communicationListener;

  /**
  * 主设备开启成功,进行从设备唤醒
  * @param usbDevice
  */
    @Override
    public void openAccessoryModel(UsbDevice usbDevice) {
        initDevice(deviceVid,devicePid);
    }

    /**
     * 主设备权限被拒绝
     */
    @Override
    public void openDevicesError() {
        Toast.makeText(mContext,"USB权限被拒绝",Toast.LENGTH_SHORT).show();
    }

    /**
     * usb连接断开 释放资源
     */
    @Override
    public void usbDetached() {
        if (mUsbDeviceConnection != null) {
            mUsbDeviceConnection.releaseInterface(mUsbInterface);
            mUsbDeviceConnection.close();
            mUsbDeviceConnection = null;
        }
        mUsbEndpointIn = null;
        mUsbEndpointOut = null;
        isReceiverMessage = false;
        mContext.unregisterReceiver(mUsbDetacheReceiver);
        mContext.unregisterReceiver(mOpenDevicesReceiver);
    }

	//usb设备连接通道
    private UsbDeviceConnection mUsbDeviceConnection;
    //usb接口
    private UsbInterface mUsbInterface;
    //usb输出端点
    private UsbEndpoint mUsbEndpointOut;
    //usb输入端点
    private UsbEndpoint mUsbEndpointIn;

    private void initDevice(int VID,int PID) {
        /**
         * 使用usbManager遍历设备集合,通过producetId找出匹配accessory模式的设备
         */
        //通过context获取到当前系统的USB管理器
        mUsbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
        HashMap<String,UsbDevice> deviceList = mUsbManager.getDeviceList();
        Collection<UsbDevice> values = deviceList.values();
        if (!values.isEmpty()) {
            for (UsbDevice usbDevice : values) {
                if (usbDevice.getVendorId() == VID && usbDevice.getProductId() == PID) {
                    if (mUsbManager.hasPermission(usbDevice)) {
                        //获取当前usb设备的通讯连接
                        mUsbDeviceConnection = mUsbManager.openDevice(usbDevice);
                        if (mUsbDeviceConnection != null) {
                            //获取通讯连接接口
                            mUsbInterface = usbDevice.getInterface(0);
                            //获取通讯连接端点数量
                            int endpointCount = mUsbInterface.getEndpointCount();
                            for (int i = 0;i < endpointCount;i++) {
                                //遍历所有端点,找到输入端点与输出端点
                                UsbEndpoint usbEndpoint = mUsbInterface.getEndpoint(i);
//                            if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                                if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
                                    mUsbEndpointOut = usbEndpoint;//赋值输出端点
                                }else if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
                                    mUsbEndpointIn = usbEndpoint;//赋值输入端点
                                }
//                            }
                            }
                            //当输出端点和输入端点都不为空时,表示usb连接成功,初始化完成,可以进行数据收发
                            if (mUsbEndpointOut != null && mUsbEndpointIn != null) {
                                mHandler.sendEmptyMessage(INIT_SUCCESS);
                            }
                        }
                    }else {
                        //申请usb权限
                        mUsbManager.requestPermission(usbDevice,PendingIntent.getBroadcast(mContext,0,new Intent(USB_ACTION),0));
                    }
                }
            }
        }else {
            //初始化失败
            mHandler.sendEmptyMessage(INIT_FAILED);
        }
    }


  /**
   * 开启usb连接的接口实现
   * @param communicationListener
   */
  @Override
  public void openCommunication(CommunicationListener communicationListener, int VID, int PID) {
    devicePid = VID;
    deviceVid = PID;
    this.communicationListener = communicationListener;
    //用来申请usb权限
    PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,0,new Intent(USB_ACTION),0);
    //注册usb连接开启的广播
    mOpenDevicesReceiver = new OpenDevicesReceiver(this);
    IntentFilter intentFilter = new IntentFilter(USB_ACTION);
    mContext.registerReceiver(mOpenDevicesReceiver,intentFilter);
    //设置连接成功的监听回调
    mOpenDevicesReceiver.setCommunicationListener(communicationListener);
    /**
             * 通过usbManager获取当前连接的设备集合
             * 遍历集合通过productId过滤不符合条件的设备
             */
    HashMap<String,UsbDevice> deviceList = mUsbManager.getDeviceList();
    communicationListener.onFaild("获取设备:" + deviceList);
    if (deviceList != null) {
        for (UsbDevice usbDevice : deviceList.values()) {
            int productId = usbDevice.getProductId();
            if (usbDevice.getVendorId() == VID && usbDevice.getProductId() == PID) {
                communicationListener.onFaild("获取设备到:" + PID);
                if (mUsbManager.hasPermission(usbDevice)) {
                    //子设备初始化
		initDevice(deviceVid,devicePid);
                }else {
                    //申请usb权限
                    mUsbManager.requestPermission(usbDevice,pendingIntent);
                }
            }
        }
    }else {
        //连接失败回调
        communicationListener.onFaild("请连接USB");
    }
}

  /**
   * 关闭usb连接
   * 释放所有资源
   */
  @Override
  public void closeCommunication() {
      if (mUsbDeviceConnection != null) {
          mUsbDeviceConnection.releaseInterface(mUsbInterface);
          mUsbDeviceConnection.close();
          mUsbDeviceConnection = null;
      }
      mUsbEndpointIn = null;
      mUsbEndpointOut = null;
      isReceiverMessage = false;
      mContext.unregisterReceiver(mUsbDetacheReceiver);
      mContext.unregisterReceiver(mOpenDevicesReceiver);
  }

/**
   * 发送数据接口实现
   * 发送byte[]类型数据
   * 通过回调监听成功或失败
   * @param bytes
   * @param listener
   */
  @Override
  public void sendMessage(final byte[] bytes, SendMessageListener listener) {
      this.sendMessageListener = listener;
      if (bytes != null) {
          /**
           * 耗时操作在子线程中执行
           */
          new Thread(new Runnable() {
              @Override
              public void run() {
                  /**
                                  * 发送数据的地方,只接收byte数据类型的数据
                                  */
                  int i = mUsbDeviceConnection.bulkTransfer(mUsbEndpointOut,bytes,bytes.length,3000);
                  if (i > 0) {//大于0表示发送成功
                      mHandler.sendEmptyMessage(SEND_MESSAGE_SUCCESS);
                  }else {
                      mHandler.sendEmptyMessage(SEND_MESSAGE_FAILED);
                  }
              }
          }).start();
      }else {
          listener.onFaild("发送数据为null");
      }
  }

  //接收数据循环变量,usb连接成功后需要一直监听用户发送的数据
  private boolean isReceiverMessage = true;
  //暂定的接收数据的大小,需要优化
  private byte[] mBytes = new byte[1024];


  /**
   * 接收数据的实现
   * 通过回调来返回接收的数据
   * 使用handler来传递接收的数据到主线程
   * @param listener
   */
  @Override
  public void receiveMessage(ReciverMessageListener listener) {
      this.reciverMessageListener = listener;
      new Thread(new Runnable() {
          @Override
          public void run() {
              SystemClock.sleep(1000);
              while (isReceiverMessage) {
                  /**
                   * 循环接受数据的地方 , 只接受byte数据类型的数据
                   */
                  if (mUsbDeviceConnection != null && mUsbEndpointIn != null) {
                      int i = mUsbDeviceConnection.bulkTransfer(mUsbEndpointIn, mBytes, mBytes.length, 3000);
                      if (i > 0) {
                          Message message = Message.obtain();
                          message.what = RECEIVER_MESSAGE_SUCCESS;
                          message.obj = mBytes;
                          mHandler.sendMessage(message);
                      }
                  } else {
                      mHandler.sendEmptyMessage(RECEIVER_MESSAGE_FAILED);
                  }
              }
          }
      }).start();
  }
}

  2.2 找到设备
    2.2.1 添加权限
    在AndroidManifest.xml下添加以下权限:

<uses-feature android:name="android.hardware.usb.host"/>
<uses-permission android:name="android.permission.usb.accessory"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    在AndroidManifest.xml的<activity android:name=”.MainActivity”></activity>中添加以下注册信息:

<intent-filter>
    <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>

    2.2.2 检查手机是否支持USB_HID
    在进行通信前,需要检查手机是否支持USB_HID设备,即手机是否支持OTG,只有手机支持OTG时才可以通过USB_HID和外部进行通信。
    在MainActivity.java的onCreate()方法中写入以下代码用来检查手机是否支持OTG:

manager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (manager == null) {
    Log.e(TAG,"不支持OTG");
    Toast.makeText(MainActivity.this,"不支持OTG",Toast.LENGTH_SHORT).show();
    return;
}else {
    Log.e(TAG,"usb设备:" + manager.toString());
    Toast.makeText(MainActivity.this,manager.toString(),Toast.LENGTH_SHORT).show();
    mTvState.setText(manager.toString());
}

    2.2.3初始化USB_HID连接对象
    在MainActivity.java的onCreate()方法中初始化usb连接对象。

usbCommunication = UsbCommunication.getInstance(this);//初始化usb连接对象

    2.2.4找到USB_HID连接
    在初始化连接对象后根据VID和PID找到对应的设备,如果不知道PID和VID可以向硬件工程师问,也可以通过API方法获取(就在前面)。

usbCommunication.openCommunication(new CommunicationListener() {
    @Override
    public void onSuccess(int code, String msg) {
    //连接成功回调
   }
    @Override
    public void onFaild(String msg) {
        //连接失败回调
    }
},0x04d9,0xf541);

  2.3 获取权限
  要使APP和USB_HID设备能够通信,需要申请usb临时权限,如果mUsbManager.hasPermission(usbDevice)的返回值为true,那么说明权限已获取,如果为false,则说明未获取权限,也就不能够通信。获取权限使用动态申请的方式获取,方法为:

//申请usb权限
mUsbManager.requestPermission(usbDevice,pendingIntent);

  执行requestPermission()方法后界面会弹出一个申请权限框,效果如下图所示:

  点击确定说明已经获取到了权限,即可与连接的设备通信了。
  2.4 使用控制传输发送数据
  通过控制传输发送数据用的是设备端点0进行发送,在发送之前得执行以下步骤:
  ①获取设备

HashMap<String,UsbDevice> deviceList = manager.getDeviceList();
if (!deviceList.isEmpty()) {
    for (UsbDevice device : deviceList.values()) {
        if (device.getVendorId() == 0x04d9 && device.getProductId() == 0xf541) {//获取设备列表,根据PID和VID找到设备
            mUsbDevice = device;
        }
    }
}

  ②判断是否获取通信权限

if (manager.hasPermission(mUsbDevice)) {//如果已经获取到了通信授权,那么返回的是true。

}

  ③获取通信

mUsbDeviceConnection = manager.openDevice(mUsbDevice);//打开设备获取通信连接

  ④选择接口

mUsbInterface = mUsbDevice.getInterface(0);

  选择通信的接口,有些设备里面可能有多个接口,但是每个接口都会有一个端点0,一定要选择存在的接口,不能选择不存在的接口,因为安卓不会像电脑一样自动寻找接口,这点上一篇文章中已经提到。
  ⑤声明接口

mUsbDeviceConnection.claimInterface(mUsbInterface,true);

  ⑥发送数据

final byte[] conToBootloaderData = {0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
new Thread(new Runnable() {
      @Override
      public void run() {
      int j = mUsbDeviceConnection.controlTransfer(0x21,0x09,0x0303,0,conToBootloaderData,8,3000);
         if (j > 0) {
            //发送成功
            showResponse("发送成功");
          }else {
            //发送失败
            showResponse("发送失败");
          }
       }
}).start();

  数据发送过去的数据得定义好,当数据发送成功时会返回一个整型类型的数值,即程序里面的j。发送失败返回的会是一个负数。
  2.5 使用控制传输接收数据
  接收数据的方法和发送数据的方法是一样的,只不过是方法里面的参数取值不一样。

new Thread(new Runnable() {
  @Override
  public void run() {
    int j = mUsbDeviceConnection.controlTransfer(0xA1,0x01,0x0303,0,receiveData,8,3000);
    if (j > 0) {
        //接收成功
        showResponse("接收成功:" + j);
     }else {
        //接收失败
        showResponse("接收失败:" + j);
     }
   }
}).start();

  当发送成功时,同样会返回一个int类型的非负数,发送失败会返回一个int类型的负数,可以根据此判断是否发送成功。

三、总结

  3.1 MainActivity.java中的代码

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "UsbHid";

    private final byte[] receiveData = new byte[8];

    private UsbManager manager;//USB管理器
    private UsbDevice mUsbDevice;//找到的USB设备
    private UsbInterface mUsbInterface;//设备接口
    private UsbDeviceConnection mUsbDeviceConnection;//用于打开连接设备,发送或接收数据
    private UsbCommunication usbCommunication;

    private TextView mTvState;
    private TextView mTvDataReceive;
    private Button mButton;
    private Button mBtnReceive;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTvState = findViewById(R.id.tv_state);
        mTvDataReceive = findViewById(R.id.tv_data_receive);
        mButton = findViewById(R.id.btn_send);
        mButton.setOnClickListener(this);

        mBtnReceive = findViewById(R.id.btn_receive);
        mBtnReceive.setOnClickListener(this);

        manager = (UsbManager) getSystemService(Context.USB_SERVICE);
        if (manager == null) {
            Log.e(TAG,"不支持OTG");
            Toast.makeText(MainActivity.this,"不支持OTG",Toast.LENGTH_SHORT).show();
            return;
        }else {
            Log.e(TAG,"usb设备:" + manager.toString());
            Toast.makeText(MainActivity.this,manager.toString(),Toast.LENGTH_SHORT).show();
            mTvState.setText(manager.toString());
        }

        usbCommunication = UsbCommunication.getInstance(this);//初始化usb连接对象
        mTvState.setText("初始化usb连接对象");
        //第一次是为了获取通信权限,目的是获取VID为0x04d9、PID为0xf541设备的通信权限
        usbCommunication.openCommunication(new CommunicationListener() {
            @Override
            public void onSuccess(int code, String msg) {
            }

            @Override
            public void onFaild(String msg) {

            }
        },0x04d9,0xf541);

        HashMap<String,UsbDevice> deviceList = manager.getDeviceList();//获取设备列表
        if (!deviceList.isEmpty()) {
            for (UsbDevice device : deviceList.values()) {
                if (device.getVendorId() == 0x04d9 && device.getProductId() == 0xf541) {//根据PID和VID找到设备
                    mUsbDevice = device;
                }
            }
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_send:
                if (manager != null) {
                    if (mUsbDevice != null) {
                        if (manager.hasPermission(mUsbDevice)) {//判断是否获取到了通信权限
                            //获取当前usb设备的通讯连接
                            mUsbDeviceConnection = manager.openDevice(mUsbDevice);
                            if (mUsbDeviceConnection != null) {
                                mUsbInterface = mUsbDevice.getInterface(0);//选择接口0
                                mUsbDeviceConnection.claimInterface(mUsbInterface,true);//声明接口
                                final byte[] conToBootloaderData = {0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {//发送数据
                                        int j = mUsbDeviceConnection.controlTransfer(0x21,0x09,0x0303,0,conToBootloaderData,8,3000);
                                        if (j > 0) {
                                            //接收成功
                                            showResponse("发送成功");

                                        }else {
                                            //接收失败
                                            showResponse("发送失败");
                                        }
                                    }
                                }).start();
                            }
                        }
                    }
                }
                break;
            case R.id.btn_receive://接收数据
                if (manager.hasPermission(mUsbDevice)) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            int j = mUsbDeviceConnection.controlTransfer(0xA1,0x01,0x0303,0,receiveData,8,3000);
                            if (j > 0) {
                                //接收成功
                                showResponse("接收成功");
                            }else {
                                //接收失败
                                showResponse("接收失败");
                            }
                        }
                    }).start();
                }
                break;
            default:
                break;
        }
    }

    private void showResponse(final String response) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                switch (response) {
                    case "发送成功":
                        mTvDataReceive.setText("发送成功");
                        break;
                    case "发送失败":
                        mTvDataReceive.setText("发送失败");
                        break;    
                    case "接收成功":
                        mTvDataReceive.setText("接收成功");
                        break;
                    case "接收失败":
                        mTvDataReceive.setText("接收失败");
                        break;    
                    default:
                        break;
                }
            }
        });
    }
}

  3.2 activity_main.xml中的代码

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn_send"
        android:text="发送"
        android:layout_alignParentRight="true"
        android:textAllCaps="false"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn_receive"
        android:text="接收"
        android:textAllCaps="false"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tv_data_receive"
        android:layout_centerHorizontal="true"
        android:text="接收到的数据"
        android:layout_marginTop="90dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tv_state"
        android:text="Hello World!"
        android:layout_centerInParent="true"/>

</RelativeLayout>

  以上就是我如何用APP实现与USB_HID设备通过控制传输通信的过程,从一开始USB是什么都不知道,到后面怎么使用控制传输通信,特别是控制传输方法中的参数选择,每个设备都有不一样的接口、ReportID等等,网上有很多文章都没说明这些参数如何选择,所以本文中特别说明了这些参数是如何选取的,这些都是要跟硬件工程师那边进行交流沟通才知道的。如果有大佬看到文章中哪里写得不对的地方,恳请指出,及时修改。最后,看到这里,如果对你有所帮助,给个赞呗~

推荐阅读