您的位置:首页 > 移动开发 > Android开发

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资源无法释放,在此连接无法成功的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: