天天看點

Android USB Host開發筆記

最近在做有關Android TV端使用USB外設進行使用者互動方面的開發。總結一下開發過程中的困惑、解決方案以及開發的整個過程:

一、做USB Host開發前的準備工作:

1. “工欲善其事必先利其器”,先簡單了解一下USB Host:

1> USB Device:從硬體角度看就是一個帶有USB Client控制器的裝置;從軟體角度看,就是一個挂在USB總線上的一個普通意義上的裝置,隻不過它們的驅動是基于Host驅動之上的。

2> USB Host:USB主裝置,可以從另外一個USB Device中取得資料,包括USB Host控制器和USB Host協定。與USB Client裝置和USB Slave協定相對應。

3> USB Client:從硬體角度看就是指USB Device,從軟體角度看,就是指USB Client協定。

4> USB OTG:On The Go,正在進行中的意思,也就是可以直接傳輸,就是可以從一個機器直接傳到另一個機器中。

5> USB HUB:USB擴充/集線器,一種可以将一個USB接口擴充為多個(通常為4個),并可以使這些接口同時使用的裝置。

我總結一下:Android TV上提供USB插口,Android TV是USB Host(包含協定,是主裝置),插在插口上的USB Device為USB Client。如果一個插口上插了個USB分線器,這個是USB HUB。USB Client與USB Host之間進行資料交換叫做OTG。

2. Check your device surpport USB Host or not.

怎麼檢查呢?

1> 使用ES或RE檔案浏覽器,進入 /system/etc/permissions 目錄下,檢視是否有 android.hardware.usb.host.xml 檔案。

2> 若有那麼OK。沒有的話自己建立一個同名的檔案,在檔案中寫入:

<permissions>
            <feature name="android.hardware.usb.host />
     </permissions>
           

3> 最好檢查一下: /system/etc/permissions 目錄下

手機:handheld_core_hardware.xml

平闆/TV:tablelet_core_hardware.xml

檔案中的< permissions >< /permissions >節點下是否第2>點中的那句話,沒有的話可以考慮加一下。(Google官方說不加且有第1>點中的檔案是沒問題的。)

二、USB Host 的Android工程Demo編寫

  1. 建立一個Android 工程,在清單配置檔案中設定應用的USB Host權限(注意:此處是應用的權限,之前第一點是裝置的硬體支援)
<uses-feature
        android:name="android.hardware.usb.host"
        android:required="true" />

<uses-permission android:name="android.hardware.usb.host" />
<uses-permission android:name="android.hardware.usb.accessory" />
           

另外,需要在< application>的某個< activity>節點中添加意圖過濾(Intent-Fliter)、中繼資料(meta-data),具體如下:

<intent-filter>
    <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" />
           

注意:此處是重點:

1) 意圖過濾表示的是 硬體USB裝置依附的動作意圖;

2) 中繼資料表示的是 關聯res/xml檔案夾下的device_filter.xml檔案,是以需要在對應的目錄下建立這個檔案,并且在此檔案中編寫代碼(表示的是你的應用要擷取的指定的USB裝置的資料),我的是這樣的:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="11021" product-id="257" />
    <usb-device vendor-id="11020" product-id="513" />
</resources>
           

這裡的vendor_id也好、 product_id也好,一定要寫你自己需要擷取資料的裝置的id。

2.Java代碼部分。

1) 需要知道的幾個類:UsbManager、UsbDevice、UsbInterface、UsbDeviceConnection、UsbEndPoint這幾個類。

大概介紹一下(想詳細了解可以參考AndroidDevelopers官方網站):

UsbManager:USB管理器,是獲得USB裝置,進行資料交換的最基本的類。

UsbDevice:表示USB裝置的一個類。

UsbDeviceConnection:表示USB裝置連接配接的類

UsbInterface:連接配接的USB裝置的接口(類似Channel),表示資料是從哪個通道傳遞過來的。下面是一些通用的基本操作:

// 建立USB管理器對象
UsbManager mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
// 獲得被USB管理器管理的所有裝置
HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
// 檢視管理器是否對某個USB裝置有管理權限
mUsbManager.hasPermission(mUsbDevice)
// 獲得USB資料傳輸的通道
UsbInterface intf = mUsbDevice.getInterface(index);
           

2) 資料接收類(根據業務需求封裝的類) —— HidUsb.java

重磅幹貨來襲!!!

