安卓USB开发详解
【第一版更编完成】
一、前言
1.是什么
安卓手机,通过OTG,与物联网设备相连,进行数据交互
2.为什么
~~~~~~~ 近期,开发一款Android系统的智能家居产品——带屏智能音箱,需要USB OTG转串口连接物联网设备。物联网设备,获取家居环境等数据,通过串口传递给智屏,智屏将家居环境数据,显示在屏幕上。
3.怎么做
步骤:
- 查询谷歌官方文档、Android SDK API文档
- 总结前期积累,初步开发
- 期间遇见一小时以上卡顿,复看文档,再者论坛广纳,见贤思齐、取精去粕
- 实践与测试,开发Demo,验证并总结
- 重新设计规划,封装为组件,放入安卓组件库
- 迭代
~~~~~~~ 文章稍长,适合较全理解安卓USB程序开发。如对文长不适,请通过目录,跳转到想要阅读的部分。感谢阅读,祝能收获到想要的知识点。初心为总结加深理解和记忆,开帖,为了同样学习USB的兄弟姐妹提供一点帮助。学习路上,一起加油。
二、简析USB
~~~~~~~ 如文名,涉及三个概念:
- USB :Universal Serial Bus,通用串行总线
- 安卓设备:运行着安卓操作系统的设备
- USB设备:具有USB接口及实现了USB协议的设备
~~~~~~~
虽然本片文章主要讲解站在安卓应用层对USB的开发,但简单了解USB,而不只是分析安卓SDK相关USB的API,个人认为对我们的安卓开发会起到很大的帮助。
~~~~~~~
如果您对USB比较了解,可以通过目录直接跳转到想要阅读的部分。
1.外部总线标准
~~~~~~~
USB,即Universal Serial Bus(通用串行总线)的缩写,是一个外部总线标准。
~~~~~~~
USB采用四线电缆,其中两根是用来传送数据的串行通道,另两根为设备提供电源。
~~~~~~~
对于任何已经成功连接且相互识别的外设,将以双方设备均能够支持的最高速率传输数据。USB总线会根据外设情况在所兼容的传输模式中自动地由高速向低速动态转换且匹配锁定在合适的速率(虽然如此,最好确定好安卓设备和USB硬件外围设备,都支持的某个波特率)。
2.主从结构星型拓扑
~~~~~~~
USB是一种主从结构的系统。包含主机和从机(功能设备),两种角色。
~~~~~~~
USB系统采用级联星型拓扑,该拓扑由三个基本部分组成:主机(Host),集线器(Hub)和功能设备。
3.自、总供电模式
4.OTG
~~~~~~~
一旦离开了PC,各设备间无法利用USB口进行操作,因为没有一个设备能够像PC一样充当主机。On-The-Go,即OTG技术就是实现在没有Host的情况下,实现从设备间的数据传送,主要应用于各种不同的设备或移动设备间的联接,进行数据交换。
~~~~~~~
通过OTG技术,可以给智能终端扩展USB接口配件以丰富智能终端的功能:为设备供电(安卓设备当于充电宝)、文件互传、扩展手机内存、连接外部设备等。
OTG线
- 文件互传、扩展内存
- 连接外部设备
- 所以,请提前准备好OTG线;如果,要接多个设备,还需准备Hub
- 或者使用Genymotion+VirtualBox,直接把插在电脑上的USB设备作为OTG设备 转接到安卓模拟器上(将专开一帖讲解)
5.数据传输模式
1.控制传输(Control Transfer)
2.中断传输(Interrupt Transfer)
3.批量传输或叫块传输(Bulk Transfer)
4.实时传输或叫同步传输(Isochronous Transfer)
6.小结
~~~~~~~ 作为安卓应用层开发者,我们无需对上述概念完完全全掌握。但请务必了解下面几点:
- USB是一种设备间的通信标准
- USB主从结构,配Hub可做级联星型拓扑
- USB主机设备可对从设备执行读写操作
- USB从设备与Hub,从主线获取电源或自供电源
- OTG在无Host时,使从设备间可通信
~~~~~~~ 了解了基本的USB知识后,咱们一起开始探究安卓USB开发吧。
三、安卓USB开发探究
1.从谷歌官方资料入手
~~~~~~~
如果能静下心,把官方的文档吃透,可以较好的掌握安卓USB开发相关概念。实际开发,还需要迈过很多坑。
~~~~~~~
本节内容,为官网截图加分析形式进行讲解。每小节均附有官网地址传送门。有时间和耐心的读者,可以自行看完官网介绍,再回来看总结。时间局限的读者,可以直接阅读本节总结。
(1)引子
~~~~~~~
在AS中敲代码,输入Usb,立马提示Android SDK的相关API,总共有九个。
想要了解这些类该怎么使用以及安卓与USB设备之间的风流韵事。咱们得先去谷歌瞧瞧,走起。
(2)总览
android.hardware.usb:为驱动了USB硬件外围设备的安卓系统设备,提供与USB硬件外围设备的通信支持。
- 使用UsbManager访问USB的状态,并与连接的硬件外围设备通信。
- 如果安卓系统设备充当USB主机,则使用UsbDevice与硬件外设通信。
- 如果外设充当USB主机,则使用UsbAccessory。
安卓USB主机与从机API
类 | 说明 |
---|---|
UsbAccessory | 表示一个 USB 配件,并包含访问其标识信息的方法 |
UsbConfiguration | USBDevice类的USB配置类 |
UsbConstants | 定义与 Linux 内核的 linux/usb/ch9.h 中的定义相对应的 USB 常量 |
UsbDevice | 表示连接的 USB 设备,并包含用于访问其标识信息、接口和端点的方法 |
UsbDeviceConnection | 表示与设备的连接,可在端点上传输数据。借助此类,您能够以同步或异步方式反复发送数据 |
UsbEndpoint | 表示接口端点,是此接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点 |
UsbInterface | 表示 USB 设备的接口,它定义设备的一组功能。设备可以具有一个或多个用于通信的接口 |
UsbManager | 您可以枚举连接的 USB 设备并与之通信 |
UsbRequest | 表示通过 UsbDeviceConnection 与设备通信的异步请求 |
(3)与外部设备交互模式
~~~~~~~
两种模式,安卓设备作为主机的主机模式;安卓设备作为从机的从机模式。
~~~~~~~
当连接的外部设备占用了手机唯一的USB口后,可以通过WiFi ADB的方式(稍后实战部分会具体讲解),为手机烧录程序和进行其它相关操作。
(4)安卓设备作为主机
#API简析
a) UsbManager
Usb设备的管理类,可以枚举连接的 USB 设备
常量 | 作用 |
---|---|
ACTION_USB_DEVICE_ATTACHED | USB设备连接时的广播 |
ACTION_USB_DEVICE_DETACHED | USB设备拔出时的广播 |
EXTRA_PERMISSION_GRANTED | 包含布尔值Extra,表示用户是否授予操作设备 |
方法 | 作用 |
---|---|
Hash<String, UsbDevice> getDeviceList() | 获取当前已物理连接的设备列表 |
boolean hashPermission(UsbDevice device) | 判断是否对某设备具有操作权限 |
UsbDeviceConnection openDevice(UsbDevice device) | 开启设备,以便后续进行数据交互 |
void requestPermission(UsbDevice device, PendingIntent pi) | 为某设备请求操作权限 |
如其名,UsbManager是USB的管理者。对设备操作前,要最先对其实例化,通过其:
1.获取设备
2.申请相应设备权限
3.获取UsbDeviceConnection
b) UsbDevice
USB设备,表示连接的 USB 设备,并包含用于访问其标识信息、接口的方法。
方法 | 作用 |
---|---|
int getVendorId() | 获取Usb设备供应商ID |
int getDeviceId() | 获取设备的唯一整数ID |
String getSerialNumber() | 获取设备的序列号 |
UsbInterface getInterface(int index) | 获取给定索引index处的UsbInterface |
c) UsbInterface
Usb设备接口,定义设备的一组功能。设备可以具有一个或多个用于通信的接口。每个接口提供与其他接口分开的不同功能。接口将具有一个或多个UsbEndpoint。
方法 | 作用 |
---|---|
int getEndpointCount() | 返回接口包含的UsbEndpoint数目 |
UsbEndpoint getEndpoint(int index) | 获取指定索引index处的UsbEndpoint |
d) UsbEndpoint
Usb设备端点,是接口的通信通道。一个接口可以具有一个或多个端点,并且通常具有用于与设备进行双向通信的输入和输出端点。
方法 | 作用 |
---|---|
int getType() | 获取端点的类型 |
int getDirection() | 获取端点类型。0:数据方向为主机到设备,输出;128:数据方向为设备到主机,输入 |
int getMaxPacketSize() | 返回端点的最大数据包大小 |
e) UsbDeviceConnection
与Usb设备的连接,可在端点上传输数据。借助此类,可以同步或异步方式反复发送数据
方法 | 作用 |
---|---|
int bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout) | 大量数据传输,可作为发送和接收 |
UsbRequest requestWait() | 返回异步数据传输类对象 |
void close() | 释放与设备有关的所有系统资源 |
f) UsbRequest
表示通过 UsbDeviceConnection 与设备通信的异步请求
方法 | 作用 |
---|---|
boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) | 初始化请求,以便它可以在给定的端点上读取或写入数据 |
boolean queue(ByteBuffer buffer) | 将请求排队以在其端点上发送或接收数据 |
void close() | 释放与此请求有关的所有资源 |
g) UsbConstants
定义与 Linux 内核的 linux/usb/ch9.h 中的定义相对应的 USB 常量
常数 | 意义 |
---|---|
int USB_DIR_IN | 表示UsbEndpoint输入方向为输入(设备到主机) |
int USB_DIR_OUT | 表示UsbEndpoint输入方向为输出(主机到设备) |
int USB_CLASS_HID | 人机接口设备(例如,鼠标和键盘)的USB类 |
int USB_CLASS_HUB | USB集线器的USB类 |
int USB_CLASS_MASS_STORAGE | 大容量存储设备的USB类 |
int USB_CLASS_COMM | 通信设备的USB类 |
int USB_CLASS_VIDEO | 视频设备的USB类 |
int USB_CLASS_WIRELESS_CONTROLLER | 无线控制器设备的USB类 |
(5)安卓设备作为从机
传送门
暂不对这部分内容做介绍,请读者参照从机开发,开发的思想是类似的。
(6)权限申请
2. 总结
- 只要有设备通过USB连接到手机,安卓系统便会广播有设备连接的事件:android.hardware.usb.action.USB_DEVICE_ATTACHED
- 这样我们便可以通过创建接收该事件的接收器,每当有设备接入时,便收到消息,执行相应操作
- 进行数据接收的逻辑代码,最好另开线程进行
安卓USB开发一般步骤:
1.创建权限申请Inten及动态广播接收器
2.获取设备并检测是否有设备操作权限
3.检测是否有设备连接
3.与设备建立连接
4.与设备进行通信
5.当作为承载的context被销毁时,销毁广播接收器,断开与设备连接
四、安卓USB开发实战
1.需求分析
- 安卓设备与ZigBee模块,进行物理上的连接(OTG转串口)
- 两个设备之间数据交互(设备检测、设备连接、数据收发、统一数据格式)
2.程序设计
- 创建广播接收器
- 创建设备访问权限Intent
- 获取已连接设备列表
- 连接设备
- 与设备建立数据交互通道
- 释放相关系统资源
3.程序开发
创建广播接收器
// 创建广播接收器
public void initBroadcastReceiver() {
// 申请USB设备操作权限
mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
// 时间相关广播接收器
timeBroadcastReceiver = new TimeBroadcastReceiver();
timeIntentFilter = new IntentFilter();
timeIntentFilter.addAction(Intent.ACTION_TIME_TICK);
timeIntentFilter.addAction(Intent.ACTION_TIME_CHANGED);
this.context.registerReceiver(timeBroadcastReceiver, timeIntentFilter);
// USB相关广播接收器
usbBroadcastReceiver = new UsbBroadcastReceiver();
usbIntentFilter = new IntentFilter();
usbIntentFilter.addAction(ACTION_USB_PERMISSION);
usbIntentFilter.addAction(UsbManager.EXTRA_DEVICE);
usbIntentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
usbIntentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
this.context.registerReceiver(usbBroadcastReceiver, usbIntentFilter);
}
class TimeBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().contains(Intent.ACTION_TIME_TICK)) {
Toast.makeText(context, "接收到了每分钟时间计时器的广播", Toast.LENGTH_LONG).show();
}
}
}
class UsbBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println(intent.getAction().toString());
String action = intent.getAction();
System.out.println(action);
if (action.contains(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
Toast.makeText(context, "USB设备插入", Toast.LENGTH_LONG).show();
} else if (action.contains(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
Toast.makeText(context, "USB设备拔出", Toast.LENGTH_LONG).show();
} else if (action.contains(ACTION_USB_PERMISSION)) {
synchronized (this) {
if (deviceList.size() != 0) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (null != deviceList.get(0)) {
Toast.makeText(context, "设备权限获取成功", Toast.LENGTH_LONG).show();
initDevices();
}
} else {
Toast.makeText(context, "设备权限获取失败", Toast.LENGTH_LONG).show();
}
} else {
//getDevice();
}
}
}
}
}
USB设备操作权限
// 在UsbBroadcastReceiver中
else if (action.contains(ACTION_USB_PERMISSION)) {
synchronized (this) {
if (deviceList.size() != 0) {
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (null != deviceList.get(0)) {
Toast.makeText(context, "设备权限获取成功", Toast.LENGTH_LONG).show();
initDevices();
}
} else {
Toast.makeText(context, "设备权限获取失败", Toast.LENGTH_LONG).show();
}
} else {
//getDevice();
}
}
}
获取已连接设备列表
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (usbManager != null) {
deviceHashMap = usbManager.getDeviceList();
System.out.println("已连接设备数为:" + deviceHashMap.size());
Iterator<UsbDevice> deviceIterator = deviceHashMap.values().iterator();
if (deviceHashMap.size() != 0) {
while (deviceIterator.hasNext()) {
UsbDevice usbDevice = deviceIterator.next();
if (usbManager.hasPermission(usbDevice)) {
deviceList.add(usbDevice);
System.out.println("\n 设备信息:设备名字:" + usbDevice.getDeviceName() + "; 设备描述符" + usbDevice.describeContents());
} else {
usbManager.requestPermission(usbDevice, mPermissionIntent);
}
}
}
}
连接设备
if (deviceList.size() != 0) {
usbInterface = deviceList.get(0).getInterface(0);
System.out.println("该接口具有的端点:" + usbInterface.getEndpointCount());
usbOutEndpoint = usbInterface.getEndpoint(1);
System.out.println("端点方向:" + usbOutEndpoint.getDirection() + "; 端点类型:" + usbOutEndpoint.getType());
usbInEndpoint = usbInterface.getEndpoint(0);
maxPacketSize = usbInEndpoint.getMaxPacketSize();
System.out.println("端点方向:" + usbInEndpoint.getDirection() + "; 端点类型:" + usbOutEndpoint.getType());
usbDeviceConnection = usbManager.openDevice(deviceList.get(0));
usbDeviceConnection.claimInterface(usbInterface, true);
}
与设备建立数据交互通道
// 发送数据
public boolean sendData(String data) {
System.out.println("开始发送数据");
// 装配数据
try {
sendBuf = data.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 发送数据
if (usbDeviceConnection != null) {
if (usbDeviceConnection.bulkTransfer(usbOutEndpoint, sendBuf, sendBuf.length, 0) > 0) {
Log.i("ShareUsb.sendDta()", "数据发送成功");
return true;
} else {
Log.e("ShareUsb.sendDta()", "数据发送失败");
return false;
}
} else {
Log.e("ShareUsb.sendDta()", "未连接任何设备!");
return false;
}
}
// 接收数据
public void enableReceiveData() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (this) {
if (usbDeviceConnection != null) {
receiveBuf = new byte[usbInEndpoint.getMaxPacketSize()];
int resultCode = usbDeviceConnection.bulkTransfer(usbInEndpoint, receiveBuf, receiveBuf.length, 0);
byte[] date = null;
if (resultCode > 0) {
date = ByteBuffer.allocate(resultCode).array();
for (int i = 0; i < resultCode; i++) {
date[i] = receiveBuf[i];
}
// 将数据传输给观察者
if (receiveDataListenerList != null) {
for (ReceiveDataListener receiveDataListener : receiveDataListenerList) {
receiveDataListener.receiveData(new String(date), resultCode);
}
}
}
} else {
Log.e("ShareUsb.sendDta()", "未连接任何设备!");
}
}
}
}
}).start();
}
public void addReceiveDataListener(ReceiveDataListener receiveDataListener) {
receiveDataListenerList.add(receiveDataListener);
}
public interface ReceiveDataListener {
void receiveData(String data, int dataLength);
}
释放系统资源
public void end() {
if (timeBroadcastReceiver != null) {
context.unregisterReceiver(timeBroadcastReceiver);
}
if (usbBroadcastReceiver != null) {
context.unregisterReceiver(usbBroadcastReceiver);
}
if (usbDeviceConnection != null) {
usbDeviceConnection.close();
}
}
4.程序测试
shareUsb = new ShareUsb();
shareUsb.run(this);
shareUsb.sendData("Phone's Data");
// 开启接收数据
shareUsb.enableReceiveData();
shareUsb.addReceiveDataListener(new ShareUsb.ReceiveDataListener() {
@Override
public void receiveData(String data, int dataLength) {
System.out.println("收到数据:" + data);
}
});
日志
开发Demo,并测试
测试环境:
- ShareUsb-Demo
- Genymotion模拟器
- Oracl VM VirtualBox虚拟机
- USB设备(电脑1 --> USB Hub --> USB转TTL --> TTL转USB --> 电脑2 --> SSCOM v5.13.1;或者已经开发好串口交互程序的物联网设备)
- 真机(安卓手机或安卓开发板,安卓系统版本越新越好)
5.程序交付
ShareUsb.java
ShareUsb-Demo(Github链接)
五、鸣谢
- 本篇文章,部分图片资源来自百度图片和百度百科;部分图片资源来自书籍《计算机USB系统原理及其主/从机设计》;部分图片资源来源于谷歌官方网站;部分图片来源于其它网络资源。在此,对资源主表示衷心的感谢。如果,认为部分资源,不适合或不应该出现在本篇文章,博主深表抱歉,请及时与博主联系协商。
- 感谢各位的阅读,博主各方面知识有限,难免出现偏误,如果您不吝指正,请在评论区留言。祝大家快乐编程,头发永茂!