USB从属模式(Accessory)

USB的从属模式允许用户连接到专门给Android设备设计的USB主机硬件。这些附件必须遵循在Android从属模式开发工具文档中描述的Android从属模式协议。这个协议允许Android设备即使不作为USB主机依然能够跟USB硬件进行交互。当Android设备处于USB从属模式时,被连接的Android USB附件会作为主机,提供用于供电的USB总线,并列举被连接的设备。Android3.1(API Level 12)开始支持USB从属模式,并且这个功能也能够被移植到Android2.3.4(API Level 10)中,以便能够支持更广泛的设备。

选择正确的USB从属模式API

尽管USB从属模式API是在Android3.1中被引入的,但是它们也可以作为附加类库在Android2.3.4中使用。因为这些API是使用一个外部类库来移植的,所以要导入两个包才能支持USB从属模式。根据你想要支持的Android设备,你要使用以下其中一种类库:

  • com.android.future.usb

    要在Android2.3.4中支持USB从属模式,就要使用包含USB从属模式的Google API的附加类库,这些类库被包含在这个命名空间中。Android3.1也支持对这个命名空间的类的引入和调用,以便支持用这个附加类来编写的应用程序。这个附加类库是围绕android.hardware.usb从属模式API的一个简单封装,并且不支持USB主机模式。如果你希望设备能够最大范围的支持USB从属模式,就要使用这个附加类库,并要导入这个包。重点要注意的是,不是所有的Android2.3.4设备都需要支持USB从属功能。每个独立的设备制造商会决定是否支持这种能力,这就是为什么要在你的清单文件中声明的原因。

  • android.hardware.usb

    这个命名空间包含了Android3.1中所支持的USB从属模式的类。这个包被包含在框架API中,因此Android3.1不使用附加类库来支持USB从属模式。如果你只关注Android3.1或更新的支持USB从属模式的硬件设备,那么就可以在你的清单文件中声明使用这个包。

安装Google API附加类库

如果你要安装这个附加类库,使用SDK管理器,通过安装Google APIs Android API 10来完成。

API概要

因为该附加的类库是一个针对框架API的封装,所以支持USB从属模式的类都是类似的。即使你正在使用这个附加类库,你也能够使用Android.hardware.usb的参考文档

注意:但是,在附加类库和框架API之间有一点使用上的差异,你应该注意。

下表介绍了支持USB从属模式API的类:

介绍
UsbManager 允许你列举被连接的USB附件,并跟它们通信。
UsbAccessory 代表一个USB附件,并包含了访问该附件标识信息的方法。

Add-on类库和平台API之间在使用上的差异

在使用Google APIs add-on类库和平台API之间有两种使用上的差异。

如果使用add-on类库,必须用下列方式获取UsbManager对象:

UsbManager manager = UsbManager.getInstance(this);

否则,必须用下列方式获取UsbManager对象:

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

在使用Intent过滤器过滤一个被连接的附件时,在传递给你的应用程序的Intent对象内部包含了UsbAccessory对象。如果你正在使用add-on类库,就必须使用以下方式来获取UsbAccessory对象:

UsbAccessory accessory = UsbManager.getAccessory(intent);

否则,必须使用以下方式来获取UsbAccessory对象:

UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

Android清单要求

下面列出了在使用USB从属模式API工作之前,需要添加到你的应用程序清单文件中的内容:

  • 因为不是所有的Android设备都保证支持USB从属模式API,所以要在元素中声明使用android.hardware.usb.accessory功能;

  • 如果你是使用add-on类库,还要添加元素来指定要使用的com.android.future.usb.accessory类库;

  • 如果你是使用add-on类库,则要设置该应用程序的SDK的最小版本号是API Level 10,如果使用的是android.hardware.usb包,则最小版本号是API Level 12.

  • 如果你希望获得连接USB附件的通知,就要在你的主Activity中给和元素指定android.hardware.usb.action.USB_ACCESSORY_ATTACHED类型的Intent。元素要指向一个外部的XML资源文件,该文件中声明了有关你想要检测的附件的标识信息。

在这个XML资源文件中,用元素来声明你想要过滤的附件。每个<usb-accessory>元素能够有以下属性:

  • manufacturer

  • model

  • version

该资源文件保存在res/xml目录中。资源文件名称(不含.xml扩展名)必须跟元素中指定的名称相同。下例显示该XML资源文件的格式:

清单和资源文件的示例

<manifest ...>
    <uses-feature android:name="android.hardware.usb.accessory" />

    <uses-sdk android:minSdkVersion="<version>" />
    ...
    <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>
</manifest>

在这种情况下,以下资源文件应该被保存在res/xml/accessory_filter.xml文件中,并且指定了要过滤的附件所对应的模式、制造商和版本号。USB附件会把这些属性发送给Android设备:


<resources>
    <usb-accessory model="DemoKit" manufacturer="Google" version="1.0"/>
</resources>

跟附件一起工作

当用户把USB附件连接到Android设备时,Android系统能够判断你的应用程序是否对接入的附件感兴趣。如果感兴趣,你能够跟期望的附件建立通信。以下是你的应用程序要做的事情:

  • 通过使用过滤附件设备接入事件的Intent过滤器或列举已经接入的附件设备来发现对应的附件设备;

  • 如果不是已知的附件设备,就要询问用户是否允许跟该附件设备通信;

  • 通过读写对应接口端点上的数据来跟附件设备通信。

