背景

上篇文章中我们了解了嵌入式设备端将Android手机设置为accessory模式的流程以及嵌入式设备端接收和发送数据的流程,本文将对应介绍Android端accessory模式被激活的过程,以及接下来如何与嵌入式设备端进行通信。本文的源码下载地址:https://git.oschina.net/vonchenchen/aoa_android.git

实现

USBConnStatusManager 底层启动accessory模式

Android系统api通过UsbManager类管理usb相关,这里我们关注一下与accessory模式相关的内容。
当设备端启动android的accessory模式时,系统将会发送一条广播,设备拔出时也会发送一条广播,同时还有一条申请usb使用权限的广播。所以,要做的第一步就是动态注册这些广播,并编写一个广播接收者来处理对应的事件。这里对于的方法我们封装到了USBConnStatusManager类中,用来管理accessory相关连接。

IntentFilter filter = new IntentFilter();//接收权限信息filter.addAction(ACTION_USB_PERMISSION);//接收accessory连接事件filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);//接收accessory断开事件filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);getContext().registerReceiver(mUsbReceiver, filter);

下面是对应的广播接收者,这里其实只需要监听两个广播,一个是获取usb权限,一旦这个广播发出我们就可以认为设备现在正在启动手机的accessory模式,第一次连接时手机会弹出对话框,让我们选择是否运行usb权限,另外一个就是需要在usb断开时做出反应,告诉设备连接已经断开了。下面是处理广播事件的过程:

mUsbReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();Log.i(TAG, "receive usb connect broadcast:" + action);if (ACTION_USB_PERMISSION.equals(action)) {synchronized (this) {//UsbAccessory accessory = UsbManager.getAccessory(intent);UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);//获取accessory句柄成功if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {Log.d(TAG, "prepare to open usb stream");sCurStatus = STATUS_CONN_OK;mUsbAccessory = accessory;if (mOnUSBConnStatusChanged != null) {mOnUSBConnStatusChanged.onUSBConnect(accessory);}} else {Log.d(TAG, "permission denied for accessory " + accessory);sCurStatus = STATUS_CONN_ERR;mUsbAccessory = null;if (mOnUSBConnStatusChanged != null) {mOnUSBConnStatusChanged.onUSBConnectFailed(accessory);}}}} else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);//if (accessory != null && accessory.equals(mAccessory)) {//检测到usb断开Log.d(TAG, "USB_ACCESSORY_DETACHED " + accessory);sCurStatus = STATUS_DISCONN;mUsbAccessory = null;//closeAccessory();//synchronized (USBConnStatusManager.class) {if (mOnUSBConnStatusChanged != null) {mOnUSBConnStatusChanged.onUSBDisconnect(accessory);}//}//}}}};

这里拿到accessory的引用以后就可以用这个引用获取usb的读写流,然后将accessory交给外部接口,由外部类处理数据的具体读写内容。如果接收到设备拔出广播,则手动释放引用,更新连接状态。
另外,如果设备已经插入并且处于accessory模式,广播接受者并不会调用,这时可以同步开启设备。可以检查mUsbManager.getAccessoryList(),如果有accessory设备则可以直接获取设备引用。由于一般手机都只有一个U口,此处默认只要有一个accessory连接就是我们的设备。下面代码用于同步开启已经存在的于accessory表中的设备:

    public void checkUSBDevice() {UsbAccessory[] accessories = mUsbManager.getAccessoryList();if(accessories == null){Log.i(TAG, "accessories list is null");return;}Log.i(TAG, "accessories length "+accessories.length);UsbAccessory accessory = (accessories == null ? null : accessories[0]);if (accessory != null) {if (mUsbManager.hasPermission(accessory)) {sCurStatus = STATUS_CONN_OK;mUsbAccessory = accessory;//synchronized (USBConnStatusManager.class) {if (mOnUSBConnStatusChanged != null) {mOnUSBConnStatusChanged.onUSBConnect(accessory);}//}} else {//synchronized (mUsbReceiver) {if (!mPermissionRequestPending) {mUsbManager.requestPermission(accessory, mPermissionIntent);mPermissionRequestPending = true;}//}}}}

USBHelper 具体操作usb的开关和读写等功能

USBHelper类具体操作usb的功能,这个类中持有USBConnStatusManager的单例对象,有了USBConnStatusManager就可以拿到accessroy,通过USBConnStatusManager获取到读写流,这个类就是在外层调用USBConnStatusManager方法,对usb进行操作。

openAsync

这个方法用来开启usb,首先注册广播接收者回调,用来检测usb插拔信息,注册完毕后检查当前系统中存在的accessory设备,如果已经连接了accessroy设备,则直接获取其accessroy的引用,通过这个引用获取读写流,者就是usb的打开过程。

    /*** accessory模式打开android的 usb设备* 如果当前列表有处于accessory模式的句柄则直接打开* 如果当前没有则回监听usb插拔,监听到对应事件后检查系统列表* @param onUSBConnStatusChanged*/@Overridepublic void openAsync(final OnUSBConnStatusChanged onUSBConnStatusChanged) {mReciveBuffer = new byte[RECIVE_BUF_SIZE];//注册USB连接状态监听mUSBConnStatusManager.registOnUSBConnStatusChangedListener(new OnUSBConnStatusChanged() {@Overridepublic void onUSBConnect(UsbAccessory accessory) {openAccessory(accessory);if (onUSBConnStatusChanged != null) {onUSBConnStatusChanged.onUSBConnect(accessory);}}@Overridepublic void onUSBConnectFailed(UsbAccessory accessory) {closeAccessory();if (onUSBConnStatusChanged != null) {onUSBConnStatusChanged.onUSBConnectFailed(accessory);}}@Overridepublic void onUSBDisconnect(UsbAccessory accessory) {closeAccessory();if (onUSBConnStatusChanged != null) {onUSBConnStatusChanged.onUSBDisconnect(accessory);}}});//检查usb列表 查看是否已经连接accessory设备mUSBConnStatusManager.checkUSBDevice();}/*** 通过accessory句柄拿到usb设备的输入输出流* @param accessory*/private void openAccessory(UsbAccessory accessory) {mFileDescriptor = mUsbManager.openAccessory(accessory);if (mFileDescriptor != null) {mAccessory = accessory;FileDescriptor fd = mFileDescriptor.getFileDescriptor();//usb读写流mInputStream = new FileInputStream(fd);mOutputStream = new FileOutputStream(fd);if (mOnDataTranPrepared != null) {Log.d(TAG, "accessory opened DataTranPrepared");mOnDataTranPrepared.onDataTranPrepared(mInputStream, mOutputStream);}Log.d(TAG, "accessory opened");} else {Log.d(TAG, "accessory open fail");}}

另外这个类还提供了usb数据读写和关闭设备等方法,大家可以参考项目源码。

SimpleTcpWrapper 封装上层通信协议

打通底层数据通道,下面就是封装我们自己协议了,在项目中使用tcp头简单封装了一个协议,可以实现三次握手,数据包通过序列号校验以及根据不同端口分发数据的功能。本章只讨论Android设备底层通信的实现,所以删除了协议部分,只是将usb发送过来的数据原样发送回去。

SimpleTcpWrapper中创建一个USBHelper对象用来管理usb数据通信,调用openAsync异步打开数据。一旦数据连接成功,我们就是开启一个数据接收线程,读取这个accessory的inputstream,一旦收到数据就将数据写入accessory的outputstrem中。

/*** 数据通信协议封装类,本例中只涉及简单usb层传输,并没有封装上层协议* Created by lidechen on 2/25/17.*/public class SimpleTcpWrapper implements ISimpleTcpManager {private static final String TAG = "SimpleTcpWrapper";private Context mContext;/*** 数据通信对象引用*/private USBHelper mConmunicateHelper;private Thread mRecieveThread;private ReciveTask mReciveTask;/*** 强制重启usb标志*/private boolean mForceOpenUSB = false;public SimpleTcpWrapper(Context context) {mContext = context;mConmunicateHelper = new USBHelper(mContext);}public void start(){//数据传输层准备就绪 可以开启数据收发任务mConmunicateHelper.setOnDataTranPrepared(new USBHelper.OnDataTranPrepared() {@Overridepublic void onDataTranPrepared(FileInputStream inputStream, FileOutputStream outputStream) {if (Config.DEBUG) {Log.i(TAG, "accessory opened: inputStream " + inputStream + " outputStream " + outputStream);}//建立从usb读取数据的任务mReciveTask = new ReciveTask(inputStream);mRecieveThread = new Thread(mReciveTask);mRecieveThread.start();}});}public void openUSBAsync(boolean reset) {if (mForceOpenUSB == false) {//状态为已连接且不是从后台进入 直接返回if (!reset) {return;}if(Config.DEBUG){Log.i(TAG, "openUSBAsync ...");}}mForceOpenUSB = false;mConmunicateHelper.openAsync(new OnUSBConnStatusChanged() {@Overridepublic void onUSBConnect(UsbAccessory accessory) {}@Overridepublic void onUSBConnectFailed(UsbAccessory accessory) {Log.i(TAG, "connect state ### onUSBConnectFailed ###");mConmunicateHelper.close();mForceOpenUSB = true;}@Overridepublic void onUSBDisconnect(UsbAccessory accessory) {Log.i(TAG, "connect state ### onUSBDisconnect ###");mConmunicateHelper.close();mForceOpenUSB = true;}});}public void closeUSB() {if (mRecieveThread != null) {mReadTaskRun = false;Thread.State state = mRecieveThread.getState();if (state == Thread.State.BLOCKED || state == Thread.State.TIMED_WAITING || state == Thread.State.TIMED_WAITING) {mRecieveThread.interrupt();}mRecieveThread = null;}mConmunicateHelper.close();}@Overridepublic int readTcpData(byte[] data, int realLen) {return 0;}@Overridepublic void writeRawData(byte[] data, int realLen) {}private byte[] mReciveBuffer;private static final int RECIVE_BUF_SIZE = 1024*10;private boolean mReadTaskRun = false;public class ReciveTask implements Runnable {private FileInputStream mInputStream;public ReciveTask(FileInputStream inputStream) {mInputStream = inputStream;}@Overridepublic void run() {mReciveBuffer = new byte[RECIVE_BUF_SIZE];mReadTaskRun = true;int off = 0;int len = 0;while (mReadTaskRun) {try {if (Config.DEBUG) {Log.i(TAG, "accessory opened start read");}//usb数据接收int ret = mInputStream.read(mReciveBuffer, off, RECIVE_BUF_SIZE - off);//将接收到的数据返回给usbbyte[] retBuf = new byte[ret];System.arraycopy(mReciveBuffer, 0, retBuf, 0, ret);mConmunicateHelper.writeSyncToUSB(retBuf);Log.i(TAG, "feedback "+new String(retBuf));} catch (IOException e) {if (Config.DEBUG) {Log.i(TAG, "ReciveTask exception :\n" + e.toString());}try {Thread.sleep(1);} catch (InterruptedException e1) {e.printStackTrace();}}}}}}

建立连接服务

在实践中发现上述SimpleTcpWrapper如果与应用建立在本app服务中,如果应用闪退usb则不能被正确释放,有时候服务还会被杀死,相对而言开一个单独进程的服务则比较稳定,app奔溃也不会挂掉,如果手动杀死app则外部服务也会被正确释放,不会占用usb资源。
独立进程的服务可以通过socket与我们的app进行数据交互。

关于嵌入式设备启动app应用

上一篇文章我们提到了设备开启android的app,会写入一些信息,那么android端是如何对应这些信息的呢?我们看看嵌入式端给android写的那些信息是什么

    const char *setupStrings[6];setupStrings[0] = vendor;setupStrings[1] = model;setupStrings[2] = description;setupStrings[3] = version;setupStrings[4] = uri;setupStrings[5] = serial;

这里我们在android的项目下资源文件中建立一个xml文件夹。建立一个accessory_filter.xml文件,文件内容如下:

<!--?xml version="1.0" encoding="utf-8"?-->
<resources><usb-accessory manufacturer="vonchenchen" model="android.usbaoa" version="0.1">
</usb-accessory></resources>
<!--
const char *vendor = "vonchenchen";
const char *model = "android.usbaoa";
const char *description = "Android Aoa Interface";
const char *version = "0.1";
const char *uri = "https://www.baidu.com/";
const char *serial = "1234567890";
-->

下面的注释是c代码中对应的字符串,manufacturer与vendor对应,model,version两个量对应一样,accessory就会被启动起来。假如手机中没有对应app,就会弹出对话框,选择是否启动浏览器,访问uri对应的地址。对话框中的描述信息就是description对应的字符串。

总结

到此,Android通过AOA协议与嵌入式设备通信的流程就已经分析完了,在设备端我们借助了libusb库,通过其api操作usb,将手机设置为accessory模式,然后通过libusb读写数据,在Android手机端监听accessory事件,同时查询本地accessory列表,一旦拿到accessory引用,就可以获取读写流,同时Android
端最好将accessory的相关处理放在单独进程的服务中处理,防止应用闪退导致usb资源无法释放,在此连接无法成功的问题。

Android usb学习笔记:Android AOA协议Android端 流程总结相关推荐

  1. Android USB的AOA协议设备端(主机模式,配件模式),ADB连接

    USB的ADB/AOA协议(一种是ADB模式,一种是AOA模式).AOA协议是Google公司推出的用于实现Android设备与外围设备之间USB通信的协议. ADK中与USB配件模式相关的两个类是U ...

  2. 疯狂Android讲义 - 学习笔记(二)

    疯狂Android讲义 - 学习笔记(二) Android应用的用户界面编程 2.1 界面编程与视图(View)组件 Android应用的绝大部分UI组件放在android.widget.androi ...

  3. Android 开发学习笔记

    Android 开发学习笔记 Lesson1 (2.28) android之父: Andy Rubin(安迪 鲁宾) Lesson2 (3.3) android 的优点: 开放.网络接入自由.丰富的硬 ...

  4. Android Studio --- [学习笔记]RadioButton、CheckBox、ImageView、ListView、TCP的三次握手

    说明 源代码 在2.x里有TCP的三次挥手与四次握手,先对它进行简单的回答(百度).预计在下一篇里,会继续说明TCP 接上一篇: Android Studio - > [学习笔记]Button. ...

  5. Android高级终端开发学习笔记(《疯狂Android讲义》第11章-第17章)

    Android高级终端开发笔记 2021/6/19 下午 13:34开始 多媒体应用开发 Android支持的音频格式有:MP3 WAV 3GP等.支持的视频格式有MP4 3GP等. 多媒体数据既可以 ...

  6. java/android 设计模式学习笔记(1)--- 单例模式

    前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使 ...

  7. Android Binder 学习笔记

    前言: Binder是Android给我们提供的一种跨进程通信方式.理解Binder能帮助我们更好的理解Android的系统设计,比如说四大组件,AMS,WMS等系统服务的底层通信机制就都是基于Bin ...

  8. Android:日常学习笔记(8)———探究UI开发(2)

    Android:日常学习笔记(8)---探究UI开发(2) 对话框 说明: 对话框是提示用户作出决定或输入额外信息的小窗口. 对话框不会填充屏幕,通常用于需要用户采取行动才能继续执行的模式事件. 提示 ...

  9. Android:日常学习笔记(6)——探究活动(3)

    Android:日常学习笔记(6)--探究活动(3) 活动的生命周期 返回栈 Android中的活动是可以叠加的,我们每启动一个新活动,就会覆盖在原来的活动上,点击Back以后销毁最上面的活动,下面的 ...

最新文章

  1. 分享一个android debug模式,出现 waiting for debugger把界面卡住,取巧的解决办法
  2. 【262】pscp命令 实现windows与linux互传文件
  3. JVM系列之:对象的锁状态和同步
  4. minigui linux 安装与运行
  5. python argument list too long_间歇“OSError:[Errno 7]参数列表太长”,命令短(~125个字符)...
  6. Mysql学习笔记(六)增删改查
  7. telnet 超时_python3从零学习-5.10.10、telnetlib—Telnet 客户端
  8. 《商务新星.NET 4.0》发布说明
  9. (1)Matplotlib_xticks, yticks
  10. 《Unity虚拟现实开发实战》——第1章,第3.1节虚拟现实设备集成的软件
  11. 自动控制原理第七版胡寿松pdf_自动控制原理简明笔记—(01)
  12. c语言铺地板,【北理乐学】铺地板
  13. linux网络串口工具下载,串口调试工具手机版下载
  14. 服务器虚拟机迁移的过程和步骤是什么,物理机向虚拟机迁移过程详解
  15. 【软件测试】你最常用的web测试-浏览器兼容性测试
  16. 使用svg实现的曲线时间线 2022-01-09
  17. php学习笔记——PHP 概述
  18. 当你写爬虫抓不到APP请求包的时候该怎么办?【高级篇-混淆导致通用Hook工具失效】
  19. i58250u是低端处理器吗_玩都市天际线要很大的显卡吗
  20. 未来创业前景最好的十几个行业是哪些?

热门文章

  1. Java官方教程目录2021最新版
  2. Java 泛型方法/接口、泛型限定
  3. 维度数据建模的概念和术语
  4. 纯CSS3写的10个不同的酷炫图片遮罩层效果【转】
  5. 第六章 Cesium学习入门之添加Geojson数据(dataSource)
  6. @赛迪顾问拍了拍我:数据标注基地全国多点开花
  7. CC2640R2F BLE5.0 CC2640R2BLE5.0开发文档
  8. 【小沐学qt】生成二维码
  9. 培训直播平台这些功能是必不可少的
  10. router传参接参(详细)