Android USB 開發詳解
先附上 Android USB 官方文檔
Android通過兩種模式支持各種 USB 外設和 Android USB 附件(實現Android附件協議的硬件):USB附件和USB主機。USB開發需 Android 3.1(API級別12)以上。由於本人工作中只用到了主機模式,所以本文的側重點在主機模式開發。
調試
在使用 USB 連接設備調試的時候,USB 外設將不能連接至設備,可以使用 WIFI 的方式連接調試,settings -> plugin -> WiFi ADB 插件好幾個,選個適合自己的就 OK。
或者…我這里正好有一篇Android 模擬器連接 USB 設備喜歡點個贊哈!
一、AndroidManifest 文件設置
- uses-feature 申明這個軟件需要使用 USB 功能,申明這個 Google Play 會把不滿足的設備過濾掉,一般用 USB 的都是定制開發,忽略就行
<uses-feature android:name="android.hardware.usb.host"/>
- 1
- 將應用程序的最低SDK設置為API級別12或更高,早期 API 沒有。
- 如果你希望你的應用程序連接指定的 USB 設備時被通知,需指定 和 元素對用於 android.hardware.usb.action.USB_DEVICE_ATTACHED。該 元素指向聲明識別有關您要檢測的設備信息的外部XML資源文件。
<activity android:name=".MainActivity" android:screenOrientation="landscape"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <!-- 如果這里是啟動 Activity 的話,點擊 USB 接入的彈窗會啟動該頁面 --> <category android:name="android.intent.category.LAUNCHER" /> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </activity>
- 在XML資源文件中,聲明要過濾的USB設備的元素。以下列表描述了屬性 。通常,如果要過濾特定設備並使用類,子類和協議(如果要過濾一組USB設備(如大容量存儲設備或數碼相機)),請使用供應商(vendor-id)和產品(product-id)ID,在開發中這些過濾ID一般可以在文檔中找到,或者自己連上看也行。你可以指定部分或全部這些屬性。
將資源文件保存在res/xml/目錄中。資源文件名(不帶.xml擴展名)必須與您在元素中指定的文件名相同 。對於XML資源文件格式的 例子如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device class="255" product-id="5678" protocol="1 " subclass="66" vendor-id="1234" /> </resources>
配置好清單文件后當用戶連接與您的設備過濾器匹配的設備時,系統會向他們顯示一個對話框,詢問他們是否要啟動您的應用程序。如果用戶接受,則應用程序將自動具有訪問設備的權限,直到設備斷開連接。如果給了默認,那么這個 USB 設備插入后會自動啟動這個 Activity
二、USB 設備的連接和使用
在清單文件中配置好以后我們直接進入 Java 代碼環節
1.Android 中的 USB
Android 3.1(API級別12)以上原生提供了 USB 開發的 API,在android.hardware.usb包下提供了開發的相關類。
Class | 說明 |
---|---|
UsbManager | 獲得 USB 管理器,與連接的 USB 設備通信。 |
UsbDevice | USB 設備的抽象,每個UsbDevice 都代表一個 USB 設備。 |
UsbInterface | 定義了設備的功能集,一個 UsbDevice 可能包含一個或多個UsbInterface,每個 Interface 都是獨立的。 |
UsbEndpoint | UsbEndpoint 是 interface 的通信通道。 |
UsbDeviceConnection | host 與 device 建立的連接,並在 endpoint 傳輸數據。 |
UsbRequest | USB 請求包。 |
UsbConstants | USB 常量的定義 |
2.USB 設備的插入
Android 系統中,USB 設備的插入和拔出是以系統廣播的形式發送的,我們只要注冊監聽這個廣播就好
public class USBReceiver extends BroadcastReceiver { public static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { // 獲取權限結果的廣播 synchronized (this) { UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { //call method to set up device communication if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { Log.e("USBReceiver", "獲取權限成功:" + device.getDeviceName()); } else { Log.e("USBReceiver", "獲取權限失敗:" + device.getDeviceName()); } } } }else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { // 有新的設備插入了,在這里一般會判斷這個設備是不是我們想要的,是的話就去請求權限 } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { // 有設備拔出了 } } }
3.獲取 UsbManager
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
4.獲取 USB 設備列表
public List<UsbDevice> getDeviceList() { HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); List<UsbDevice> usbDevices = new ArrayList<>(); while (deviceIterator.hasNext()) { UsbDevice device = deviceIterator.next(); usbDevices.add(device); Log.e("USBUtil", "getDeviceList: " + device.getDeviceName()); } return usbDevices; }
5.獲取特定的設備
/** * mVendorId=1137,mProductId=85 佳博 3150T 標簽打印機 * * @param vendorId 廠商ID * @param productId 產品ID * @return device */ public UsbDevice getUsbDevice(int vendorId, int productId) { HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList(); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); while (deviceIterator.hasNext()) { UsbDevice device = deviceIterator.next(); if (device.getVendorId() == vendorId && device.getProductId() == productId) { Log.e("USBUtil", "getDeviceList: " + device.getDeviceName()); return device; } } Toast.makeText(context, "沒有對應的設備", Toast.LENGTH_SHORT).show(); return null; }
6.申請 USB 設備使用權限
安卓系統對 USB 設備的使用需要得到相應的權限,這個權限要用戶手動授予,或插入設備時應用到你的應用中。在使用 USB 設備前首先我們要確認一下上一節中的device是否已經獲得權限,如果沒有就要主動申請權限:
/** * 判斷對應 USB 設備是否有權限 */ public boolean hasPermission(UsbDevice device) { return usbManager.hasPermission(device); } /** * 請求獲取指定 USB 設備的權限 */ public void requestPermission(UsbDevice device) { if (device != null) { if (usbManager.hasPermission(device)) { Toast.makeText(context, "已經獲取到權限", Toast.LENGTH_SHORT).show(); } else { if (mPermissionIntent != null) { usbManager.requestPermission(device, mPermissionIntent); Toast.makeText(context, "請求USB權限", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "請注冊USB廣播", Toast.LENGTH_LONG).show(); } } } }
注冊廣播:
public void registerReceiver(Activity context) { mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); context.registerReceiver(usbReceiver, filter); }
7.通信
與 USB 設備的通信可以是同步的也可以是異步的。無論哪種情況,你都應該創建一個新線程來執行所有數據傳輸,避免阻塞UI線程。
第一步,打開通信端口
public boolean openPort(UsbDevice device) { //獲取設備接口,一般只有一個,多個的自己研究去 usbInterface = device.getInterface(0); // 判斷是否有權限 if (hasPermission(device)) { // 打開設備,獲取 UsbDeviceConnection 對象,連接設備,用於后面的通訊 usbConnection = usbManager.openDevice(device); if (usbConnection == null) { return false; } if (usbConnection.claimInterface(usbInterface, true)) { Toast.makeText(Utils.getContext(), "找到 USB 設備接口", Toast.LENGTH_SHORT).show(); } else { usbConnection.close(); Toast.makeText(Utils.getContext(), "沒有找到 USB 設備接口", Toast.LENGTH_SHORT).show(); return false; } } else { Toast.makeText(Utils.getContext(), "沒有 USB 權限", Toast.LENGTH_SHORT).show(); return false; } //獲取接口上的兩個端點,分別對應 OUT 和 IN for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { UsbEndpoint end = usbInterface.getEndpoint(i); if (end.getDirection() == UsbConstants.USB_DIR_IN) { usbEndpointIn = end; } else { usbEndpointOut = end; } } return true; }
第二步,發送數據
usbConnection.bulkTransfer(usbEndpointOut, bytes, bytes.length, 500);
其他
剩余的 API 我會在項目不斷完善的同時更新上來
附:demo 傳送門