Android Open Accessory (AOA) 协议完全解析
目录
AOA协议概述
[核心概念:Host Mode与Accessory Mode](#核心概念:Host Mode与Accessory Mode)
两种模式核心差异速查
AOA版本演进
协议握手流程详解
[代码实践:Host Mode完整示例](#代码实践:Host Mode完整示例)
[代码实践:Accessory Mode完整示例](#代码实践:Accessory Mode完整示例)
使用场景总结
一、AOA协议概述
Android Open Accessory (AOA) 协议是Google定义的一套标准,允许外部USB硬件(称为Android USB配件)与Android设备进行交互。该协议的核心设计目标,是解决早期Android设备只能作为USB从设备、无法主动连接外部USB设备的限制。
为什么需要AOA?
在AOA出现之前,Android设备通过USB连接时通常扮演"从设备"角色(如连接到电脑时作为存储设备)。这意味着Android设备无法主动控制连接它的U盘、键盘、游戏手柄等外设------因为控制这些外设需要设备具备"USB主机"能力。而很多Android设备并不支持USB Host模式。
AOA通过一种巧妙的角色反转机制绕过了这个限制:让外部配件充当USB主机,Android设备充当USB从机。这种模式下,配件负责供电和管理总线,Android设备则作为"配件"响应通信。
关键特性
配件必须提供500mA、5V的充电电力
配件模式依赖设备硬件支持,并非所有Android设备都支持
应用可通过
二、核心概念:Host Mode与Accessory Mode
理解AOA协议的关键,在于厘清USB通信中的两个角色:
USB Host Mode(主机模式)
定义:承担总线管理职责,负责枚举设备、分配地址、供电
典型场景:电脑、支持OTG的手机等
在AOA中的角色 :由外部配件承担
USB Accessory Mode(配件模式)
定义:作为被管理的设备,响应主机指令
典型场景:U盘、键盘、鼠标等
在AOA中的角色 :由Android设备承担
AOA通信中的角色定位
以下表格清晰展示了角色在不同阶段的分配:
阶段
USB Host
USB Device
初始连接
Android设备(默认)
外部配件(默认)
AOA握手完成后
外部配件
Android设备
AOA协议的本质,就是协调Android设备和外部配件完成这一角色互换。切换完成后,配件获得总线控制权,可以主动向Android设备发送数据或接收来自设备的数据。
三、两种模式核心差异速查
在实际开发中,Host Mode和Accessory Mode在Android API层面的差异如下:
对比维度
Host Mode(主机模式)
Accessory Mode(配件模式)
谁当老板
Android设备充当主机
外部配件充当主机
谁供电
Android设备向外设供电
外部配件向Android设备供电
总线控制权
Android设备拥有
外部配件拥有
核心类
UsbDevice
UsbAccessory
获取连接列表
UsbManager.getDeviceList()
UsbManager.getAccessoryList()
打开连接
UsbManager.openDevice() → UsbDeviceConnection
UsbManager.openAccessory() → ParcelFileDescriptor
数据传输方式
UsbDeviceConnection.bulkTransfer(endpoint, buffer, ...)
FileInputStream / FileOutputStream
端点处理
需手动查找 UsbEndpoint(IN/OUT端点)
无需处理端点,直接用流
权限请求
requestPermission(device, pendingIntent)
requestPermission(accessory, pendingIntent)
连接广播
ACTION_USB_DEVICE_ATTACHED
ACTION_USB_ACCESSORY_ATTACHED
断开广播
ACTION_USB_DEVICE_DETACHED
ACTION_USB_ACCESSORY_DETACHED
XML过滤器
Manifest声明
数据流向示意
复制代码
Host Mode (Android是主机):
┌─────────────┐ ┌──────────────┐
│ Android │ ──供电──→ │ USB外设 │
│ (Host) │ ←─数据──→ │ (Device) │
└─────────────┘ └──────────────┘
Accessory Mode (外部配件是主机):
┌─────────────┐ ┌──────────────┐
│ Android │ ←─供电── │ 外部配件 │
│ (Device) │ ←─数据──→ │ (Host) │
└─────────────┘ └──────────────┘
四、AOA版本演进
AOA协议有两个主要版本:
AOAv1(基础版)
发布:2011年Google I/O,随Android 3.1 (API Level 12) 引入
兼容性:Android 2.3.4 (API Level 10) 及以上可通过Add-On Library支持
功能:支持通用配件通信和ADB调试
典型场景:自定义数据传输、设备控制(机器人、传感器等)
AOAv2(增强版)
发布:2012年Google I/O,随Android 4.1 (API Level 16) 引入
新增功能 :
音频流:配件可作为音频接收端,接收Android设备的音频输出
HID(人机接口设备):配件可模拟键盘、鼠标、游戏手柄等输入设备
典型场景:音频底座、游戏外设连接
五、协议握手流程详解
这是AOA协议最核心的部分------配件如何将Android设备从普通模式切换至配件模式。以下流程参考AOA 1.0规范:
第1步:检测设备连接
配件应持续检查是否有Android设备连接。当设备连接时,配件通过读取USB设备描述符获取设备的VID(Vendor ID)和PID(Product ID)。
第2步:判断设备状态
连接时设备可能处于三种状态之一:
状态
VID
PID
说明
已处于配件模式
0x18D1 (Google)
0x2D00 或 0x2D01
可直接通信
支持但未切换
设备制造商VID
设备制造商PID
需要触发切换
不支持配件模式
设备制造商VID
设备制造商PID
无法使用AOA
注意 :0x2D00用于标准配件模式,0x2D01额外支持ADB调试接口。
第3步:查询协议版本(请求码51)
如果设备未处于配件模式,配件发送请求码51查询设备是否支持AOA协议:
复制代码
requestType: USB_DIR_IN | USB_TYPE_VENDOR
request: 51
value: 0
index: 0
data: 协议版本号(16位小端)
设备若支持,会返回非零版本号(1表示AOAv1,2表示AOAv2)。
第4步:发送身份标识(请求码52)
配件发送请求码52,将自身标识信息传给Android设备。这些信息用于系统匹配对应的应用程序:
String ID
内容
是否必须
0
制造商名称 (Manufacturer)
必须
1
型号名称 (Model)
必须
2
描述 (Description)
可选
3
版本 (Version)
可选
4
URI (指向APK的链接)
可选
5
序列号 (Serial Number)
可选
每个字符串最大256字节(含结束符\0)。
复制代码
requestType: USB_DIR_OUT | USB_TYPE_VENDOR
request: 52
value: 0
index: String ID (0-5)
data: 以\0结尾的UTF-8字符串
第5步:启动配件模式(请求码53)
最后,配件发送请求码53,指示设备切换到配件模式:
复制代码
requestType: USB_DIR_OUT | USB_TYPE_VENDOR
request: 53
value: 0
index: 0
data: none
发送此请求后,设备会重新枚举(re-enumerate)------相当于USB重新连接,但此时设备将以Google的VID(0x18D1)和配件模式的PID重新出现在总线上。
第6步:建立通信
设备成功进入配件模式后,配件通过查询设备的接口描述符获取批量传输端点(Bulk Endpoints):
PID 0x2D00:1个接口,包含2个批量端点(IN + OUT)
PID 0x2D01:2个接口,每个接口2个批量端点(第1接口用于数据,第2接口用于ADB)
配件使用SET_CONFIGURATION(0x09)将设备配置设为1,然后即可通过批量端点收发数据。
六、代码实践:Host Mode完整示例
以下代码演示在Android应用侧通过UsbManager以主机模式(Host Mode) 连接自定义USB设备。
6.1 Manifest配置
xml
复制代码
package="com.example.usbhostdemo"> android:name="android.hardware.usb.host" android:required="true" /> android:launchMode="singleTop"> android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" />
res/xml/device_filter.xml:
xml
复制代码
vendor-id="1234" product-id="5678" /> 6.2 Host Mode核心通信代码 java 复制代码 package com.example.usbhostdemo; import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.widget.TextView; import android.widget.Toast; import java.util.HashMap; public class HostModeActivity extends Activity { private static final String TAG = "HostMode"; private static final String ACTION_USB_PERMISSION = "com.example.usbhostdemo.USB_PERMISSION"; private static final int TIMEOUT = 1000; private UsbManager mUsbManager; private UsbDevice mUsbDevice; private UsbDeviceConnection mConnection; private UsbInterface mInterface; private UsbEndpoint mEndpointIn; private UsbEndpoint mEndpointOut; private TextView mStatusTextView; private TextView mDataTextView; private Handler mMainHandler = new Handler(Looper.getMainLooper()); private volatile boolean mIsReading = false; private Thread mReadThread; // 权限请求广播接收器 private final BroadcastReceiver mPermissionReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (ACTION_USB_PERMISSION.equals(intent.getAction())) { UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { if (device != null) openDevice(device); } else { Toast.makeText(HostModeActivity.this, "USB权限被拒绝", Toast.LENGTH_SHORT).show(); } } } }; // 设备插拔监听 private final BroadcastReceiver mDeviceReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) requestDevicePermission(device); } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null && device.equals(mUsbDevice)) { closeDevice(); } } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_host_mode); mStatusTextView = findViewById(R.id.status_text); mDataTextView = findViewById(R.id.data_text); mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_USB_PERMISSION); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); registerReceiver(mPermissionReceiver, filter); registerReceiver(mDeviceReceiver, filter); } @Override protected void onResume() { super.onResume(); HashMap if (!deviceList.isEmpty()) { UsbDevice device = deviceList.values().iterator().next(); if (mUsbManager.hasPermission(device)) { openDevice(device); } else { requestDevicePermission(device); } } } private void requestDevicePermission(UsbDevice device) { PendingIntent pi = PendingIntent.getBroadcast( this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE ); mUsbManager.requestPermission(device, pi); } /** * Host Mode关键方法:通过UsbDevice打开连接 */ private void openDevice(UsbDevice device) { mUsbDevice = device; mInterface = device.getInterface(0); // 查找批量传输端点 for (int i = 0; i < mInterface.getEndpointCount(); i++) { UsbEndpoint ep = mInterface.getEndpoint(i); if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { if (ep.getDirection() == UsbConstants.USB_DIR_IN) { mEndpointIn = ep; } else { mEndpointOut = ep; } } } // Host Mode核心:通过openDevice()获取UsbDeviceConnection mConnection = mUsbManager.openDevice(device); if (mConnection == null) { mStatusTextView.setText("打开设备失败"); return; } // 声明接口(占用设备) if (!mConnection.claimInterface(mInterface, true)) { mConnection.close(); mConnection = null; mStatusTextView.setText("声明接口失败"); return; } mStatusTextView.setText("Host模式已连接"); startReading(); sendCommand(new byte[]{0x01, 0x00}); } /** * Host Mode发送数据:使用bulkTransfer */ public void sendCommand(byte[] data) { if (mConnection == null || mEndpointOut == null) return; new Thread(() -> { int transferred = mConnection.bulkTransfer( mEndpointOut, data, data.length, TIMEOUT ); mMainHandler.post(() -> mStatusTextView.setText("发送: " + transferred + "字节") ); }).start(); } /** * Host Mode接收数据:使用bulkTransfer轮询 */ private void startReading() { mIsReading = true; mReadThread = new Thread(() -> { byte[] buffer = new byte[64]; while (mIsReading && mConnection != null && mEndpointIn != null) { int len = mConnection.bulkTransfer( mEndpointIn, buffer, buffer.length, TIMEOUT ); if (len > 0) { final byte[] data = new byte[len]; System.arraycopy(buffer, 0, data, 0, len); mMainHandler.post(() -> mDataTextView.setText("接收: " + bytesToHex(data)) ); } } }); mReadThread.start(); } private void closeDevice() { mIsReading = false; if (mConnection != null) { mConnection.releaseInterface(mInterface); mConnection.close(); mConnection = null; } mStatusTextView.setText("已断开"); } private String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02X ", b)); return sb.toString().trim(); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mPermissionReceiver); unregisterReceiver(mDeviceReceiver); } } 七、代码实践:Accessory Mode完整示例 以下代码演示在Android应用侧如何通过UsbManager以配件模式(Accessory Mode) 连接外部配件。 7.1 Manifest配置 在AndroidManifest.xml中声明配件模式支持: xml 复制代码 android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" android:resource="@xml/accessory_filter" /> res/xml/accessory_filter.xml内容: xml 复制代码 manufacturer="Google, Inc." model="DemoKit" version="1.0" /> 7.2 权限管理 AOA使用独立于普通USB设备的权限机制,通过UsbManager.requestPermission()申请: java 复制代码 private static final String ACTION_USB_PERMISSION = "com.example.USB_PERMISSION"; private void requestAccessoryPermission(UsbAccessory accessory) { PendingIntent permissionIntent = PendingIntent.getBroadcast( this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE ); mUsbManager.requestPermission(accessory, permissionIntent); } 两种权限获取方式: Intent Filter方式 :在Manifest中声明USB_ACCESSORY_ATTACHED的intent-filter,用户连接配件时会被询问是否允许应用自动启动------勾选"默认使用"后可免去每次授权 动态请求方式 :在代码中通过requestPermission()主动申请 7.3 打开配件通信 获取权限后,通过UsbManager.openAccessory()获取ParcelFileDescriptor,进而获得输入/输出流: java 复制代码 private UsbManager mUsbManager; private UsbAccessory mAccessory; private ParcelFileDescriptor mFileDescriptor; private FileInputStream mInputStream; private FileOutputStream mOutputStream; private void openAccessory(UsbAccessory accessory) { // Accessory Mode核心:通过openAccessory()获取ParcelFileDescriptor mFileDescriptor = mUsbManager.openAccessory(accessory); if (mFileDescriptor != null) { mAccessory = accessory; FileDescriptor fd = mFileDescriptor.getFileDescriptor(); mInputStream = new FileInputStream(fd); mOutputStream = new FileOutputStream(fd); // 启动后台线程读取数据 new Thread(() -> { byte[] buffer = new byte[16384]; int len; try { while ((len = mInputStream.read(buffer)) >= 0) { // 处理接收到的数据 } } catch (IOException e) { // 处理异常 } }).start(); } } 7.4 发送与接收数据 发送数据: java 复制代码 public void sendCommand(String command) { if (mOutputStream != null) { try { mOutputStream.write(command.getBytes()); mOutputStream.flush(); } catch (IOException e) { // 处理异常 } } } 接收数据(在后台线程中轮询): java 复制代码 private void startReading() { new Thread(() -> { byte[] buffer = new byte[1024]; int len; try { while (true) { len = mInputStream.read(buffer); if (len > 0) { String received = new String(buffer, 0, len); // 通过Handler发送到UI线程处理 mHandler.obtainMessage(MSG_DATA, received).sendToTarget(); } } } catch (IOException e) { // 连接断开 } }).start(); } 7.5 处理断开事件 通过广播接收器监听断开事件: java 复制代码 private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) { UsbAccessory accessory = intent.getParcelableExtra( UsbManager.EXTRA_ACCESSORY ); if (accessory != null && accessory.equals(mAccessory)) { closeAccessory(); } } } }; 7.6 完整生命周期管理 在Activity生命周期中合理管理连接资源: java 复制代码 @Override protected void onResume() { super.onResume(); UsbAccessory[] accessories = mUsbManager.getAccessoryList(); if (accessories != null && accessories.length > 0) { UsbAccessory accessory = accessories[0]; if (mUsbManager.hasPermission(accessory)) { openAccessory(accessory); } else { requestAccessoryPermission(accessory); } } } @Override protected void onPause() { super.onPause(); closeAccessory(); } private void closeAccessory() { try { if (mFileDescriptor != null) { mFileDescriptor.close(); } } catch (IOException e) { // 处理异常 } finally { mFileDescriptor = null; mAccessory = null; mInputStream = null; mOutputStream = null; } } 八、使用场景总结 基于以上技术分析,AOA协议在实际中的应用场景可归纳为: 场景类型 适用模式 适用版本 典型应用 自定义USB外设通信 Host Mode --- 传感器数据采集、LED控制器、工业设备 标准USB外设 Host Mode --- U盘读写、键盘鼠标、游戏手柄 自定义数据通信 Accessory Mode AOAv1 机器人控制、传感器数据采集 ADB调试 Accessory Mode AOAv1 开发调试、自动化测试 音频外设 Accessory Mode AOAv2 车载音响底座、智能音箱扩展坞 HID输入设备 Accessory Mode AOAv2 游戏手柄、键盘、鼠标、遥控器 车载互联 Accessory Mode AOAv2 车载信息娱乐系统(如SmartDeviceLink方案)