public class HidUsb {
    private static final String TAG = HidUsb.class.getSimpleName();
    private Context mContext;
    private UsbManager mUsbManager;
    private UsbDevice mUsbDevice;
    private UsbInterface mInterface;
    private UsbDeviceConnection mDeviceConnection;
    private UsbEndpoint mEpOut;
    private UsbEndpoint mEpIn;
    //主線程消息處理器
    private Handler mHandler;
    //工作線程消息處理器
    private HandlerThread mHandlerThread;
    private HidListener mListener;
    private Handler mHidHandler;

    public HidUsb(Context context) { 
        Log.i(TAG, "The construction method of HidUsb had been executed!");
        mContext = context;
        mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
        start();
    }

    public void release() {
        stop();
    }

    /**
     * 此方法用來設定監聽器
     *
     * @param listener
     */
    public void setListener(HidListener listener) {
        mListener = listener;
    }

    /**
     * 此方法用來開啟HID
     */
    public void openHID() {
        // 關閉HID
        closeHID();
        // 判斷是否是要求的裝置
        enumerateDevice();
        // 判斷是否有使用USB HOST的權限。
        if (hasPermission()) {
            // 有權限則連接配接HID裝置
            // TODO 此處仍有提升空間connectHID() 和 openDevice() 方法都傳回布爾值,傳回false需要進行什麼處理
            connectHID();
        } else {
            // 沒有權限則獲得權限
            obtainPermission();
        }
    }

    /**
     * 此方法用來連接配接HID裝置
     */
    private boolean connectHID() {
        // 找到接口,将找到的指定接口的代号賦給成員變量mInterface
        findInterface();
        // 配置設定端節點,給端節點指派
        assignEndpoint();
        // 傳回打開HID裝置
        return openDevice();
    }

    public void closeHID() {
        if (mDeviceConnection != null) {
            mDeviceConnection.releaseInterface(mInterface);
            mDeviceConnection.close();
        }
        mDeviceConnection = null;
        mUsbDevice = null;
        mInterface = null;
    }


    /**
     * 查找device,找到指定的HidUsb裝置
     */
    private void enumerateDevice() {
        Log.v(TAG, "enumerateDevice.");
        if (mUsbManager != null) {
            Log.d(TAG, "enumerateDevice2");
            HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();

            if (!deviceList.isEmpty()) {
                Iterator deviceIterator = deviceList.values().iterator();

                while (deviceIterator.hasNext()) {
                    UsbDevice device = (UsbDevice) deviceIterator.next();
                    Log.v(TAG, "device = " + device);
                    // 獲得USB裝置清單,判斷是否是要求的USB裝置
                    if (isYourRequiredDevice(device)) {
                        // 是要求的USB 裝置的情況下,将此裝置指派給成員變量 mUsbDevice
                        mUsbDevice = device;
                        Log.d(TAG, "enumerateDevice: Get Device OK.");
                        break;
                    }
                }
            } else {
                Log.e(TAG, "enumerateDevice: Device list is null !!!");
            }
        } else {
            Log.e(TAG, "enumerateDevice: USB manager is null !!!");
        }
        // 否則報出錯誤是裝置是空、或者裝置管理器是空等
    }

    /**
     * 是否有權限
     *
     * @return true if caller has permission
     */
    private boolean hasPermission() {
        if (mUsbDevice != null) {
            return mUsbManager.hasPermission(mUsbDevice);
        }
        return false;
    }

    /**
     * 擷取權限
     */
    private void obtainPermission() {
        if (mUsbDevice != null) {
            PendingIntent mPermissionIntent = PendingIntent.getBroadcast(mContext, , new Intent(Constants.ACTION_USB_PERMISSION), );
            mUsbManager.requestPermission(mUsbDevice, mPermissionIntent);
        }
    }

    private void findInterface() {
        // 如果成員變量mUsbDevice不是空(參考enumerateDevice方法)時,
        // 獲得Usb裝置的接口(通道)的數目,在所有接口中找到
        if (mUsbDevice != null) {
            int intfCount = mUsbDevice.getInterfaceCount();
            for (int i = ; i < intfCount; ++i) {
                UsbInterface intf = mUsbDevice.getInterface(i);
                int intfClass = intf.getInterfaceClass();
                int intfSubClass = intf.getInterfaceSubclass();
                int intfProtocol = intf.getInterfaceProtocol();
                if (是你要的通道) {
                   // TODO
                }
            }
        }
    }

    private void assignEndpoint() {
        // 如果指定的端口代号不是空,獲得EndPoint的數目
        // 找出指定的端節點,設定成員變量EndPointOut/EndPointIn
        if (mInterface != null) {
            int epCount = mInterface.getEndpointCount();
            for (int i = ; i < epCount; ++i) {
                UsbEndpoint ep = mInterface.getEndpoint(i);
                int epType = ep.getType();
                int epDir = ep.getDirection();
                if (epType == 指定的端節點) {
                    // TODO 
                }
            }
        }
    }

