Android USB通信

1:简介:

           自Android3.1(API Level 12)版本开始,Android系统直接支持USB配件(Accessory Mode)和USB主机(Host Mode)两种模式支持各种USB外围设备和Android USB配件(实现Android配件协议的硬件)。

注意:以上关于Android设备支持USB主机模式和从机模式的支持方式只是软支持,另外还须有硬件支持,并且硬件对两种模式的支持具有决定性。

2:Android USB Host Mode(USB 主机模式)

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

Android USB主机模式原理图

在USB主机模式下,我们的Android设备必须支持以下条件:

          1、Android系统版本3.1(API Level 12)及以上。在概述中我们也提到,自Android3.1(API Level 12)开始才提供Android USB Host Mode的支持。

          2、Android设备须支持OTG功能。我们的智能手机和相机等移动设备本身是无法像PC那样直接充当USB Host为总线供电的,而OTG正是为解决此类问题而生,目前主流的Android手机和平板都已添加OTG模块。在主机模式下,我们将Android设备上的USB主机模块又称为USB嵌入式主机(Embedded Host 简称 EH)。EH无法像PC上的USB主机一样,为接入总线的未识别外围设备加载驱动程序,所以EH设备提前在系统中对其目标外围设备列表TPL(Target Peripheral List)进行了定义,在这些外围USB设备中大部分是HID设备(Human Interface Device,如游戏手柄)、BOMS设备(Bulk Only Mass Storage,如读卡器、U盘)和CDC设备(Communication Device Class,USB通信设备类,如打印机、相机),其驱动程序已存在于Android平台的系统中(Linux Kernel),Android设备可以直接与这些设备直接通信。

         3、支持AOA协议。AOA协议(Android Open Accessory Protocol,Android开发配件协议)是Google公司推出的用于实现Android设备与外围设备之间进行USB通信的协议,该协议拓展了Android设备USB接口的功能,为基于Android系统的智能设备应用于设备控制和数据采集领域提供了条件。关于AOA协议的固件源码烧写于硬件中,我们有时也把这项称作为Android设备的硬件支持。(该条件仅在接入Android设备外围设备是另一台Android设备时需要,当接入像U盘或打印机时,可不需要AOA协议的支持)

          4、系统features.xml文件中提供了关于<uses-featureandroid:name="android.hardware.usb.host"/> 的定义。在使用Android USB Host Mode开发时,我们会在应用程序的AndroidMainifast.xml文件中指定<uses-featureandroid:name="android.hardware.usb.host"/> 用来对不支持Android USB Host Mode的设备进行过滤,Android系统版本3.1及以上默认支持,但是不排除设备商会对原生的Android系统进行裁剪,可能会裁剪掉features.xml文件中关于<uses-featureandroid:name="android.hardware.usb.host"/>的定义,也就是说当应用程序安装时来扫描系统的硬件支持feature时,发现没有关于<uses-featureandroid:name="android.hardware.usb.host"/>的定义,认为该设备不支持Android USB Host Mode,也就会提示你无法安装了。

3、Android USB Accessory Mode(USB 从机模式)

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

Android USB从机模式原理图

  在USB 从机模式下,我们的Android设备必须支持以下条件:

         1、Android系统版本2.3.4(API Level 10)及以上。在概述中我们也提到,自Android3.1(API Level 12)开始才直接提供对Android USB Accessory Mode的支持,同时Google提供的配件开发工具包ADK(Accessory Development Kit)提供了Android设备与Android配件通过USB通信的API,该ADK包能够向后兼容至Android2.3.4系统版本。(Android 3.1及以上的系统之所以能够直接支持也是因为系统直接封装了ADK的API)

         2、必须支持AOA协议。在Android USB Accessory Mode中,Android设备必须支持AOA协议,因为当Android设备以从机的方式接入Android配件时,Android配件会通过AOA协议检测并初始化Android设备的USB通信的环境和启动Android设备的USB从机模式。

         3、系统features.xml文件中提供了关于<uses-featureandroid:name="android.hardware.usb.accessory"/> 的定义。关于该条件与Android USB Host Mode中的原因一致,这里不再赘述。

4:Android USB Host Mode 通信开发指南:


4-1:USB设备管理器在不同版本系统中的获取方式不同:

Android 2.3.4版本:UsbManager manager=UsbManager.getInstance(this);
Android 3.1版本:UsbManager manager=(UsbManager)getSystemService(Context.USB_SERVICE);

