美文网首页Android FrameworkAndroid基础知识
Android USB Host接入(USB OTG)

Android USB Host接入(USB OTG)

作者: gstansen | 来源:发表于2017-04-01 16:45 被阅读6017次

    接入前先了解一下USB OTG的概念

    USB OTG:USB On-The-Go通常缩写为USB OTG,是USB2.0规格的补充标准。它可使USB设备,例如播放器或手机,从USB周边设备变为USB主机,与其他USB设备连接通信。在正常情况下,这些支持OTG的USB设备和USB主机(如台式机或者手提电脑),仍然作为USB周边设备使用。

    这段话是wiki对USB OTG的说明。

    举一个简单的例子:如果你的安卓机可以通过一根OTG线直接读取U盘里的内容(有些U盘直接支持OTG输出不需要OTG线),这就是OTG功能了,它能允许你的手机像电脑一样直接读取U盘里的内容,现在的手机一般都支持OTG功能。

    OTG线长这个样子,公头插进手机,母头插入U盘等外设:

    otg.jpg

    支持OTG的U盘长这个样子(电脑手机双用>.<):

    otgU盘.jpg

    有些文件管理器(ES文件浏览器等)直接支持OTG挂载功能,也就是将读取U盘功能完成了。

    想要接入OTG功能还需要了解USB Host and Accessory的概念。

    USB Host and Accessory

    Android官网的介绍如下:
    Android系统可以通过USB配件模式和USB主机模式支持各种各样的USB外设和Android USB配件(这些硬件需要先实现USB配件协议)。

    在USB配件模式下,外部的USB硬件就相当于USB的主机端了,Android设备就是配件端。像机器人控制器,扩展坞,音乐设备,读卡器等这些配件都可以作为主机端。USB配件模式就给了不支持USB主机模式的Android设备和其它USB硬件交互的能力。Android USB配件必须支持Android配件交流协议,并且它们在被设计的时候就必须能够和Android设备协同工作。

    在USB主机模式下,Android设备就成为了主机端,像数码相机,键盘鼠标和游戏手柄等就是附属设备了。那些为了各种应用和不同场景而设计的USB设备依然可以和那些能和正常设备沟通的Android应用交互。

    图1是这两种模式的区别。


    figure 1.png

    这两种模式在Android3.1(API 12)及以上都是直接支持的,USB配件模式在Android2.3.4(API 10)上作为一个支持库也可以使用。设备制造商可以选择是否将这个库添加进他们的系统镜像中。

    USB Host接入

    Android Manifest 要求

    • 添加<uses-feature> 标签申明应用将使用 android.hardware.usb.host特性

    • 将minimum SDK设置为API 12及以上

    • 添加<intent-filter>和<meta-data> 以便USB设备插入时可以捕获你想要识别的设备并获取提示。
      将<intent-filter>中的action设为android.hardware.usb.action.USB_DEVICE_ATTACHED时,USB设备插入系统底部就会弹出一个提示框里边有声明了这个action的所有应用(这里就是一个隐式的intent),里边就有自己的应用了。
      <meta-data>会指向一个外部的xml文件,在这个xml资源文件里我们可以指定我们想要捕获的设备因为并不是所有设备我们都想检测到。我们可以通过在xml中声明vendor-id,product-id,class,subclass,protocol字段来过滤设备,如果不声明的话则会默认检测到所有接入的USB设备。

    举个xml(device_filter.xml)的例子:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
    </resources>
    

    Manifest 的例子

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="me.onionpie.otgexplorer">
        <uses-feature android:name="android.hardware.usb.host"/>
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
    
                    <category android:name="android.intent.category.LAUNCHER" />
    
                    <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                        android:resource="@xml/device_filter" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    

    申请权限

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

    使用广播接收权限申请结果和设备插入拔出的的消息

    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action.equals(ACTION_USB_PERMISSION)) {
                    synchronized (this) {
                        UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                            if (usbDevice != null) {
                                mInfo = "";
                                collectDeviceInfo(usbDevice);
                                mInfoTextView.setText(String.format("权限获取成功,设备信息:%s", mInfo));
                                //读取usbDevice里的内容
                            }
                        } else {
                            mInfoTextView.setText("权限被拒绝了");
                            Log.v(LOGTAG, "permission is denied");
                        }
                    }
                } else if (action.equals(UsbManager.ACTION_USB_ACCESSORY_DETACHED)) {
                    UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (usbDevice != null) {
                        //close connection
                        mInfoTextView.setText("与设备断开连接");
                    }
                }else if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)){
                    //当设备插入时执行具体操作
                    Log.v(LOGTAG,"设备接入");
                    mInfoTextView.setText("设备接入");
                }
            }
        };
    

    获取设备

    列举出插入的设备

    UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
    ...
    HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
    Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
    while(deviceIterator.hasNext()){
        UsbDevice device = deviceIterator.next();
        //your code
    }
    

    读取设备具体的数据(这里比较繁琐可以使用这个库https://github.com/magnusja/libaums

    private Byte[] bytes;
    private static int TIMEOUT = 0;
    private boolean forceClaim = true;
    ...
    
    UsbInterface intf = device.getInterface(0);
    UsbEndpoint endpoint = intf.getEndpoint(0);
    UsbDeviceConnection connection = mUsbManager.openDevice(device);
    connection.claimInterface(intf, forceClaim);
    connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread(异步处理)
    

    在结束读取时或者设备拔出时记得调用releaseInterface()和close()关闭UsbInterface和UsbConnection,可以通过广播监听设备拔出,前面已经贴了代码。

    USB Host大致的流程就是这样,Usb Accessory的接入下次有机会再记录一下,自己写了个简单的demo(https://github.com/stansen/OTGDemo) ,可能和这里贴的代码不一样因为这里的代码是Android官网的,具体读取数据的内容并没有写,如果有需要可以使用这个库https://github.com/magnusja/libaums

    这里有个开源的文件浏览器:https://github.com/1hakr/AnExplorer ,里边集成了USB Host功能,可以去看看,它也是使用了libaums这个库的。

    参考:
    https://developer.android.com/reference/android/hardware/usb/UsbDeviceConnection.html
    https://developer.android.com/guide/topics/connectivity/usb/index.html

    上图:

    DSC_1037.jpg

    相关文章

      网友评论

      • 0bf077bb532c:楼主、就是我们是内网、没有wifi、蓝牙,,最好只能通过数据线,跟内网的服务器进行交互,需求跟用OTG是类似的嘛
      • 策马鞭程:请问能否判断设备是否支持OTG?
        妙法莲花1234:可以这里下载: https://apkpure.com/cn/otg/com.btssm.doihaveotg
        妙法莲花1234:有个软件叫做 OTG? 可以搜索下载
        gstansen:The official way to determine if the device has USB host capability is to use the associated system feature.

        Ideally, add a <uses-feature> element to your manifest, indicating that you are interested in the android.hardware.usb.host feature.

        OR use PackageManager, hasSystemFeature(), and FEATURE_USB_HOST. FEATURE_USB_HOST is defined as the same string as you would be using in <uses-feature> (android.hardware.usb.host).

        in code words :

        context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_USB_HOST);
      • StoneLeon:android 通过otg连接单反相机获取照片 这个楼主做过么?
        gstansen: @我是七十七 我记得UsbHostManager这个类里有个getdevicelist函数可以获取连接的设备的,这样不就可以知道usb的状态了
        我是七十七:你好 attach和dettach能在插入和拔出的时候做出响应 如果是进入 app的时候就判断usb state有可能嘛
        gstansen:没有自己写过呢,如果你有这个需求的话建议你去看一下https://github.com/1hakr/AnExplorer这个项目,它实现otg连接单反获取照片,当时测试是在nikon的机子上,而且android上otg好像只能读fats格式的u盘,ntfs格式的是读不了的

      本文标题:Android USB Host接入(USB OTG)

      本文链接:https://www.haomeiwen.com/subject/vieuottx.html