Android USB通信

Android USB通信

  • Author: 迷你小猪
  • Date: 2020-09-07
  • Email: Benjie1017@outlook.com

知识简介:

  1. Android 通过 USB 配件和 USB 主机两种模式支持各种 USB 外围设备和 Android USB 配件(实现 Android 配件协议的硬件)。

    • USB 配件模式下,外部 USB 硬件充当 USB 主机,配件包括机器人控制器、扩展坞、音乐设备、自助服务终端、读卡器等。不具备主机功能的 Android 设备就能够与 USB 硬件互动。Android USB 配件必须设计为与 Android 设备兼容,并且必须遵守 Android 配件通信协议。

    • USB 主机模式下,Android 设备充当主机,设备包括数码相机、键盘、鼠标和游戏控制器等。针对各类应用和环境设计的 USB 设备仍可与能够与设备正常通信的 Android 应用互动。

  2. 下图展示了这两种模式之间的差异。

    • Android 设备处于主机模式时,它会充当 USB 主机并为总线供电。

    • Android 设备处于 USB 配件模式时,所连接的 USB 硬件(本例中为 Android USB 配件)充当主机并为总线供电。
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OuBVIJUv-1605706806773)(https://developer.android.com/images/usb-host-accessory.png)]

  3. Android 3.1(API 级别 12)或更高版本的平台直接支持 USB 配件和主机模式。USB 配件模式还作为插件库向后移植到 Android 2.3.4(API 级别 10)中,以支持更广泛的设备。设备制造商可以选择是否在设备的系统映像中添加该插件库。


USB通信模式简称:

  1. Accessory Mode: 配件模式,以下简称 AM

  2. Host Mode: 主机模式,以下简称 HM

  3. USB 设备管理器:

    • Android 2.3.4 版本:

      UsbManager manager = UsbManager.getInstance(this);
      
    • Android 3.1 版本:

      UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
      

USB AM介绍:

获取指定的配件设备:

  1. 获取已连接的配件(通过清单文件结合IntentFilter获取):

    • Android 2.3.4 版本:

      UsbAccessory accessory = UsbManager.getAccessory(intent);
      
    • Android 3.1 版本:

      UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
      
  2. 配置 AndroidManifest.xml

    • Android 2.3.4 版本:

      • 添加 <uses-feature> 元素来声明您的应用使用 com.android.future.usb.accessory 功能

        <uses-feature android:name="com.android.future.usb.accessory" android:required="true"/>
        <uses-sdk android:minSdkVersion="10" />
        
    • Android 3.1 版本:

      • 添加 <uses-feature> 元素来声明您的应用使用 android.hardware.usb.accessory 功能

        <uses-feature android:name="android.hardware.usb.accessory" android:required="true"/>
        <uses-sdk android:minSdkVersion="12" />
        
  3. 如果需要动态监听 USB 配件的连接或断开,需要在 Activity 的标签中制定 <intent-filter><meta-data> 元素。其中,<meta-data> 元素指向一个外部 XML 资源文件,该文件位于 res/xml/ 路径下,声明了关于要检测的配件的识别信息。该 XML 资源文件中,为要过滤的配件声明 <usb-accessory> 元素。每个 <usb-accessory> 可以包含属性:manufacturer、model 、version

    • activity 标签配置示例:

      <application>
      	<uses-library android:name="com.android.future.usb.accessory" />
      	<activity ...>
      		...
      		<intent-filter>
          		<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
      		</intent-filter>
      
      		<meta-data 
      			android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
          		android:resource="@xml/accessory_filter" />
      	</activity>
      </application>
      
    • 型号、制造商、版本信息的 xml 文件如:

      <?xml version="1.0" encoding="utf-8"?>
      <resources>
      	<usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
      </resources>
      

获取设备列表:

  • 获取已连接的所有 USB 配件的列表

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    UsbAccessory[] accessoryList = manager.getAccessoryList();
    

AM通信开发指南:

  1. 发现配件:

    • 有两种方式发现配件:使用在用户连接配件时会收到通知的 Intent 过滤器,或者获取已连接的配件列表。

    • 如果需要应用能够自动检测所需的配件,那么使用 Intent 过滤器会很便捷。

    • 如果需要获得所有已连接配件的列表,或者您的应用没有 Intent 过滤器,那么枚举已连接的配件很便捷。

  2. 使用配件:当用户将 USB 配件连接到 Android 设备时,Android 系统可以确定您的应用是否对所连接的配件感兴趣。如果感兴趣,您可以根据需要设置与该配件的通信。为此,您的应用必须执行以下操作:

    • 发现连接的配件,方法是使用 Intent 过滤器来过滤配件连接事件,或者枚举所连接的配件并找到适合的配件。

    • 请求用户授予与配件通信的权限(如果尚未获得该权限)。

    • 通过在适当的接口端点读取和写入数据来与配件通信。

  3. 获取与配件通信的权限:

    • 要明确获取权限,请先创建一个广播接收器。此接收器监听在您调用 requestPermission() 时接收广播的 Intent。调用 requestPermission() 会向用户显示一个对话框,请求授予连接到配件的权限。以下示例代码展示了如何创建广播接收器:

      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) {
      				UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
      				if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
      					if(accessory != null) {
      						// call method to set up accessory communication
      					}
      				} else {
      					Log.d(TAG, "permission denied for accessory " + accessory);
      				}
      			}
      		}
      	}
      };
      
    • 注册广播接收器,请将以下代码添加到您的 Activity 中的 onCreate() 方法中:

      UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
      permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
      IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
      registerReceiver(usbReceiver, filter);
      
    • 要显示对话框来向用户请求连接到配件的权限,请调用 requestPermission() 方法:

      UsbAccessory accessory;
      if (BUILD.VERSION.SDK_INIT >= 10 && BUILD.VERSION.SDK_INIT < 12) {
      	accessory = UsbManager.getAccessory(intent);
      } else {
      	accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
      }
      usbManager.requestPermission(accessory, permissionIntent);
      
    • 当用户在该对话框中作出回复时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED extraIntent,该 extra 是代表用户回复的布尔值。在连接到配件之前,请检查此 extra 的值是否为 true

  4. 与配件进行通信:

    • 使用 UsbManager 来获取文件描述符,然后设置输入和输出流来向描述符读写数据,以此与配件通信。这些流表示配件的输入和输出批量端点。您应该在另一个线程中设置设备和配件之间的通信,这样就不会锁定主界面线程。在线程的 run() 方法中,您可以使用 FileInputStreamFileOutputStream 对象来读写配件。

    • 使用 FileInputStream 对象从配件读取数据时,请确保您使用的缓冲区足以存储 USB 数据包数据。Android 配件协议支持最大 16384 字节的数据包缓冲区,因此为了简单起见,您可以选择始终将缓冲区声明为此大小。

    • 在较低的 API 级别,USB 全速配件的数据包为 64 字节,USB 高速配件的数据包为 512 字节。为简单起见,Android 配件协议将全速和高速数据包打包到一个逻辑数据包中。

    • 以下示例展示了如何打开配件以与之通信:

      UsbAccessory accessory;
      ParcelFileDescriptor fileDescriptor;
      FileInputStream inputStream;
      FileOutputStream outputStream;
      ...
      
      private void openAccessory() {
          Log.d(TAG, "openAccessory: " + accessory);
          fileDescriptor = usbManager.openAccessory(accessory);
          if (fileDescriptor != null) {
              FileDescriptor fd = fileDescriptor.getFileDescriptor();
              inputStream = new FileInputStream(fd);
              outputStream = new FileOutputStream(fd);
              Thread thread = new Thread(null, new Runnable() {
              		@override
              		public void run() {
              			// TODO
              		}
              }, "AccessoryThread");
              thread.start();
          }
      }
      
  5. 终止与配件通信:

    • 当完成与配件的通信后或者配件断开连接后,请调用 close() 来关闭您打开的文件描述符。

    • 在应用内(而不是清单中)创建广播接收器,可让应用仅在运行时才处理断开连接事件。这样,断开连接事件只会发送到当前正在运行的应用,而不会广播到所有应用。

    • 要监听断开连接事件,请创建如下所示的广播接收器:

      BroadcastReceiver usbReceiver = new BroadcastReceiver() {
      	public void onReceive(Context context, Intent intent) {
          	String action = intent.getAction();
          	if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
              	UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
              	if (accessory != null) {
                  	// call your method that cleans up and closes communication with the accessory
              	}
          	}
      	}
      };
      

