1. 程式人生 > >Android usb學習筆記:Android AOA協議Android端 流程總結

Android usb學習筆記:Android AOA協議Android端 流程總結

背景

上篇文章中我們瞭解了嵌入式裝置端將Android手機設定為accessory模式的流程以及嵌入式裝置端接收和傳送資料的流程,本文將對應介紹Android端accessory模式被啟用的過程,以及接下來如何與嵌入式裝置端進行通訊。本文的原始碼下載地址:https://git.oschina.net/vonchenchen/aoa_android.git

實現

USBConnStatusManager 底層啟動accessory模式

Android系統api通過UsbManager類管理usb相關,這裡我們關注一下與accessory模式相關的內容。
當裝置端啟動android的accessory模式時,系統將會發送一條廣播,裝置拔出時也會發送一條廣播,同時還有一條申請usb使用許可權的廣播。所以,要做的第一步就是動態註冊這些廣播,並編寫一個廣播接收者來處理對應的事件。這裡對於的方法我們封裝到了USBConnStatusManager類中,用來管理accessory相關連線。

IntentFilter filter = new IntentFilter();
        //接收許可權資訊
        filter.addAction(ACTION_USB_PERMISSION);
        //接收accessory連線事件
        filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
        //接收accessory斷開事件
        filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
        getContext().registerReceiver(mUsbReceiver, filter
);

下面是對應的廣播接收者,這裡其實只需要監聽兩個廣播,一個是獲取usb許可權,一旦這個廣播發出我們就可以認為裝置現在正在啟動手機的accessory模式,第一次連線時手機會彈出對話方塊,讓我們選擇是否執行usb許可權,另外一個就是需要在usb斷開時做出反應,告訴裝置連線已經斷開了。下面是處理廣播事件的過程:

