【Android】Android之USB
Android, USB, Accessory, Host
【转载请注明出处】
首先介绍一个概念:USB Host and Accessory
Android通过两种模式支持一系列的USB外围设备和Android USB附件(实现了Android附件协议的硬件设备):USB从设备模式和USB主设备模式,在USB从设备模式下,外围的USB硬件设备作为USB主设备,从设备模式的例子包括扩展坞,读卡器等等。这使得Android设备不具备和USB硬件主动交流的能力,Android USB附件必须遵循Android附件交流协议。在USB主设备模式下,Android设备是作为主设备的,例子包括游戏控制器,数码相机等等。
下面两幅图展示了两种模式的区别。的那个Android设备处于主设备模式的时候,它给USB提供动力,当它是从设备模式的时候,连接的USB硬件设备作为主设备,给USB提供动力。
USB主设备和从设备模式在Android3.1(API 12)或者更新的平台上被直接支持,也可以在Android2.3.4(API 10)上被支持,需要一个add-on库,设备制造商可以选择是否在系统镜像中包含该库。(备注:对USB的主/从设备的支持完全独立于硬件,而且不限于API Level。开发者可以使用<uses-feature>元素来过滤支持USB主/从设备模式的设备)。
HSB Host
主设备模式下,你可以主动的和对方设备进行交互。涉及到的类包括:UsbManager,UsbDevice,UsbInterface,UsbEndpoint,UsbDeviceConnection,UsbRequest,UsbConstants.一般情况下你需要用到全部这些类(UsbRequest只在你需要进行异步通信的时候使用),你需要一个UsbManager对象去检索需要的UsbDevice对象,当你获得这个设备之后,就需要找到合适UsbInterface和它的UsbEndpoint进行交互,一旦获得了正确的endpoint,就可以打开一个UsbDeviceConnection与USB设备进行交互了。
Android Manifest Requirements
下面列举了在使用USB host API之前需要对应用的manifest文件作出的改变:
1)因为不是所有的Android设备都保证支持USB host API,所以应用中需要包含<uses-featrue>元素来声明,你的应用需要用到android.hardware.usb.host特征;
2)将SDK的最小值设置为12或者更高,USB host API在更早的版本上不被支持;
3)如果你想你的应用在有USB设备连接上去的时候得到通知,需要声明<intent-filter>和<meta-data>元素对(详情见后面),<meta-data>指向一个外部的xml资源文件,该文件描述了应用关心的设备的身份信息。在XML资源文件中,使用<usb-device>元素来过滤关心的USB设备,接下去的列表则描述了设备的属性,包括vendor-id,product-id,class,subclass,protocol。如果没有列出属性,则表示对任何设备都感兴趣。资源文件放在res/xml/文件夹下面,文件的名字必须和<meta-data>元素中声明的一样。例子:
1 <manifest ...> 2 <uses-feature android:name="android.hardware.usb.host" /> 3 <uses-sdk android:minSdkVersion="12" /> 4 ... 5 <application> 6 <activity ...> 7 ... 8 <intent-filter> 9 <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> 10 </intent-filter> 11 12 <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" 13 android:resource="@xml/device_filter" /> 14 </activity> 15 </application> 16 </manifest>
View Code
接下来,我们在res/xml/文件夹下面新建一个device_filter.xml
文件,内容如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <resources> 4 <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" /> 5 </resources>
View Code
和设备交互的步骤如下:
1)发现设备
应用可以通过intent filter发现设备或者主动列举已经连接的设备来发现设备。如果你想应用自动发现想要的设备,使用Intent-filter是非常有效的。如果你的设备没有过滤器,而且想获得连接设备的列表,列举USB设备就会变得非常有用。
使用Intent filter的manifest文件的改变如上代码已经列出,我们写明了一个Intent filter,用于过滤得到android.hardware.usb.action.USB_DEVICE_ATTACHED
intent,然后写明xml文件,当连接的设备符合你的filter之后,系统会弹出一个对话框,询问是否打开你的应用,如果用户接受,你的应用就会自动获取访问设备的的权限,直到设备断开连接。
这个时候我们就在Activity中,使用下面的代码获取Intent代表的设备:
1 UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
2)列举设备
如果你的应用在运行的时候对所有连接的设备都感兴趣,它可以列举在连接在总线上的所有设备,使用getDeviceList()方法就可以得到一个所有连接的设备的hash map列表。hashmap的key是设备的名字。代码如下:
1 UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); 2 ... 3 HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); 4 UsbDevice device = deviceList.get("deviceName");
View Code
你也可以使用迭代器遍历所有的设备:
1 UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); 2 ... 3 HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); 4 Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); 5 while(deviceIterator.hasNext()){ 6 UsbDevice device = deviceIterator.next() 7 //your code 8 }
View Code
3)获取和设备交互的权限
在进行交互之前,应用必须获得用户的许可,也就是必须需要用户同意才可以和设备交互。(备注“如果你的应用使用intent-filter来发现设备,当用户允许应用处理Intent的时候就自动获取了权限,如果不是,则需要显示的向用户发出请求)
有些时候,显示的请求是必须的,假如不获取权限就进行交互,就会收到一个runtime error。为了显示的获取权限,首先需要创建一个广播接收器,接收器接收你调用requestPermission()方法产生的Intent,该方法调用会产生一个对话框询问连接到该设备的权限,下面是示例代码:
1 private static final String ACTION_USB_PERMISSION = 2 "com.android.example.USB_PERMISSION"; 3 private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { 4 5 public void onReceive(Context context, Intent intent) { 6 String action = intent.getAction(); 7 if (ACTION_USB_PERMISSION.equals(action)) { 8 synchronized (this) { 9 UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 10 11 if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { 12 if(device != null){ 13 //call method to set up device communication 14 } 15 } 16 else { 17 Log.d(TAG, "permission denied for device " + device); 18 } 19 } 20 } 21 } 22 };
View Code
然后在onCreate()方法里面注册监听器:
1 UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 2 private static final String ACTION_USB_PERMISSION = 3 "com.android.example.USB_PERMISSION"; 4 ... 5 mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); 6 IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); 7 registerReceiver(mUsbReceiver, filter);
View Code
只需要下面的步骤就可以弹出对话框:
1 UsbDevice device; 2 ... 3 mUsbManager.requestPermission(device, mPermissionIntent);
View Code
当用户做出选择之后,就会产生一个广播,接收到之后就可以做出相应的判断。
4)和设备交互
和USB设备的交流既可以是同步的也可以是异步的,不管是哪种,都需要创建一个新的线程来执行数据传输以避免阻塞UI线程。为了和设备正确交互,我们需要获取设备正确的UsbInterface和UsbEndpoint,然后在该endpoint使用正确的UsbDeviceConnection发送请求。使用bulkTransfer()或者controlTransfer()方法传送数据。下面的代码是一段同步数据传输的例子,实际的代码需要更加严密的逻辑:
1 private Byte[] bytes 2 private static int TIMEOUT = 0; 3 private boolean forceClaim = true; 4 5 ... 6 7 UsbInterface intf = device.getInterface(0); 8 UsbEndpoint endpoint = intf.getEndpoint(0); 9 UsbDeviceConnection connection = mUsbManager.openDevice(device); 10 connection.claimInterface(intf, forceClaim); 11 connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
View Code
如果想要异步的发送数据,就使用UsbRequest类来初始化和队列话异步请求,然后可以使用requestWait()来等待传输结果。
5)终止交互
当应用完成了交互或者设备被移除了,就需要通过调用releaseInterface()和close()关闭UsbInterface和UsbDeviceConnection,为了监听移除时间,需要使用以下监听器:
1 BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { 2 public void onReceive(Context context, Intent intent) { 3 String action = intent.getAction(); 4 5 if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { 6 UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); 7 if (device != null) { 8 // call your method that cleans up and closes communication with the device 9 } 10 } 11 } 12 };
View Code
在应用中创建监听器,而不要在manifest中,这样就可以使得只有在程序运行的时候监听到该事件,就可以让移除时间不会被发送给所有的应用程序而只是运行中的程序。
HSB Accessory
USB Accessory允许用连接专门为Android设备设计的USB主设备硬件。这些硬件必须遵循相应的协议。这允许不能作为USB主设备的Android设备也能和USB硬件交互,当一个设备处于USB从设备模式的时候,连接上来的硬件就是主设备,硬件就会给USB提供电能,并且列举连接的设备。
Choosing the Right YSB Accessory APIs
尽管USB Accessory API是在Android 3.1被引入的,它在Android 2.3.4上面也可以通过附加库使用,因为是通过外部库,总共有两个库可以被导入支持USB从设备模式,使用哪一个库决定于你支持的设备:
1)com.android.future.usb:为了在Android2.3.4上面支持USB从设备模式, Google APIs add-on library包含了这些API,并且就存在于该名称空间下面。Android3.1也支持导入和使用该名称空间中的类。这个库是android.hardware.usb中接口的一层薄包裹,并且不支持USB主设备模式,如果你想最大范围的支持USB从属设备模式,使用附加库,导入该包!值得注意的是并非所有的Android2.3.4设备都被要求支持USB accessory特征。每一个设备生产商自行决定是否要支持该功能,所有你要在你的manifest文件声明(是否需要使用该特征);
2)android.hardware.usb:该名称空间包括了Android3.1下面支持USB从属设备模式的所有类,它是Framework API的一部分,因而Androuid3.1并不需要附加库来实现从属设备模式,如果你只关心Android3.1或者更新的设备,你可以使用该库。
API Overview
有两个类涉及到Android从设备模式,一个是UsbManager,另外一个是UsbAccessory。因为附加库是framework API的一个包裹,所以接口基本一直,你可以看着android.hardware.usb库的文档使用附加库。但是略有不同:
如果你使用附加库,则可以使用以下代码获取UsbManager:
1 UsbManager manager = UsbManager.getInstance(this);
View Code
否则使用如下代码:
1 UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
View Code
使用Intent filter捕捉设备连接事件的时候,如果你是使用的附加库,则使用以下语句获取UsbAccessory:
1 UsbAccessory accessory = UsbManager.getAccessory(intent);
View Code
否则使用如下代码:
1 UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
View Code
Android Manifest requirements
下面列举了在使用USB accessory API之前需要对应用的manifest文件作出的改变:
1)因为不是所有的设备都保证支持USB accessory APIs,所以必须在文件中包含<uses-feature>元素,声明应用需要使用android.hardware.usb.accessory特征;
2)如果你是在使用附加库,则添加<uses-library>元素明确需要使用android.hardware.usb包;
3)如果你在使用附加库,将SDK的最小值设置为10,如果在使用android.hardware.usb包,则设置为12;
4)如果你想你的应用在有USB设备连接上去的时候得到通知,需要在主Activity中声明<intent-filter>和<meta-data>元素对(详情见后面),<meta-data>指向一个外部的xml资源文件,该文件描述了应用关心的设备的身份信息。在XML资源文件中,使用<usb-accessory>元素来过滤关心的USB设备,每个<usb-accessory>包含下列属性,包括manufactures,model,version。如果没有列出属性,则表示对任何设备都感兴趣。资源文件放在res/xml/文件夹下面,文件的名字必须和<meta-data>元素中声明的一样。例子:
1 <manifest ...> 2 <uses-feature android:name="android.hardware.usb.accessory" /> 3 4 <uses-sdk android:minSdkVersion="<version>" /> 5 ... 6 <application> 7 <uses-library android:name="com.android.future.usb.accessory" /> 8 <activity ...> 9 ... 10 <intent-filter> 11 <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" /> 12 </intent-filter> 13 14 <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" 15 android:resource="@xml/accessory_filter" /> 16 </activity> 17 </application> 18 </manifest>
View Code
接下来,我们在res/xml/文件夹下面新建一个accessory_filter.xml
文件,内容如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <resources> 4 <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/> 5 </resources>
View Code
和设备交互的步骤如下:
1)发现设备
这里大致和主设备模式一样,代码前面都有,intent-filter获得的时候,要注意区分是不是使用的附加库。
2)列举设备
当你的程序正在运行的时候,可以列举确认了它们作为附件的设备,代码如下:
1 UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE); 2 UsbAccessory[] accessoryList = manager.getAcccessoryList();
View Code
备注:目前,Android设备一次只能作为一个硬件设备的附件,API如此设计是为了未来的发展。
3)获取和设备交互的权限
这里基本上和从设别一直,只是用户确认以后,是使用:
1 UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
View Code
语句获取设备的句柄的。其余一致。
4)与设备交互
应用可以使用UsbManager来获得附件的一个文件描述符,然后使用建立一个输入输出流来向文件描述符读写数据。这些流代表着附件的输入输出endpoints,你同样应该为此新建一个线程。例子:
1 UsbAccessory mAccessory; 2 ParcelFileDescriptor mFileDescriptor; 3 FileInputStream mInputStream; 4 FileOutputStream mOutputStream; 5 6 ... 7 8 private void openAccessory() { 9 Log.d(TAG, "openAccessory: " + accessory); 10 mFileDescriptor = mUsbManager.openAccessory(mAccessory); 11 if (mFileDescriptor != null) { 12 FileDescriptor fd = mFileDescriptor.getFileDescriptor(); 13 mInputStream = new FileInputStream(fd); 14 mOutputStream = new FileOutputStream(fd); 15 Thread thread = new Thread(null, this, "AccessoryThread"); 16 thread.start(); 17 } 18 }
View Code
在线程的run()方法中你可以使用FileInputStream和FileOutputStream对象来读写附件,当你从设备上使用FileInputStream对象读取数据的时候,要确保你的缓冲区足够大,可以存储USB包数据,Android附件协议支持包的大小上限是16384bytes,所以你可以选择简单的就声明成这个大小。
5)终止交互
当应用完成了交互或者设备被移除了,就需要通过调用rclose()关闭文件描述符,为了监听移除时间,需要使用以下监听器:
1 BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { 2 public void onReceive(Context context, Intent intent) { 3 String action = intent.getAction(); 4 5 if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) { 6 UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 7 if (accessory != null) { 8 // call your method that cleans up and closes communication with the accessory 9 } 10 } 11 } 12 };
View Code
在应用中创建监听器,而不要在manifest中,这样就可以使得只有在程序运行的时候监听到该事件,就可以让移除时间不会被发送给所有的应用程序而只是运行中的程序。
以上就是有关Android USB的相关知识了,具体的开发需要参考SDK中的例子,以及相应的开发文档。