USB HM介绍:

  • 当您的 Android 设备处于 USB 主机模式时,它会充当 USB 主机,为总线供电并枚举连接的 USB 设备。Android 3.1 及更高版本支持 USB 主机模式。

  • 在开始前,请务必了解您需要使用的类。下表介绍了 android.hardware.usb 软件包中的 USB 主机 API

    说明
    UsbManager可以枚举连接的 USB 设备并与之通信。
    UsbDevice表示连接的 USB 设备,并包含用于访问其标识信息、接口和端点的方法。
    UsbInterface表示接口端点,是此接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点。
    UsbDeviceConnection表示与设备的连接,可在端点上传输数据。借助此类,您能够以同步或异步方式反复发送数据。
    UsbRequest表示通过 UsbDeviceConnection 与设备通信的异步请求。
    UsbConstants定义与 Linux 内核的 linux/usb/ch9.h 中的定义相对应的 USB 常量。
  • 在大多数情况下,您需要在与 USB 设备通信时使用所有这些类(只有在进行异步通信时才需要 UsbRequest)。一般来说,您需要获取 UsbManager 才能检索所需的 UsbDevice。当您有了设备后,需要找到相应的 UsbInterface 和该接口的 UsbEndpoint 以进行通信。获得正确的端点后,打开 UsbDeviceConnection 以与 USB 设备通信。