4-2:配置AndroidManifest.xml:

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

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

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(设备或接口)如下图:

AndroidManifest资源文件
在 res/xml/device_filter.xml 中,并指定应过滤具有指定属性的所有 USB 设备:


4-3:USB通信实现代码封装到UsbHidHelper类中:

 

/**

* Created by 631934797 on 2021/3/3

*/

public class UsbHidHelper {

private static final StringTAG = UsbHidHelper.class.getCanonicalName();

    private UsbManagermUsbManager;

    private UsbDeviceConnectionmUsbDeviceConnection;

    private UsbEndpointmUsbEndpointOut;

    private UsbEndpointmUsbEndpointIn;

    private UsbInterfacemUsbInterface;

    private static UsbHidHelpermInstance =null;

    private static ContextmContext;

    private boolean mToggle =true;

    private boolean isConnect =false;

    private ExecutorServicemThreadPool;

    private byte[]recvBuffer  =new byte[1024];

    private int mVendorID ;

    private int mProductID ;

    private int mFindCont =3;

    private final StringUSB_PERMISSION ="roy-lee.usb.permission";

    private PendingIntentmPrtPermissionIntent; //获取外设权限的意图

    /**

    * 获取UsbHidHelper对象

    * @param context

    * @return

    */

    public static UsbHidHelper  getInstance(Context context) {

             if(mInstance ==null) {

            mInstance =new UsbHidHelper();

           }

         mContext = context;

        return mInstance;

    }

/**

    * 初始化 USB设备

    *

    * @param vendorID

    * @param productID

    */

    public void initUsb_Hid(int vendorID, int productID){

        mVendorID = vendorID;

        mProductID = productID;

        // init UsbManager

        mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);

        // 初始化线程池

        mThreadPool = Executors.newFixedThreadPool(5);

        // 注册usb广播

        registerReceiver();

        // 查找USB设备

        findUsbDevice();

    }

/**

    * 动态注册usb广播,拔插动作,注册动作

    * */

    private void registerReceiver(){

        //注册在此service下的receiver的监听的action

        IntentFilter intentFilter =new IntentFilter();

        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);

        intentFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);

        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);

        intentFilter.addAction(USB_PERMISSION);

        mContext.registerReceiver(usbReceiver, intentFilter);//注册receiver

        //通知监听外设权限注册状态

        //PendingIntent:连接外设的intent

       //ask permission

        mPrtPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(USB_PERMISSION), 0);

    }

private BroadcastReceiverusbReceiver =new BroadcastReceiver() {

        @Override

        public void onReceive(Context context, Intent intent) {

                 if (intent ==null) {

                          return;

                  }

                String action = intent.getAction();

                switch (action){

                       // USB注册动作

                      case USB_PERMISSION:

                              synchronized (this) {

                                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {

UsbDevice parcelableExtra = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

                            if (parcelableExtra !=null) {

// 连接设备

                                connectDevice(parcelableExtra);

                            }else {

Log.e(TAG,"usb device suddenly disappera.");

                                Log.e(TAG,"...USB外设意外消失...");

                            }

}else {

Log.e(TAG,"usb permission granted fail.");

                            Log.e(TAG,"...USB权限注册失败...");

                            mFindCont--;

                            if (mFindCont >0){

findUsbDevice();

                            }

}

}

break;

                // USB插入动作

                case UsbManager.ACTION_USB_ACCESSORY_ATTACHED:

case UsbManager.ACTION_USB_DEVICE_ATTACHED:

Log.i(TAG,"...USB 插入...");

                    mFindCont =3;

                    findUsbDevice();

break;

                // USB拔出动作

                case UsbManager.ACTION_USB_DEVICE_DETACHED:

Log.e(TAG,"...USB 已被拔出...");

                    mToggle =true;

                    isConnect =false;

break;

            }

}

};

    /**

    *  查找设备

    */

    private boolean findUsbDevice(){

mThreadPool.execute(new Runnable() {

@Override

            public void run() {

//                while (mToggle && mFindCont > 0) {

                    Log.d(TAG, "...查找USB设备...");

                    HashMap deviceList =mUsbManager.getDeviceList();

                    Collection values = deviceList.values();

                    if (!values.isEmpty()) {

for (UsbDevice usbDevice : values) {

// 输出设备信息

                            Log.e(TAG,"mVendorID : "+mVendorID +"  mProductID : " +mProductID);

                            Log.d(TAG, "设备ID: vid = " + String.format("%x", usbDevice.getVendorId()) +" , pid = " + String.format("%x", usbDevice.getProductId()));

                            int vendorId = usbDevice.getVendorId();

                            int productId = usbDevice.getProductId();

                            if (vendorId ==mVendorID && productId ==mProductID) {

Log.d(TAG, "...枚举SUB设备成功...");

                                // 获取权限

                                if (mUsbManager.hasPermission(usbDevice)) {

// 建立连接

                                    connectDevice(usbDevice);

                                }else {

Log.e(TAG,"...申请USB权限失败...");

                                    mUsbManager.requestPermission(usbDevice, mPrtPermissionIntent);

                                }

}

}

}else {

// TODO 没有USB设备

                        Log.e(TAG,"...没有USB设备...");

                    }

//                    SystemClock.sleep(10000);

//                    mFindCont--;

//                }

            }

});

        return isConnect;

    }

