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及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋。 註解的功能建立在反