获取指定的 USB 设备:

  1. Activity 中,您可以通过以下方式从 Intent 获取代表所连接设备的 UsbDevice

    UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
    
  2. 配置 AndroidManifest.xml

    • 添加 <uses-feature> 元素来声明您的应用使用 android.hardware.usb.host 功能。

    • 将应用的最低 SDK 设置为 API 级别 12 或更高级别。USB 主机 API 在更早的 API 级别中不存在。

      <uses-feature android:name="android.hardware.usb.host" />
      <uses-sdk android:minSdkVersion="12" />
      
  3. 如果您希望应用接收有关连接的 USB 设备的通知,请为主 Activity 中的 android.hardware.usb.action.USB_DEVICE_ATTACHED Intent 指定 <intent-filter><meta-data> 元素对。<meta-data> 元素指向外部 XML 资源文件,用于声明有关要检测的设备的标识信息。
    XML 资源文件中,为要过滤的 USB 设备声明 <usb-device> 元素。下表介绍了 <usb-device> 的属性。一般来说,如果您想过滤某个特定设备,请使用供应商 ID 和产品 ID;如果您想过滤一组 USB 设备(例如大容量存储设备或数码相机),请使用类、子类和协议。您可以指定所有这些属性,也可以不指定任何属性。如果不指定任何属性,则会与每个 USB 设备进行匹配,因此只在应用需要时才这样做:vendor-id、product-id、class、subclass、protocol(设备或接口)

    • 以下示例展示了示例清单及其相应的资源文件:

      <uses-feature android:name="android.hardware.usb.host" />
      <uses-sdk android:minSdkVersion="12" />
      ...
      <application>
      	<uses-library android:name="com.android.future.usb.accessory" />
      	<activity ...>
      		...
      		<intent-filter>
           		<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
      		</intent-filter>
      
      		<meta-data 
          		android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
          		android:resource="@xml/accessory_filter" />
      	</activity>
      </application>
      
    • 在这种情况下,以下资源文件应保存在 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设备列表:

  • 获取已连接的所有USB设备的列表:

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    // 获取到的是设备名与USB设备的映射
    HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
    Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
    while(deviceIterator.hasNext()) {
    	UsbDevice device = deviceIterator.next();
    	// your code
    }
    