    /**
     * 打開device
     *
     * if(接口代号不為空){
     *     if(USB裝置管理器有制定裝置的管理權限){
     *         初始化USB裝置連接配接;
     *     }
     *
     *     // 表示有權限或者沒權限初始化了連接配接
     *     if(USB裝置連接配接仍然為空){
     *         說明無法連接配接指定的USB裝置;
     *         return false;
     *     }
     *
     *     // 表示連接配接不是空
     *     if(申明接口){
     *         重新發送消息,執行任務;
     *         return true;
     *     }
     *
     * }else{
     *     // 表示接口代号為空
     *     if(連接配接不為空){
     *         // 表示連接配接了裝置,這個裝置卻沒有接收資料的接口
     *         關閉連接配接;
     *         return false;
     *     }
     *
     *     return false;
     * }
     */
    private boolean openDevice() {
        if (mInterface != null) {
            UsbDeviceConnection conn = null;
            if (mUsbManager.hasPermission(mUsbDevice)) {
                conn = mUsbManager.openDevice(mUsbDevice);
            }
            Log.d(TAG, "conn = " + conn);
            if (conn == null) {
                Log.e(TAG, "open device null!!!");
                return false;
            }
            if (conn.claimInterface(mInterface, true)) {
                mDeviceConnection = conn;
                Log.d(TAG, "open device OK.");
                mHandler.removeCallbacks(mRunnable);
                mHandler.post(mRunnable);
                return true;
            } else {
                if (conn != null) {
                    conn.close();
                }
                Log.e(TAG, "open device Error!!!");
            }
        }
        return false;
    }

    private void start() { 
        Log.i(TAG, "start method running!");
        // 1.建立了一個關聯目前線程(主線程)的Handler
        mHandler = new Handler();
        // 2.建立了一個自帶Handler的工作線程,并啟動線程
        mHandlerThread = new HandlerThread("Hid_Thread");
        mHandlerThread.start();
        // 3.獲得了工作線程的Handler對象
        mHidHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    // 收到GET_DATA_MSG消息時執行
                    case GET_DATA_MSG:
                        try {
                            // 調用getData方法
                            final long gd = getData();
                            // 判斷獲得的資料不為0,監聽器不為空,主線程的Handler不為空時執行
                            // 向主線程發送消息,調用監聽器的onCodeReceive方法
                            if (gd !=  && mListener != null && mHandler != null) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        mListener.onCodeReceive(gd);
                                    }
                                });
                            }
                        } catch (NullPointerException e) {
                            // 抓去空指針異常
                            e.printStackTrace();
                            // 休眠50毫秒
                            Thread.sleep();
                        } catch (InterruptedException e2) {
                                e2.printStackTrace();
                        }

                        // 4.移除主線程任務隊列中的可執行任務
                        if(mHandler!=null) {
                            mHandler.removeCallbacks(mRunnable);
                            // 5.向主消息隊列中放置任務
                            mHandler.post(mRunnable);
                        }
                        break;
                    default:
                        break;
                }
            }
        };
    }

    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            // 當主線程Handler不為空時執行清空消息隊列中的消息,并發送GET_DATA_MSG消息。
            if (mHidHandler != null) {
                mHidHandler.removeMessages(GET_DATA_MSG);
                mHidHandler.sendEmptyMessage(GET_DATA_MSG);
            }
        }
    };


    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void stop() {
        mHandler.removeCallbacks(mRunnable);
        mHandler = null;
        if (mHandlerThread != null) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
                mHandlerThread.quit();
            } else {
                mHandlerThread.quitSafely();
            }
            mHandlerThread = null;
            mHidHandler = null;
        }
    }

    private long getData() {
       return 你從底層獲得的資料
    }

    private boolean isYourRequiredDevice(UsbDevice device) {
        return 根據vendorid productid判斷是否是你要的USB裝置
    }

    /**
     * 這是一個監聽器接收資料的接口
     */
    public interface HidListener {
        void onCodeReceive(long code);
    }
}
           

3.在頁面中獲得資料 MainActivity.java

public class  MainActivity extends Activity implements HidUsb.HidListener {
    private static final String TAG = MainActivity.class.getSimpleName();

    private HidUsb mHidUsb;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        onInitUsb();
    }

    private void onInitUsb() {
        mHidUsb = new HidUsb(this);
        mHidUsb.setListener(this);
        mHidUsb.openHID();
    }

    @Override
    public void onCodeReceive(long code) {
        // TODO
    }
}
           

我覺得注釋寫的還算清楚就不解釋了~~~

————————————————————————