1:簡介:
?????????? 自Android3.1(API Level 12)版本開始,Android系統(tǒng)直接支持USB配件(Accessory Mode)和USB主機(jī)(Host Mode)兩種模式支持各種USB外圍設(shè)備和Android USB配件(實現(xiàn)Android配件協(xié)議的硬件)戴尸。
注意:以上關(guān)于Android設(shè)備支持USB主機(jī)模式和從機(jī)模式的支持方式只是軟支持,另外還須有硬件支持付魔,并且硬件對兩種模式的支持具有決定性。
2:Android USB Host Mode(USB 主機(jī)模式)
????????? 在Android USB Host Mode模式下飞蹂,Android設(shè)備充當(dāng)主機(jī)几苍,設(shè)備包括數(shù)碼相機(jī)、鍵盤陈哑、鼠標(biāo)和游戲控制器等妻坝。針對各類應(yīng)用和環(huán)境設(shè)計的USB設(shè)備仍可與能夠與設(shè)備正常通信的Android應(yīng)用互動。
在USB主機(jī)模式下芥颈,我們的Android設(shè)備必須支持以下條件:
????????? 1惠勒、Android系統(tǒng)版本3.1(API Level 12)及以上。在概述中我們也提到爬坑,自Android3.1(API Level 12)開始才提供Android USB Host Mode的支持。
????????? 2涂臣、Android設(shè)備須支持OTG功能盾计。我們的智能手機(jī)和相機(jī)等移動設(shè)備本身是無法像PC那樣直接充當(dāng)USB Host為總線供電的,而OTG正是為解決此類問題而生赁遗,目前主流的Android手機(jī)和平板都已添加OTG模塊署辉。在主機(jī)模式下,我們將Android設(shè)備上的USB主機(jī)模塊又稱為USB嵌入式主機(jī)(Embedded Host 簡稱 EH)岩四。EH無法像PC上的USB主機(jī)一樣哭尝,為接入總線的未識別外圍設(shè)備加載驅(qū)動程序,所以EH設(shè)備提前在系統(tǒng)中對其目標(biāo)外圍設(shè)備列表TPL(Target Peripheral List)進(jìn)行了定義剖煌,在這些外圍USB設(shè)備中大部分是HID設(shè)備(Human Interface Device材鹦,如游戲手柄)、BOMS設(shè)備(Bulk Only Mass Storage耕姊,如讀卡器桶唐、U盤)和CDC設(shè)備(Communication Device Class,USB通信設(shè)備類茉兰,如打印機(jī)尤泽、相機(jī)),其驅(qū)動程序已存在于Android平臺的系統(tǒng)中(Linux Kernel),Android設(shè)備可以直接與這些設(shè)備直接通信坯约。
???????? 3熊咽、支持AOA協(xié)議。AOA協(xié)議(Android Open Accessory Protocol闹丐,Android開發(fā)配件協(xié)議)是Google公司推出的用于實現(xiàn)Android設(shè)備與外圍設(shè)備之間進(jìn)行USB通信的協(xié)議横殴,該協(xié)議拓展了Android設(shè)備USB接口的功能,為基于Android系統(tǒng)的智能設(shè)備應(yīng)用于設(shè)備控制和數(shù)據(jù)采集領(lǐng)域提供了條件妇智。關(guān)于AOA協(xié)議的固件源碼燒寫于硬件中滥玷,我們有時也把這項稱作為Android設(shè)備的硬件支持。(該條件僅在接入Android設(shè)備外圍設(shè)備是另一臺Android設(shè)備時需要巍棱,當(dāng)接入像U盤或打印機(jī)時惑畴,可不需要AOA協(xié)議的支持)
????????? 4、系統(tǒng)features.xml文件中提供了關(guān)于<uses-featureandroid:name="android.hardware.usb.host"/> 的定義航徙。在使用Android USB Host Mode開發(fā)時如贷,我們會在應(yīng)用程序的AndroidMainifast.xml文件中指定<uses-featureandroid:name="android.hardware.usb.host"/> 用來對不支持Android USB Host Mode的設(shè)備進(jìn)行過濾,Android系統(tǒng)版本3.1及以上默認(rèn)支持到踏,但是不排除設(shè)備商會對原生的Android系統(tǒng)進(jìn)行裁剪杠袱,可能會裁剪掉features.xml文件中關(guān)于<uses-featureandroid:name="android.hardware.usb.host"/>的定義,也就是說當(dāng)應(yīng)用程序安裝時來掃描系統(tǒng)的硬件支持feature時窝稿,發(fā)現(xiàn)沒有關(guān)于<uses-featureandroid:name="android.hardware.usb.host"/>的定義楣富,認(rèn)為該設(shè)備不支持Android USB Host Mode,也就會提示你無法安裝了伴榔。
3纹蝴、Android USB Accessory Mode(USB 從機(jī)模式)
????????? 在Android USB Accessory Mode模式下,外部USB硬件充當(dāng)USB主機(jī)踪少,配件包括機(jī)器人控制器塘安、擴(kuò)展塢、音樂設(shè)備援奢、自助服務(wù)終端兼犯、讀卡器等。不具備主機(jī)功能的Android設(shè)備就能夠與USB硬件互動集漾。Android USB配件必須設(shè)計為與Android設(shè)備兼容切黔,并且必須遵守Android配件通信協(xié)議。
? 在USB 從機(jī)模式下帆竹,我們的Android設(shè)備必須支持以下條件:
???????? 1绕娘、Android系統(tǒng)版本2.3.4(API Level 10)及以上。在概述中我們也提到栽连,自Android3.1(API Level 12)開始才直接提供對Android USB Accessory Mode的支持险领,同時Google提供的配件開發(fā)工具包ADK(Accessory Development Kit)提供了Android設(shè)備與Android配件通過USB通信的API侨舆,該ADK包能夠向后兼容至Android2.3.4系統(tǒng)版本。(Android 3.1及以上的系統(tǒng)之所以能夠直接支持也是因為系統(tǒng)直接封裝了ADK的API)
???????? 2绢陌、必須支持AOA協(xié)議挨下。在Android USB Accessory Mode中,Android設(shè)備必須支持AOA協(xié)議脐湾,因為當(dāng)Android設(shè)備以從機(jī)的方式接入Android配件時臭笆,Android配件會通過AOA協(xié)議檢測并初始化Android設(shè)備的USB通信的環(huán)境和啟動Android設(shè)備的USB從機(jī)模式。
???????? 3秤掌、系統(tǒng)features.xml文件中提供了關(guān)于<uses-featureandroid:name="android.hardware.usb.accessory"/> 的定義愁铺。關(guān)于該條件與Android USB Host Mode中的原因一致,這里不再贅述闻鉴。
4:Android USB Host Mode 通信開發(fā)指南:
4-1:USB設(shè)備管理器在不同版本系統(tǒng)中的獲取方式不同:
Android 2.3.4版本:UsbManager manager=UsbManager.getInstance(this);
Android 3.1版本:UsbManager manager=(UsbManager)getSystemService(Context.USB_SERVICE);
4-2:配置AndroidManifest.xml:
1).添加<uses-feature>元素來聲明您的應(yīng)用使用android.hardware.usb.host功能茵乱;
2).將應(yīng)用的最低SDK設(shè)置為API級別 12 或更高級別。USB主機(jī)API在更早的API級別中不存在孟岛。
3).如果您希望應(yīng)用接收有關(guān)連接的 USB 設(shè)備的通知瓶竭,請為主 Activity 中的 android.hardware.usb.action.USB_DEVICE_ATTACHED Intent 指定 <intent-filter> 和 <meta-data> 元素對。<meta-data> 元素指向外部 XML 資源文件渠羞,用于聲明有關(guān)要檢測的設(shè)備的標(biāo)識信息斤贰。在 XML 資源文件中,為要過濾的 USB 設(shè)備聲明 <usb-device> 元素次询。下表介紹了 <usb-device> 的屬性荧恍。一般來說,如果您想過濾某個特定設(shè)備屯吊,請使用供應(yīng)商 ID 和產(chǎn)品 ID块饺;如果您想過濾一組 USB 設(shè)備(例如大容量存儲設(shè)備或數(shù)碼相機(jī)),請使用類雌芽、子類和協(xié)議。您可以指定所有這些屬性辨嗽,也可以不指定任何屬性世落。如果不指定任何屬性,則會與每個 USB 設(shè)備進(jìn)行匹配糟需,因此只在應(yīng)用需要時才這樣做:vendor-id屉佳、product-id、class洲押、subclass武花、protocol(設(shè)備或接口)如下圖:
4-3:USB通信實現(xiàn)代碼封裝到UsbHidHelper類中:
?
/**
* Created by 631934797 on 2021/3/3
*/
public class UsbHidHelper {
private static final StringTAG = UsbHidHelper.class.getCanonicalName();
? ? private UsbManagermUsbManager;
? ? private UsbDeviceConnectionmUsbDeviceConnection;
? ? private UsbEndpointmUsbEndpointOut;
? ? private UsbEndpointmUsbEndpointIn;
? ? private UsbInterfacemUsbInterface;
? ? private static UsbHidHelpermInstance =null;
? ? private static ContextmContext;
? ? private boolean mToggle =true;
? ? private boolean isConnect =false;
? ? private ExecutorServicemThreadPool;
? ? private byte[]recvBuffer? =new byte[1024];
? ? private int mVendorID ;
? ? private int mProductID ;
? ? private int mFindCont =3;
? ? private final StringUSB_PERMISSION ="roy-lee.usb.permission";
? ? private PendingIntentmPrtPermissionIntent; //獲取外設(shè)權(quán)限的意圖
? ? /**
? ? * 獲取UsbHidHelper對象
? ? * @param context
? ? * @return
? ? */
? ? public static UsbHidHelper? getInstance(Context context) {
???????????? if(mInstance ==null) {
??????????? mInstance =new UsbHidHelper();
? ? ? ? ?? }
????? ?? mContext = context;
? ? ? ? return mInstance;
? ? }
/**
? ? * 初始化 USB設(shè)備
? ? *
? ? * @param vendorID
? ? * @param productID
? ? */
? ? public void initUsb_Hid(int vendorID, int productID){
??????? mVendorID = vendorID;
? ? ? ? mProductID = productID;
? ? ? ? // init UsbManager
? ? ? ? mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
? ? ? ? // 初始化線程池
? ? ? ? mThreadPool = Executors.newFixedThreadPool(5);
? ? ? ? // 注冊usb廣播
? ? ? ? registerReceiver();
? ? ? ? // 查找USB設(shè)備
? ? ? ? findUsbDevice();
? ? }
/**
? ? * 動態(tài)注冊usb廣播杈帐,拔插動作体箕,注冊動作
? ? * */
? ? private void registerReceiver(){
??????? //注冊在此service下的receiver的監(jiān)聽的action
? ? ? ? IntentFilter intentFilter =new IntentFilter();
? ? ? ? intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
? ? ? ? intentFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
? ? ? ? intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
? ? ? ? intentFilter.addAction(USB_PERMISSION);
? ? ? ? mContext.registerReceiver(usbReceiver, intentFilter);//注冊receiver
? ? ? ? //通知監(jiān)聽外設(shè)權(quán)限注冊狀態(tài)
? ? ? ? //PendingIntent:連接外設(shè)的intent
?????? //ask permission
? ? ? ? mPrtPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(USB_PERMISSION), 0);
? ? }
private BroadcastReceiverusbReceiver =new BroadcastReceiver() {
??????? @Override
? ? ? ? public void onReceive(Context context, Intent intent) {
???????????????? if (intent ==null) {
????????????????????????? return;
? ? ? ? ? ? ? ??? }
??????????????? String action = intent.getAction();
? ? ? ? ? ? ? ? switch (action){
?????????????????????? // USB注冊動作
? ? ? ? ? ? ? ??????? case USB_PERMISSION:
????????????????????????????? synchronized (this) {
??????????????????????????????????????? if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
UsbDevice parcelableExtra = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (parcelableExtra !=null) {
// 連接設(shè)備
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? connectDevice(parcelableExtra);
? ? ? ? ? ? ? ? ? ? ? ? ? ? }else {
Log.e(TAG,"usb device suddenly disappera.");
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Log.e(TAG,"...USB外設(shè)意外消失...");
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
}else {
Log.e(TAG,"usb permission granted fail.");
? ? ? ? ? ? ? ? ? ? ? ? ? ? Log.e(TAG,"...USB權(quán)限注冊失敗...");
? ? ? ? ? ? ? ? ? ? ? ? ? ? mFindCont--;
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (mFindCont >0){
findUsbDevice();
? ? ? ? ? ? ? ? ? ? ? ? ? ? }
}
}
break;
? ? ? ? ? ? ? ? // USB插入動作
? ? ? ? ? ? ? ? case UsbManager.ACTION_USB_ACCESSORY_ATTACHED:
case UsbManager.ACTION_USB_DEVICE_ATTACHED:
Log.i(TAG,"...USB 插入...");
? ? ? ? ? ? ? ? ? ? mFindCont =3;
? ? ? ? ? ? ? ? ? ? findUsbDevice();
break;
? ? ? ? ? ? ? ? // USB拔出動作
? ? ? ? ? ? ? ? case UsbManager.ACTION_USB_DEVICE_DETACHED:
Log.e(TAG,"...USB 已被拔出...");
? ? ? ? ? ? ? ? ? ? mToggle =true;
? ? ? ? ? ? ? ? ? ? isConnect =false;
break;
? ? ? ? ? ? }
}
};
? ? /**
? ? *? 查找設(shè)備
? ? */
? ? private boolean findUsbDevice(){
mThreadPool.execute(new Runnable() {
@Override
? ? ? ? ? ? public void run() {
//? ? ? ? ? ? ? ? while (mToggle && mFindCont > 0) {
? ? ? ? ? ? ? ? ? ? Log.d(TAG, "...查找USB設(shè)備...");
? ? ? ? ? ? ? ? ? ? HashMap deviceList =mUsbManager.getDeviceList();
? ? ? ? ? ? ? ? ? ? Collection values = deviceList.values();
? ? ? ? ? ? ? ? ? ? if (!values.isEmpty()) {
for (UsbDevice usbDevice : values) {
// 輸出設(shè)備信息
? ? ? ? ? ? ? ? ? ? ? ? ? ? Log.e(TAG,"mVendorID : "+mVendorID +"? mProductID : " +mProductID);
? ? ? ? ? ? ? ? ? ? ? ? ? ? Log.d(TAG, "設(shè)備ID: vid = " + String.format("%x", usbDevice.getVendorId()) +" , pid = " + String.format("%x", usbDevice.getProductId()));
? ? ? ? ? ? ? ? ? ? ? ? ? ? int vendorId = usbDevice.getVendorId();
? ? ? ? ? ? ? ? ? ? ? ? ? ? int productId = usbDevice.getProductId();
? ? ? ? ? ? ? ? ? ? ? ? ? ? if (vendorId ==mVendorID && productId ==mProductID) {
Log.d(TAG, "...枚舉SUB設(shè)備成功...");
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 獲取權(quán)限
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (mUsbManager.hasPermission(usbDevice)) {
// 建立連接
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? connectDevice(usbDevice);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }else {
Log.e(TAG,"...申請USB權(quán)限失敗...");
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? mUsbManager.requestPermission(usbDevice, mPrtPermissionIntent);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
}
}
}else {
// TODO 沒有USB設(shè)備
? ? ? ? ? ? ? ? ? ? ? ? Log.e(TAG,"...沒有USB設(shè)備...");
? ? ? ? ? ? ? ? ? ? }
//? ? ? ? ? ? ? ? ? ? SystemClock.sleep(10000);
//? ? ? ? ? ? ? ? ? ? mFindCont--;
//? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
});
? ? ? ? return isConnect;
? ? }
/**
? ? * 連接設(shè)備
? ? * @param usbDevice
? ? */
? ? private void connectDevice(UsbDevice usbDevice){
Log.e(TAG,"...申請USB權(quán)限成功...");
? ? ? ? Log.e(TAG,"UsbDevice :" + usbDevice);
? ? ? ? // 打開設(shè)備
? ? ? ? UsbDeviceConnection conn=mUsbManager.openDevice(usbDevice);
? ? ? ? if (conn !=null) {
mUsbInterface = usbDevice.getInterface(0);
? ? ? ? ? ? if (conn.claimInterface(mUsbInterface, true)){
mUsbDeviceConnection = conn;
? ? ? ? ? ? ? ? int endpointCount =mUsbInterface.getEndpointCount();
? ? ? ? ? ? ? ? for (int i =0; i < endpointCount; i++) {
UsbEndpoint usbEndpoint =mUsbInterface.getEndpoint(i);
? ? ? ? ? ? ? ? ? ? Log.e(TAG,"Type: "+ usbEndpoint.getType());
? ? ? ? ? ? ? ? ? ? Log.e(TAG,"Direction: "+ usbEndpoint.getDirection());
? ? ? ? ? ? ? ? ? ? // TODO USB 4種傳輸模式专钉,根據(jù)自己實際通信需求自行更改
? ? ? ? ? ? ? ? ? ? if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT) {
if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
mUsbEndpointOut = usbEndpoint;
? ? ? ? ? ? ? ? ? ? ? ? }else if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
mUsbEndpointIn = usbEndpoint;
? ? ? ? ? ? ? ? ? ? ? ? }
}
}
if (mUsbEndpointOut !=null &&mUsbEndpointIn !=null) {
// TODO 連接USB設(shè)置成功
? ? ? ? ? ? ? ? ? ? Log.i(TAG,"connected success");
? ? ? ? ? ? ? ? ? ? mToggle =false;
? ? ? ? ? ? ? ? ? ? isConnect =true;
? ? ? ? ? ? ? ? ? ? onLoopSendData();
? ? ? ? ? ? ? ? }
}
}
}
/**
? ? * 發(fā)送HID數(shù)據(jù)
? ? *
? ? * @param messageContent
? ? * @return
? ? */
? ? public boolean sendHidData(String messageContent){
return sendHidData(messageContent.getBytes());
? ? }
public boolean sendHidData(final byte[] contentBytes){
final boolean[] states = {false};
? ? ? ? mThreadPool.execute(new Runnable() {
@Override
? ? ? ? ? ? public void run() {
if (mUsbDeviceConnection !=null &&mUsbEndpointOut !=null) {
/**
? ? ? ? ? ? ? ? ? ? * 發(fā)送數(shù)據(jù)的地方 , 只接受byte數(shù)據(jù)類型的數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? */
? ? ? ? ? ? ? ? ? ? int i =mUsbDeviceConnection.bulkTransfer(mUsbEndpointOut, contentBytes, contentBytes.length, 2000);
? ? ? ? ? ? ? ? ? ? if (i >0) {
Log.i(TAG,"發(fā) ==> ◇: " + HexDump.byteTo16String(contentBytes) +"\r\n");
? ? ? ? ? ? ? ? ? ? ? ? states[0] =true;
? ? ? ? ? ? ? ? ? ? }else {
Log.e(TAG,"...數(shù)據(jù)發(fā)送失敗...");
? ? ? ? ? ? ? ? ? ? }
}else {
Log.e(TAG,"...請先連接USB...");
? ? ? ? ? ? ? ? }
}
});
? ? ? ? return states[0];
? ? }
/**
? ? * 接收HID數(shù)據(jù)
? ? *
? ? * @param callback
? ? */
? ? public void receivedHidNewData(final HidDataCallback callback){
mThreadPool.execute(new Runnable() {
@Override
? ? ? ? ? ? public void run() {
while (true) {
/**
? ? ? ? ? ? ? ? ? ? * 循環(huán)接受數(shù)據(jù)的地方 , 只接受byte數(shù)據(jù)類型的數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? */
? ? ? ? ? ? ? ? ? ? if (mUsbDeviceConnection !=null &&mUsbEndpointIn !=null &&isConnect) {
int i =mUsbDeviceConnection.bulkTransfer(mUsbEndpointIn, recvBuffer, 64, 1000);
? ? ? ? ? ? ? ? ? ? ? ? if (i >0) {
byte[] subArray = HexDump.getSubArray(recvBuffer, 0, i);
? ? ? ? ? ? ? ? ? ? ? ? ? ? Log.i(TAG,"收 <== ◆: "+HexDump.byteTo16String(subArray) +"\r\n");
? ? ? ? ? ? ? ? ? ? ? ? ? ? if(callback !=null)
callback.onReceiveHidData(subArray);
? ? ? ? ? ? ? ? ? ? ? ? }
}
SystemClock.sleep(10);
? ? ? ? ? ? ? ? }
}
});
? ? }
}