HM通信开发指南:

  1. 发现设备:

    • 应用可以通过使用 Intent 过滤器在用户连接 USB 设备时接收通知,或者枚举已连接的 USB 设备来发现设备。

    • 如果希望应用自动检测所需的设备,那么使用 Intent 过滤器就非常有用。

    • 如果想获取所有连接的设备的列表,或者您的应用没有 Intent 过滤器,那么枚举连接的 USB 设备就非常有用。

  2. 使用设备:

    • 当用户将 USB 设备连接到 Android 设备时,Android 系统可以确定您的应用是否对连接的设备感兴趣。如果是,您可以根据需要设置与该设备的通信。为此,您的应用必须执行以下操作:
      • 发现连接的 USB 设备,具体方法是使用 Intent 过滤器在用户连接 USB 设备时接收通知,或者枚举已连接的 USB 设备。

      • 请求用户授予连接到 USB 设备的权限(如果尚未获得权限)。

      • 通过在适当的接口端点读取和写入数据来与 USB 设备通信。

  3. 获取与USB设备通信的权限:

    • 如果是通过清单获取固定的USB设备,可在 Activity 中,通过以下方式从 Intent 获取代表所连接设备的 UsbDevice

      UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
      
    • 要明确获取权限,请先创建广播接收器。此接收器监听在您调用 requestPermission() 时接收广播的 Intent。调用 requestPermission() 会向用户显示一个对话框,请求连接到设备的权限。以下示例代码展示了如何创建广播接收器:

      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);
      				}
      			}
      		}
      	}
      };
      
    • 要注册广播接收器,请在您的 ActivityonCreate() 方法中添加以下命令:

      private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
      
      UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
      permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
      IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
      registerReceiver(usbReceiver, filter);
      
    • 要显示对话框以向用户请求连接到设备的权限,请调用 requestPermission() 方法,当用户在该对话框中作出回复时,您的广播接收器会收到包含 EXTRA_PERMISSION_GRANTED extraIntent,即表示回答的布尔值。在连接到设备之前,请检查此 extra 的值是否为 true

      void requestDevicePermission(UsbDevice device) {
      	usbManager.requestPermission(device, permissionIntent);
      }
      
  4. 与设备通信:

    • USB 设备的通信可以是同步的,也可以是异步的。在这两种情况下,您都应该创建一个新线程来执行所有数据传输,这样就不会锁定界面线程。

    • 要正确设置与设备的通信,您需要获取要与之通信的设备的相应 UsbInterfaceUsbEndpoint,并使用 UsbDeviceConnection 在此端点发送请求。通常,您的代码应该执行以下操作:

      • 检查 UsbDevice 对象的属性(例如产品 ID、供应商 ID 或设备类别),判断您是否要与设备通信。

      • 在您确定要与设备通信时,找到您要用于通信的适当 UsbInterface 以及该接口的适当 UsbEndpoint。接口可以具有一个或多个端点,并且通常具有用于双向通信的输入和输出端点。

      • 找到正确的端点后,在该端点上打开 UsbDeviceConnection

      • 使用 bulkTransfer()controlTransfer() 方法提供要在端点上传输的数据。您应该在另一个线程中执行此步骤,以防止主界面线程被锁定。如需详细了解如何在 Android 中使用线程,请参阅进程和线程。

    • 以下代码段是执行同步数据传输的一种简单方式。您的代码应具有更多逻辑,以便正确地找到要进行通信的接口和端点,并且还应在与主界面线程不同的另一个线程中进行数据传输:

      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
      
  5. 终止通信:

    • 当完成与设备的通信或者设备断开连接后,请调用 releaseInterface()close() 来关闭 UsbInterfaceUsbDeviceConnection。要监听断开连接事件,请创建如下所示的广播接收器:
    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
    			}
    		}
    	}
    };
    
    • 在应用内(而不是清单中)创建广播接收器后,您的应用可以仅在运行时处理断开连接的事件。这样,断开连接事件只会发送到当前正在运行的应用,而不会广播到所有应用。

HostMode的访问流:

  1. 获取USB硬件访问权限;
  2. https://www.jianshu.com/p/e2e57cddac6a
  3. https://blog.csdn.net/shareus/article/details/50375204
  4. https://blog.csdn.net/zhoumushui/article/details/101304296
  5. https://blog.csdn.net/zhongshujunqia/article/details/53212548?locationNum=5&fps=1–
  6. https://www.cnblogs.com/AmyHu/p/10654500.html
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值