mUsbReceiver = new BroadcastReceiver() {

            @Override
            public void onReceive(Context context, Intent intent) {
                String
action = intent.getAction(); Log.i(TAG, "receive usb connect broadcast:" + action); if (ACTION_USB_PERMISSION.equals(action)) { synchronized (this) { //UsbAccessory accessory = UsbManager.getAccessory(intent); UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); //獲取accessory控制代碼成功 if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { Log.d(TAG, "prepare to open usb stream"); sCurStatus = STATUS_CONN_OK; mUsbAccessory = accessory; if (mOnUSBConnStatusChanged != null) { mOnUSBConnStatusChanged.onUSBConnect(accessory); } } else { Log.d(TAG, "permission denied for accessory " + accessory); sCurStatus = STATUS_CONN_ERR; mUsbAccessory = null; if (mOnUSBConnStatusChanged != null) { mOnUSBConnStatusChanged.onUSBConnectFailed(accessory); } } } } else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) { UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); //if (accessory != null && accessory.equals(mAccessory)) { //檢測到usb斷開 Log.d(TAG, "USB_ACCESSORY_DETACHED " + accessory); sCurStatus = STATUS_DISCONN; mUsbAccessory = null; //closeAccessory(); //synchronized (USBConnStatusManager.class) { if (mOnUSBConnStatusChanged != null) { mOnUSBConnStatusChanged.onUSBDisconnect(accessory); } //} //} } } };

這裡拿到accessory的引用以後就可以用這個引用獲取usb的讀寫流,然後將accessory交給外部介面,由外部類處理資料的具體讀寫內容。如果接收到裝置拔出廣播,則手動釋放引用,更新連線狀態。
另外,如果裝置已經插入並且處於accessory模式,廣播接受者並不會呼叫,這時可以同步開啟裝置。可以檢查mUsbManager.getAccessoryList(),如果有accessory裝置則可以直接獲取裝置引用。由於一般手機都只有一個U口,此處預設只要有一個accessory連線就是我們的裝置。下面程式碼用於同步開啟已經存在的於accessory表中的裝置:

    public void checkUSBDevice() {

        UsbAccessory[] accessories = mUsbManager.getAccessoryList();

        if(accessories == null){
            Log.i(TAG, "accessories list is null");
            return;
        }

        Log.i(TAG, "accessories length "+accessories.length);

        UsbAccessory accessory = (accessories == null ? null : accessories[0]);
        if (accessory != null) {
            if (mUsbManager.hasPermission(accessory)) {

                sCurStatus = STATUS_CONN_OK;
                mUsbAccessory = accessory;

                //synchronized (USBConnStatusManager.class) {
                    if (mOnUSBConnStatusChanged != null) {
                        mOnUSBConnStatusChanged.onUSBConnect(accessory);
                    }
                //}
            } else {
                //synchronized (mUsbReceiver) {
                    if (!mPermissionRequestPending) {
                        mUsbManager.requestPermission(accessory, mPermissionIntent);
                        mPermissionRequestPending = true;
                    }
                //}

            }
        }
    }

USBHelper 具體操作usb的開關和讀寫等功能

USBHelper類具體操作usb的功能,這個類中持有USBConnStatusManager的單例物件,有了USBConnStatusManager就可以拿到accessroy,通過USBConnStatusManager獲取到讀寫流,這個類就是在外層呼叫USBConnStatusManager方法,對usb進行操作。

openAsync

這個方法用來開啟usb,首先註冊廣播接收者回調,用來檢測usb插拔資訊,註冊完畢後檢查當前系統中存在的accessory裝置,如果已經連線了accessroy裝置,則直接獲取其accessroy的引用,通過這個引用獲取讀寫流,者就是usb的開啟過程。

    /**
     * accessory模式開啟android的 usb裝置
     * 如果當前列表有處於accessory模式的控制代碼則直接開啟
     * 如果當前沒有則回監聽usb插拔,監聽到對應事件後檢查系統列表
     * @param onUSBConnStatusChanged
     */
    @Override
    public void openAsync(final OnUSBConnStatusChanged onUSBConnStatusChanged) {

        mReciveBuffer = new byte[RECIVE_BUF_SIZE];

        //註冊USB連線狀態監聽
        mUSBConnStatusManager.registOnUSBConnStatusChangedListener(new OnUSBConnStatusChanged() {
            @Override
            public void onUSBConnect(UsbAccessory accessory) {

                openAccessory(accessory);
                if (onUSBConnStatusChanged != null) {
                    onUSBConnStatusChanged.onUSBConnect(accessory);
                }
            }

            @Override
            public void onUSBConnectFailed(UsbAccessory accessory) {

                closeAccessory();
                if (onUSBConnStatusChanged != null) {
                    onUSBConnStatusChanged.onUSBConnectFailed(accessory);
                }
            }

            @Override
            public void onUSBDisconnect(UsbAccessory accessory) {

                closeAccessory();
                if (onUSBConnStatusChanged != null) {
                    onUSBConnStatusChanged.onUSBDisconnect(accessory);
                }
            }
        });

        //檢查usb列表 檢視是否已經連線accessory裝置
        mUSBConnStatusManager.checkUSBDevice();
    }

        /**
     * 通過accessory控制代碼拿到usb裝置的輸入輸出流
     * @param accessory
     */
    private void openAccessory(UsbAccessory accessory) {

        mFileDescriptor = mUsbManager.openAccessory(accessory);

        if (mFileDescriptor != null) {
            mAccessory = accessory;
            FileDescriptor fd = mFileDescriptor.getFileDescriptor();

            //usb讀寫流
            mInputStream = new FileInputStream(fd);
            mOutputStream = new FileOutputStream(fd);

            if (mOnDataTranPrepared != null) {
                Log.d(TAG, "accessory opened DataTranPrepared");
                mOnDataTranPrepared.onDataTranPrepared(mInputStream, mOutputStream);
            }

            Log.d(TAG, "accessory opened");
        } else {
            Log.d(TAG, "accessory open fail");
        }
    }

另外這個類還提供了usb資料讀寫和關閉裝置等方法,大家可以參考專案原始碼。

SimpleTcpWrapper 封裝上層通訊協議

打通底層資料通道,下面就是封裝我們自己協議了,在專案中使用tcp頭簡單封裝了一個協議,可以實現三次握手,資料包通過序列號校驗以及根據不同埠分發資料的功能。本章只討論Android裝置底層通訊的實現,所以刪除了協議部分,只是將usb傳送過來的資料原樣傳送回去。

SimpleTcpWrapper中建立一個USBHelper物件用來管理usb資料通訊,呼叫openAsync非同步開啟資料。一旦資料連線成功,我們就是開啟一個數據接收執行緒,讀取這個accessory的inputstream,一旦收到資料就將資料寫入accessory的outputstrem中。

/**
 * 資料通訊協議封裝類,本例中只涉及簡單usb層傳輸,並沒有封裝上層協議
 * Created by lidechen on 2/25/17.
 */

public class SimpleTcpWrapper implements ISimpleTcpManager {

    private static final String TAG = "SimpleTcpWrapper";

    private Context mContext;

    /**
     * 資料通訊物件引用
     */
    private USBHelper mConmunicateHelper;

    private Thread mRecieveThread;
    private ReciveTask mReciveTask;

    /**
     * 強制重啟usb標誌
     */
    private boolean mForceOpenUSB = false;

    public SimpleTcpWrapper(Context context) {

        mContext = context;

        mConmunicateHelper = new USBHelper(mContext);
    }

    public void start(){

        //資料傳輸層準備就緒 可以開啟資料收發任務
        mConmunicateHelper.setOnDataTranPrepared(new USBHelper.OnDataTranPrepared() {
            @Override
            public void onDataTranPrepared(FileInputStream inputStream, FileOutputStream outputStream) {

                if (Config.DEBUG) {
                    Log.i(TAG, "accessory opened: inputStream " + inputStream + " outputStream " + outputStream);
                }

                //建立從usb讀取資料的任務
                mReciveTask = new ReciveTask(inputStream);
                mRecieveThread = new Thread(mReciveTask);
                mRecieveThread.start();
            }
        });
    }

    public void openUSBAsync(boolean reset) {

        if (mForceOpenUSB == false) {

            //狀態為已連線且不是從後臺進入 直接返回
            if (!reset) {
                return;
            }

            if(Config.DEBUG){
                Log.i(TAG, "openUSBAsync ...");
            }
        }

        mForceOpenUSB = false;


        mConmunicateHelper.openAsync(new OnUSBConnStatusChanged() {
            @Override
            public void onUSBConnect(UsbAccessory accessory) {

            }

            @Override
            public void onUSBConnectFailed(UsbAccessory accessory) {

                Log.i(TAG, "connect state ### onUSBConnectFailed ###");

                mConmunicateHelper.close();

                mForceOpenUSB = true;
            }

            @Override
            public void onUSBDisconnect(UsbAccessory accessory) {

                Log.i(TAG, "connect state ### onUSBDisconnect ###");

                mConmunicateHelper.close();

                mForceOpenUSB = true;
            }
        });
    }

    public void closeUSB() {

        if (mRecieveThread != null) {
            mReadTaskRun = false;
            Thread.State state = mRecieveThread.getState();
            if (state == Thread.State.BLOCKED || state == Thread.State.TIMED_WAITING || state == Thread.State.TIMED_WAITING) {
                mRecieveThread.interrupt();
            }
            mRecieveThread = null;
        }

        mConmunicateHelper.close();
    }

    @Override
    public int readTcpData(byte[] data, int realLen) {

        return 0;
    }

    @Override
    public void writeRawData(byte[] data, int realLen) {
    }

    private byte[] mReciveBuffer;
    private static final int RECIVE_BUF_SIZE = 1024*10;
    private boolean mReadTaskRun = false;

    public class ReciveTask implements Runnable {

        private FileInputStream mInputStream;

        public ReciveTask(FileInputStream inputStream) {

            mInputStream = inputStream;
        }

        @Override
        public void run() {

            mReciveBuffer = new byte[RECIVE_BUF_SIZE];

            mReadTaskRun = true;

            int off = 0;
            int len = 0;

            while (mReadTaskRun) {
                try {

                    if (Config.DEBUG) {
                        Log.i(TAG, "accessory opened start read");
                    }

                    //usb資料接收
                    int ret = mInputStream.read(mReciveBuffer, off, RECIVE_BUF_SIZE - off);

                    //將接收到的資料返回給usb
                    byte[] retBuf = new byte[ret];
                    System.arraycopy(mReciveBuffer, 0, retBuf, 0, ret);
                    mConmunicateHelper.writeSyncToUSB(retBuf);
                    Log.i(TAG, "feedback "+new String(retBuf));

                } catch (IOException e) {

                    if (Config.DEBUG) {
                        Log.i(TAG, "ReciveTask exception :\n" + e.toString());
                    }

                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e1) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}

建立連線服務

在實踐中發現上述SimpleTcpWrapper如果與應用建立在本app服務中,如果應用閃退usb則不能被正確釋放,有時候服務還會被殺死,相對而言開一個單獨程序的服務則比較穩定,app奔潰也不會掛掉,如果手動殺死app則外部服務也會被正確釋放,不會佔用usb資源。
獨立程序的服務可以通過socket與我們的app進行資料互動。

關於嵌入式裝置啟動app應用

上一篇文章我們提到了裝置開啟android的app,會寫入一些資訊,那麼android端是如何對應這些資訊的呢?我們看看嵌入式端給android寫的那些資訊是什麼

    const char *setupStrings[6];
    setupStrings[0] = vendor;
    setupStrings[1] = model;
    setupStrings[2] = description;
    setupStrings[3] = version;
    setupStrings[4] = uri;
    setupStrings[5] = serial;

這裡我們在android的專案下資原始檔中建立一個xml資料夾。建立一個accessory_filter.xml檔案,檔案內容如下:

<!--?xml version="1.0" encoding="utf-8"?-->
<resources>
    <usb-accessory manufacturer="vonchenchen" model="android.usbaoa" version="0.1">
</usb-accessory></resources>
<!--
const char *vendor = "vonchenchen";
const char *model = "android.usbaoa";
const char *description = "Android Aoa Interface";
const char *version = "0.1";
const char *uri = "https://www.baidu.com/";
const char *serial = "1234567890";
-->

下面的註釋是c程式碼中對應的字串,manufacturer與vendor對應,model,version兩個量對應一樣,accessory就會被啟動起來。假如手機中沒有對應app,就會彈出對話方塊,選擇是否啟動瀏覽器,訪問uri對應的地址。對話方塊中的描述資訊就是description對應的字串。

總結

到此,Android通過AOA協議與嵌入式裝置通訊的流程就已經分析完了,在裝置端我們藉助了libusb庫,通過其api操作usb,將手機設定為accessory模式,然後通過libusb讀寫資料,在Android手機端監聽accessory事件,同時查詢本地accessory列表,一旦拿到accessory引用,就可以獲取讀寫流,同時Android
端最好將accessory的相關處理放在單獨程序的服務中處理,防止應用閃退導致usb資源無法釋放,在此連線無法成功的問題。

相關推薦

Android usb學習筆記:Android AOA協議Android 流程總結

背景 上篇文章中我們瞭解了嵌入式裝置端將Android手機設定為accessory模式的流程以及嵌入式裝置端接收和傳送資料的流程,本文將對應介紹Android端accessory模式被啟用的過程,以及接下來如何與嵌入式裝置端進行通訊。本文的原始碼下載地址:ht

Android 學習筆記——使用 HTTP 協議訪問網路

在 Android 上傳送 HTTP 請求一般有兩種方式:HttpURLConnection 和 HttpClient,由於 HttpClient 存在 API 數量過多、擴充套件困難等問題,在 Android 6.0 已經被完全淘汰,因此官方建議使用 HttpConnetion。 除了 H

Android:日常學習筆記(7)———探究UI開發(1)

tac calling repl action its 內容 schema lesson try Android:日常學習筆記(7)———探究UI開發(1) 常用控件的使用方法 TextView 說明:TextView是安卓中最為簡單的一個控件,常用來在界面上顯示一段文本信

Android:日常學習筆記(7)———探究UI開發(4)

this 活動 eal enc panel .html http 中間 編寫 Android:日常學習筆記(7)———探究UI開發(4) UI概述 View 和 ViewGrou   Android 應用中的所有用戶界面元素都是使用 View 和 ViewGroup 對象

Android:日常學習筆記(9)———探究廣播機制

ora rri enabled cas 管理 encoding protect 其他 acc Android:日常學習筆記(9)———探究廣播機制 引入廣播機制 Andorid廣播機制   廣播是任何應用均可接收的消息。系統將針對系統事件(例如:系統啟動或設備開始充電時)傳

Android:日常學習筆記(10)———使用LitePal操作數據庫

分享 數據 turn find netstat price 彈出 category 模式 Android:日常學習筆記(10)———使用LitePal操作數據庫 引入LitePal 什麽是LitePal   LitePal是一款開源的Android數據庫框架,采用了對象關系

Android核心學習筆記

0、android系統啟動 《Android系統啟動流程 -- bootloader》 《The Android boot process from power on》 《Android 啟動過程介紹》 《Android培訓班(86)核心執行之前的載入程式》 這是一系列

Android ToolBar學習筆記

前言 開發中經常遇到頂部導航欄的需求,5.0 之後Google為了統一設計風格,默認了ToolBar這個控制元件作為統一頂部欄,並且還支援了不少的動畫和各種設定,但是!有關ToolBar 的theme,Menu,click有不少的坑,今天來系統的學習一下。 內容 基於需求來學習。

Android安全學習筆記1——鎖屏密碼方式

前言 在Android安全學習中,我接觸到第一個例子是鎖屏密碼。我們日常使用手機的時候使用最多的鎖屏密碼是怎麼構成的?下面分享一下我接觸到的知識。 鎖屏密碼的思考 為了安全,Android裝置在解鎖螢幕時會有密碼輸入,那麼在這個密碼存放在哪裡?是否為明文儲存?如果是加密儲存,

Android開發學習筆記(十二)基礎UI控制元件之ImageView、CheckBox、RadioButton

一、ImageView:直接繼承自View,它的作用是在介面上顯示Drawable物件。 ImageView在佈局檔案(如main_activity.xml)中常用的屬性 有 scaleType ,s

Android開發學習筆記(十四)基礎UI控制元件之Spinner

Spinner:彈出一個列表選擇框,供使用者選擇。繼承自ViewGroup,因為可以容納很多列表項,因此它也是一個容器控制元件。 給Spinner指定資料來源的2種方法: 一、通過指定xml檔案來指

Android開發學習筆記(十五)基礎UI控制元件之ListView

一、ListView常用XML屬性: android:choiceMode="" 設定ListView的選擇行為 android:divider="" 設定List列表項的分隔條(即可用顏

Android開發學習筆記(十六)基礎UI控制元件之ListView-SimpleAdapter

一、ListView控制元件: <ListView android:id="@+id/list_view_demo" android:layout_width="match_pa

android 註解學習筆記二: 元註解和自定義註解

首先看一個自定義的註解: 1、自定義註解 public @interface MyAnnotation { int age(); } 可見定義一個註解非常簡單,只需要使用@interface關鍵字來定義即可。 同時我們可以看到,註解的內部可以定義變

Android原始碼學習筆記:Context、ActivityThread和Activity的生命週期

總結: ①在應用啟動的時候,首先會建立一個程序process,然後建立ActivityThread這個物件。 ②根據我們之前學習的Handler,可以知道,在ActivityThread的main方法中,會建立一個Looper和MessageQueue物件。 ③在建

Android開發學習筆記(二)之啟動另外一個Activity

1、Activity 是SDK中的一個類,負責建立一個螢幕視窗,放置UI元件,供使用者互動。 2、建立一個Activity: 1)建立Activity的java類檔案 2)在AndroidManifest.xml中註冊 3)設定佈局檔案 3、AndroidManif

Android開發學習筆記(七)之類的傳遞

1、把需要傳遞的類實現Serilziable介面 class UserInfo implements Serilziable{ //類的成員屬性和方法 } 2、把需要傳遞的類實現P

android 開發零起步學習筆記(九):android 控制控制元件的位置和大小及Layout相關屬性

1、 ? 1 2 3 4 5 6 7 8 9 10 11 LinearLayout.LayoutParams p = newLinearLayout.LayoutParams(      

Android GPS學習筆記—HAL實現

        HAL的全稱是Hardware Abstraction Layer, 即硬體抽象層。         HAL層是介於Android核心與上層之間抽象出來的一層結構,它是對linux驅動的一個封裝,對上層提供統一介面,上層應用不必知道下層是如何實現的,它遮蔽

android 註解學習筆記一: java基本註解

註解 定義:註解(Annotation),也叫元資料。一種程式碼級別的說明。它是JDK1.5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋。 註解的功能建立在反