Android usb学习笔记:Android AOA协议Android端 流程总结
2017-04-22 13:00
381 查看
背景
上篇文章中我们了解了嵌入式设备端将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() { @Override public 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 */ @Override public void openAsync(final OnUSBConnStatusChanged onUSBConnStatusChanged) { mReciveBuffer = new byte[RECIVE_BUF_SIZE]; //注册USB连接状态监听 mUSBConnStatusManager.registOnUSBConnStatusChangedListener(new OnUSBConnStatusChanged() { @Override public void onUSBConnect(UsbAccessory accessory) { openAccessory(accessory); if (onUSBConnStatusChanged != null) { onUSBConnStatusChanged.onUSBConnect(accessory); } } @Override public void onUSBConnectFailed(UsbAccessory accessory) { closeAccessory(); if (onUSBConnStatusChanged != null) { onUSBConnStatusChanged.onUSBConnectFailed(accessory); } } @Override public 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() { @Override public 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() { @Override public void onUSBConnect(UsbAccessory accessory) { } @Override public void onUSBConnectFailed(UsbAccessory accessory) { Log.i(TAG, "connect state ### onUSBConnectFailed ###"); mConmunicateHelper.close(); mForceOpenUSB = true; } @Override public 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(); } @Override public int readTcpData(byte[] data, int realLen) { return 0; } @Override public 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; } @Override public 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); //将接收到的数据返回给usb byte[] 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 AOA协议Android端 流程总结
- Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程
- Android开发学习笔记:Button事件实现方法的总结
- android中Mms学习笔记——彩信(mms)发送流程和就收流程(四)
- python 学习笔记-山寨携程(列表,字符串,字典和流程控制总结)
- Android(java)学习笔记134:Handler用法总结和秒表案例
- Android Sip学习—SIP 协议完整的呼叫流程(准备知识)
- Android NFC 学习笔记与总结
- Android Sip学习(准备知识)SIP 协议完整的呼叫流程
- 个人安卓学习笔记---Android布局大总结(一)
- android菜鸟学习笔记29----Android应用向用户发送提示信息的方式总结
- android中Mms学习笔记——短信(sms)接收流程(三)
- Android总结笔记01:自定义View学习(一)
- Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭【学习鸿洋_视频博客笔记总结】
- Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程
- Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程
- Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程
- Android(java)学习笔记80:UDP协议发送数据
- Python学习笔记总结(一)对象和流程语句总结