发现附件设备

你的应用程序既可以通过用户接入附件设备时的Intent通知,也可以通过列举已经接入的附件设备来发现你想要的附件设备。如果你想要你的应用能够自动检测到期望的附件设备,那么使用Intent过滤器是有用的。如果你想要获取所有已连接的附件设备列表,或者你的应用程序并不过滤特定的Intent,那么使用列举的方法是有用的。

使用Intent过滤器

你可以指定一个android.hardware.usb.action.USB_ACCESSORY_ATTACHED类型的Intent过滤器,以便你的应用程序能够发现特殊的USB附件设备。跟这个Intent过滤器一起,你还需要指定一个包含USB附件设备属性的资源文件,这些属性包括:制造商、模式、版本。

下例显示了如何声明一个Intent过滤器:

<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>

下例是对应的资源文件的声明:


<resources>
    <usb-accessory manufacturer="Google, Inc." model="DemoKit" version="1.0" />
</resources>

在你的Activity中,你能够从Intent对象中获得代表接入的附件设备的UsbAccessory对象.

使用add-on类库的情况:

UsbAccessory accessory = UsbManager.getAccessory(intent);

使用平台API的情况:

UsbAccessory accessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);

列举附件设备

在应用程序运行时,你能够让你的应用程序列举出已经识别出的所有附件设备。

使用getAccessoryList()方法来获取所有已连接的USB附件设备:

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

注意:当前,一次只支持连接一个附件设备,但是在未来,该API被设计成要支持多个附件设备。

获得跟附件通信的权限

在跟USB附件设备进行通信之前,你的应用程序必须要从用户那里获得许可。

如果你的应用程序使用Intent过滤器来发现那些被接入的附件设备,而且用户允许你的应用程序处理该Intent对象,那么会自动的接收许可。否则,在连接附件设备之前,你必须明确的申请接入许可。

明确的申请接入许可,在有些情况下是必须的,如在你的应用程序列举出了已经接入的附件设备,并要跟其中之一进行通信时。在试图跟其通信之前,你必须检查访问该附件设备的权限。否则,如果用户拒绝了你访问该附件设备的请求,你会收到一个运行时错误。

要明确的获得许可,首先就要创建一个广播接收器。这个接收器会监听在调用requestPermission()方法时获得的用于广播的Intent对象。调用requestPermission()方法时会显示一个对话框,向用户请求连接附件设备的权限。以下代码显示了如何创建这个广播接收器:

private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
private final BroadcastReceiver mUsbReceiver = 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 mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
...
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);

调用requestPermission()方法来显示申请接入附件设备权限的对话框:

UsbAccessory accessory;
...
mUsbManager.requestPermission(accessory, mPermissionIntent);

当用户应答了该对话框,你的广播接收器会收到一个包含了EXTRA_PERMISSION_GRANTED类型附件的Intent对象,它用一个布尔值来代表回答结果。在连接该附件设备之前,要检查该附件的值是否是true。

跟一个附件设备通信

通过使用UsbManagerd对象获取一个文件描述符来跟附件设备通信,你能够使用这个文件描述符建立读写数据的输入和输出流。该流代表了附件设备的输入和数据块端点。你应该在另外一个线程中建立设备和附件之间的通信,以便不至于阻塞主UI线程。下例显示了如何打开跟附件设备的通信:

UsbAccessory mAccessory;
ParcelFileDescriptor mFileDescriptor;
FileInputStream mInputStream;
FileOutputStream mOutputStream;

...

private void openAccessory() {
    Log.d(TAG, "openAccessory: " + accessory);
    mFileDescriptor = mUsbManager.openAccessory(mAccessory);
    if (mFileDescriptor != null) {
        FileDescriptor fd = mFileDescriptor.getFileDescriptor();
        mInputStream = new FileInputStream(fd);
        mOutputStream = new FileOutputStream(fd);
        Thread thread = new Thread(null, this, "AccessoryThread");
        thread.start();
    }
}

在线程的run()方法中,你可以使用FileInputStream和FileOutputStream对象来读写附件设备。在使用FileInputStream对象从附件设备中读取数据时,要确保有足够大的缓存来存储USB包数据。Android从属模式协议支持的最大包缓存是16384个字节,因此你可以简单的始终把你的缓存声明成这个尺寸。

注意:在底层,对于全速USB附件设备的64字节包和高速USB附件设备的512字节包,Android从属模式协议会简单的把这个两个数据打包成一个逻辑包。

终止跟附件设备的通信

在完成跟附件设备的通信或附件设备被拔出时,你要调用close()方法来关闭被打开的文件描述符。创建以下广播接收器,来监听分离事件:

BroadcastReceiver mUsbReceiver = 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
            }
        }
    }
};

在应用程序中创建的广播接收器,而且不在清单文件中,这样的广播接收器允许你的应用程序只在运行时处理分离事件。这种方式,分离事件只发送给当前正在运行的应用程序,并不会广播给所有的应用程序。

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1