/**

    * 连接设备

    * @param usbDevice

    */

    private void connectDevice(UsbDevice usbDevice){

Log.e(TAG,"...申请USB权限成功...");

        Log.e(TAG,"UsbDevice :" + usbDevice);

        // 打开设备

        UsbDeviceConnection conn=mUsbManager.openDevice(usbDevice);

        if (conn !=null) {

mUsbInterface = usbDevice.getInterface(0);

            if (conn.claimInterface(mUsbInterface, true)){

mUsbDeviceConnection = conn;

                int endpointCount =mUsbInterface.getEndpointCount();

                for (int i =0; i < endpointCount; i++) {

UsbEndpoint usbEndpoint =mUsbInterface.getEndpoint(i);

                    Log.e(TAG,"Type: "+ usbEndpoint.getType());

                    Log.e(TAG,"Direction: "+ usbEndpoint.getDirection());

                    // TODO USB 4种传输模式,根据自己实际通信需求自行更改

                    if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT) {

if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_OUT) {

mUsbEndpointOut = usbEndpoint;

                        }else if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {

mUsbEndpointIn = usbEndpoint;

                        }

}

}

if (mUsbEndpointOut !=null &&mUsbEndpointIn !=null) {

// TODO 连接USB设置成功

                    Log.i(TAG,"connected success");

                    mToggle =false;

                    isConnect =true;

                    onLoopSendData();

                }

}

}

}

/**

    * 发送HID数据

    *

    * @param messageContent

    * @return

    */

    public boolean sendHidData(String messageContent){

return sendHidData(messageContent.getBytes());

    }

public boolean sendHidData(final byte[] contentBytes){

final boolean[] states = {false};

        mThreadPool.execute(new Runnable() {

@Override

            public void run() {

if (mUsbDeviceConnection !=null &&mUsbEndpointOut !=null) {

/**

                    * 发送数据的地方 , 只接受byte数据类型的数据

                    */

                    int i =mUsbDeviceConnection.bulkTransfer(mUsbEndpointOut, contentBytes, contentBytes.length, 2000);

                    if (i >0) {

Log.i(TAG,"发 ==> ◇: " + HexDump.byteTo16String(contentBytes) +"\r\n");

                        states[0] =true;

                    }else {

Log.e(TAG,"...数据发送失败...");

                    }

}else {

Log.e(TAG,"...请先连接USB...");

                }

}

});

        return states[0];

    }

/**

    * 接收HID数据

    *

    * @param callback

    */

    public void receivedHidNewData(final HidDataCallback callback){

mThreadPool.execute(new Runnable() {

@Override

            public void run() {

while (true) {

/**

                    * 循环接受数据的地方 , 只接受byte数据类型的数据

                    */

                    if (mUsbDeviceConnection !=null &&mUsbEndpointIn !=null &&isConnect) {

int i =mUsbDeviceConnection.bulkTransfer(mUsbEndpointIn, recvBuffer, 64, 1000);

                        if (i >0) {

byte[] subArray = HexDump.getSubArray(recvBuffer, 0, i);

                            Log.i(TAG,"收 <== ◆: "+HexDump.byteTo16String(subArray) +"\r\n");

                            if(callback !=null)

callback.onReceiveHidData(subArray);

                        }

}

SystemClock.sleep(10);

                }

}

});

    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容