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设备都支持

应用可通过在Manifest中声明对配件模式的需求

二、核心概念: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过滤器

匹配VID/PID或接口

匹配制造商/型号

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 deviceList = mUsbManager.getDeviceList();

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方案)