在人們的生活中,行動電話及平板裝置正扮演越來越中心的角色。人們期望能用行動裝置做到更多的事情,其中包括與其他電子產品連接,例如連接個人健身器材與醫療衛生等設備。利用行動裝置提供的多元使用者介面與連接能力,為許多嵌入式應用程式開啟寬廣的可能性。然而,這需嵌入式開發者走進嵌入式的世界,並進入行動應用程式開發的應用。
對於開發在強大處理器上運作的應用程式開發人員而言,轉而開發在行動電話/平板裝置上的應用程式,或許並不覺得陌生;對開發在較小處理器上運作的應用程式開發人員來說,開發行動應用程式或許將會是一個需要不同知識的全新體驗。
在針對Android設備進行開發作業時,有幾個不錯的工具可幫助工程師開始進行這些工作,如Google網站上有許多非常有用的資訊與教學指導,說明如何開始進行行動應用程式的開發作業。由於Android應用程式絕大部分是用Java語言所寫,讀者亦可在在網際網路上,搜尋關於Java開發的豐富資訊。
開發在行動電話與平板裝置上運行的應用程式時,開發人員必須注意,在現今市場中,這些行動電話/平板裝置運行在與標準電腦相同或非常相似的作業系統上。這些內建多核心處理器的機器在多工處理架構下,須同一時間內同時處理多個應用程式並維持多種型態的連接,以及提供使用者介面運行。
以執行緒分配裝置多工作業
在應用程式的開發上,這些不同的任務是透過執行緒處理。執行緒是在一個程序中,程式執行的分叉點,藉此形成兩組可在相同時間運行的程式碼,假如某一組程式碼必須等待某個事件的發生才能繼續往前移動時,其他的執行緒依然能夠繼續運行。對於所有行動應用程式的開發而言,了解執行緒很重要,在行動裝置上進行的任務,例如瀏覽網頁、藍牙或通用序列匯流排(USB)連接等,可能會阻斷針對非特定、或是可能不明確時間數量的執行緒。沒有適當的執行緒,可能會造成這些應用程式的使用者介面被鎖住並成為無回應狀態,直到這些任務被完成。
使用執行緒會為程式開發帶來被稱為「並行性(Concurrency)」的新問題。當兩個或更多的執行緒在同時運作,且資料必須通過兩個執行緒,或是相同的資料須被兩個執行緒進行讀取/修改的情況時,就可能出現非常複雜的資料存取問題。因為每一個執行緒的執行時間未知,所以很可能當工程師在某一個執行緒修改變數的同一時間內,第二個執行緒正在試著讀取。
對於這種兩個執行緒想要在同一時間讀取相同資料的情況,Java提供非常有用的關鍵字協助這種同步化作業。同步的關鍵字可讓開發人員創造出鎖定一個物件的單元,一般這個物件是指共享的物件,所以假如有一個執行緒是位在同步的單元內,則不會有其他的執行緒進入到這個單元,直到第一個執行緒離開這個單元為止。
|
圖1 Java使用同步的函數 |
透過Java解決執行緒同步問題
在圖1範例中,使用同步函數確保a與b兩個變數可以一起被修改,從而導致相同的加總結果。如果沒有同步化,那麼可能當某個執行緒在程式碼中,正好在a被增加但b被減少之前,呼叫了updateVariables()函數b,這時候,當第二個執行緒呼叫getSum()子程式時,就會在那一瞬間,導致加總結果有所不同。
|
圖2 使用同步的關鍵字 |
圖2範例顯示同步的單元如何被用來取代同步的函數,進而得到相同的效果。使用同步的單元可以讓處在父階物件內的其他變數與函數仍然可以被使用,因為鎖定的效用僅加諸在變數a,而不是在整個父階物件上。請注意,同步的語法有一些複雜,且容易出錯,因此工程師須選擇適當的方法。
Java也提供好幾種可在執行緒間安全傳遞資料或事件的不同方法。其中之一就是透過處理程序(Handler)與訊息(Message)(圖3~5)。處理程序就像郵箱,訊息可被置放在其中,一旦相關連的執行緒不再忙碌時,則在郵箱中的第一個訊息就會跳出來以供執行緒處理,這種方法可被用來在執行緒間安全傳遞有關事件或資料的資訊。
|
圖3 可做為訊息用的類別程式碼 |
|
圖4 創建訊息並傳送到處理程序的程式 |
|
圖5 執行一個處理程序、接收一個訊息,並解碼該訊息的程式範例。 |
應用程式生命週期變動至關重要
Android的活動/應用程式經過的不同過渡階段,就被稱為一個生命週期(Lifecycle)(圖6)。這些生命週期的過渡階段是在當行動電話/平板裝置上有一些改變,而影響到應用程式時發生。以Android作業系統為目標開發應用程式時,了解活動生命週期很重要,因為如螢幕的旋轉、滑開鍵盤或接聽來電等簡單的使用者互動,都會造成應用程式生命週期的變動。許多活動所要求的系統資源也會被註明為在某些生命週期狀態下,必須被釋放出來,例如廣播接收器會被用來偵測在USB匯流排上所發生的某些事件,像是設備的分離,然而,廣播接收器必須在應用程式暫停及恢復啟動而重新註冊時,先行註銷。
|
圖6 Android 活動生命週期 |
|
圖7 覆寫onResume()功能程式範例 |
Android作業系統提供一種可以覆寫每一個這些事件預設行為的方式,所以開發人員可以在這些生命週期的過渡階段,增添任何所需要的功能性。若想要覆寫生命週期的功能,僅須使用狀態名稱來做為該功能,同時在它之前加上@覆寫的關鍵字(圖7)。
當覆寫一個生命週期功能時,工程師要始終記住使用超級關鍵字(Super Keyword)呼叫正在覆寫的父階功能性。如此一來,可確保一般會在生命週期改變時所發生的其他步驟依然會發生,若未利用超級關鍵字,將會導致應用程式毀損或無法建立。同樣也很重要且工程師必須知道的是,有些時候超級關鍵字是與在功能的何處呼叫父階功能性有關,對於在循環(onCreate(), onStart(),onResume())的創建方(Creation Side)發生的生命週期改變,超級關鍵字功能一般而言是在該功能的開頭處呼叫;而對於在循環(onPause(), onStop(), OnDestroy())的破壞方(Destruction Side)發生的生命週期改變,一般而言,工程師要在靠近該功能或是在該功能的結尾處進行呼叫。
處理不同的生命週期改變的一種解決方法是,將一些須繼續存活的處理物件透過過渡階段推到服務端,利用供給資料連結性物件的服務,也可讓多重活動分享相同的資料連接。
採用有線/無線通訊連結配件
當針對Android作業系統開發配件時,為進行資料交換,將有多種可能的方式與Android設備進行連結,至於使用何種方式,將視作業系統的版本與在該設備上可利用的硬體特點而定。三種主要的連結介面是USB、藍牙(Bluetooth)及無線區域網路(Wi-Fi)。
Wi-Fi是在工程師應用程式的開發上,可利用到的最簡單且最詳盡(Best-documented)的介面之一。假如目標的配件具有HTTP伺服器時,甚至可不須使用客製化的應用程式,在行動電話/平板裝置上的瀏覽器就可被使用;而且也還有很多的telnet/ftp類型的應用程式可利用。假如須使用客製化的應用程式時,網路應用程式介面(API)則可供利用,其已存在於Java很長一段時間,且在如何使用API的議題上,不管是在網際網路,或者是以印刷品形式,都已有著豐富的參考資料可供查詢。
有一種Android作業系統指定的項目,須在應用程式得以使用網路的API之前,先增加到應用程式裡。在AndroidManafest.xml這個檔案裡,存取網路API的活動必須藉由增加圖8指令允許存取網路API。在Android設備上為了配件介面而使用Wi-Fi的主要限制在於,Android目前並不支援隨意網路(Ad-Hoc Networking),因此一個網路基礎設施須能讓Wi-Fi配件運作。這對某些應用程式而言可能是可實現的,例如室內的自動調溫器總是透過Wi-Fi與家用路由器連結,但對於近乎所有的行動配件而言,事實卻不是如此。
|
圖8 增加一個應用程式使用網際網路的權限 |
對於無線配件而言,藍牙則是不錯的選擇。不同的Android作業系統版本支援不同的藍牙設備。Android v2.x版本支援序列埠通訊協定(Serial Port Profile, SPP),雖然不是所有設備都可使用SPP功能,但這個序列埠規範的文件檔案對於創建客製化的應用程式有幫助,因為其不須有預先定義的資料格式。對於更多的專屬配件而言,v3.x版可以提供針對耳機麥克風及藍牙立體聲音訊傳輸Advanced Audio Distribution Profile(A2DP)的支援。Android作業系統4.x版則支援衛生保健設備規範(Health Device Profile, HDP)。
Android 3.1版起原生支援USB
從Android設備取出資料最新的方法就是利用USB。在Android作業系統2.3.4版之前,USB接口是專門讓設備製造商得以使用任何所想要的功能,但事實上,應用程式開發人員無法真正的利用這個接口。在Android作業系統的v2.3.4與v3.1更新版中,增加可讓USB接口被用來做為配件開發的功能。
Android 3.1及之後更新的版本都支援USB主機應用程式介面(USB Host API),因此可讓開發人員存取具有可插入能力的Android設備標準USB周邊。Androi3.1及之後更新的版本也內建支援數種設備類別,例如人機介面設備(Human Interface Device, HID)、大容量存儲設備(Mass Storage Devices, MSD)等。這些內建的驅動程式可讓使用者以如同使用標準電腦的慣用方式,使用這些USB周邊。對於沒有內建支援的周邊,則USB主機應用程式介面可讓應用程式開發人員透過簡單、低階的應用程式介面組件,直接與USB端點連結與溝通。
|
圖9 在USB主機模式下,針對會引起應用程式啟動的設備設定過濾器。 |
為替USB主機應用程式介面取得權限,應用程式必須在AndroidManafest.xml檔案裡宣告資料庫(Library)的使用,且若應用程式想要在特定的周邊插入USB接口時,能自動啟動,則該應用程式可藉由設定設備過濾器(Device Filter)達成。做法是在AndroidManafest.xml檔案中,創建一個與USB_DEVICE_ATTACHED事件相關聯的意圖過濾器(Intent Filter),並且將其關聯到過濾器檔案(圖9),在本文範例中是xml/device_filter.xml。
device_filter.xml檔案包含與應該引起應用程式啟動有關的設備資訊。這些資訊可是供應商識別碼(VID)/產品識別碼(PID)的搭配,或是以類別、子類別以及協定組搭配皆可。
應用程式的開發人員也可不要將每一個屬性都納入標籤內,如此就不用那麼的明確。例如,若產品-識別碼這項屬性不存在時,則任何符合供應商-識別碼的設備,也可造成應用程式的啟動。
因為現今的許多Android設備並未支援USB主機硬體,因此Google在Android設備中,把OpenAccessory的架構增加到標準的USB驅動程式上。OpenAccessory的架構為配件開發人員提供一個機制,讓其得以存取USB接口標準的USB周邊功能,進而能客製化USB流量。
OpenAccessory的協定首先會藉由交換一些製造商在完成這項設備的開發後,屬於供應商類別設備層級的控制權轉移到USB接口,這些指令會將USB驅動程式切換到配件模式,將造成USB周邊從匯流排退出,並重新以Google的供應商識別碼及兩個專屬產品識別碼中的其中一項,進行配件模式的連結。在這種模式下,有供應商類別的介面可供應用程式存取。
OpenAccessory的架構對應用程式呈現出FileStream方式的介面,資料以類似於一個檔案如何讀/寫的方式,從這個串流中被寫入與讀出。這與大多數針對USB周邊執行的韌體有所不同,FileStream介面是以USB封包的大小為基礎,因此會在應用程式開發人員與配件韌體開發人員間造成一些問題,雙方必須對此有所理解。
因為Android設備的USB驅動程式是接收串流的檔案,因此無法知道或了解在資料中,某些特定指令的潛在邏輯中斷為何。來自應用程式發出的兩個對於寫入功能個別呼叫資料,會把封包一起帶進到同樣的USB封包中,因此韌體必須察覺接收到的USB封包,可能包含來自於應用程式對寫入功能的兩個個別呼叫資訊。
對於來自於應用程式對寫入功能的單一呼叫,也可能分散在多個USB封包中。在Android設備中的USB驅動程式會把資料打散到封包中,並傳送給配件,所以配件須能將資料放在一起而回到預期的格式。
|
圖10 資料包裝與碎裂機制會造成資料是以與在Android設備中寫入時的不同格式,到達配件端。 |
資料包裝/碎裂可同時進行
資料包裝(Packing)與碎裂(Fragmentation)的機制(圖10)可一起發生。舉例而言,這個OpenAccessory的架構目前是使用64位元的封包。假如應用程式連續地呼叫兩次寫入功能時,第一個呼叫傳送20位元組的資料;第二個呼叫傳送64位元組的資料,根據USB驅動程式從串流中取得資料並透過匯流排將它傳送出去的時機而定,這兩塊資料可能會被一起包裝到一個84位元組的資料塊。這時USB驅動程式須將這個資料串流打破成USB大小的封包,並擷取最前面的64位元組資料,再傳送出去,接著傳送剩下的20位元組的封包。請注意,在第一個封包中包含有來自第一次寫入的20位元組的資料,以及第二次寫入時的44位元組資料;而第二個20位元組的封包,則是來自於第二次寫入所剩餘的資料。
最後一個使用者必須要了解的主要議題是USB大量傳輸作業如何形成。根據USB規範,USB大量傳輸作業是在兩種情況下完成,第一種情況為傳送的資料量與預期的資料量完全相同;第二種則是封包小於端點所送出的大小,或是送出長度為零的封包。
對於想要傳送剛好是端點大小(目前是64位元組)倍數資料塊的配件開發人員而言,必須在資料之後,送出長度為零的封包來完成這次的傳輸。若在需要的時候,沒有送出長度為零的封包,將造成資料留在USB驅動程式端,而且不會被傳輸到OpenAccessory的FileStream,也因此而無法傳輸到應用程式。
就像是USB主機應用程式介面,OpenAccessory的架構也要在AndroidManifest.xml中取得權限才可使用。設備可根據在進入配件模式所需步驟中,已通過的字串資訊自動啟動某項應用程式,透過與USB主機應用程式介面非常相似的xml檔案完成(圖11)。
|
圖11 當設備被連結時啟動在OpenAccessory模式下的應用程式 |
到目前為止,有關OpenAccessory架構最大的缺點之一就是,在Android作業系統中,一項選用的附加元件資料庫。有些製造商會選擇納入這項資料庫,而其他的製造商則決定不納入,這意味著是無法根據作業系統的版本,假設在任何設備上是否皆支援此功能;同樣也表示,某個設備可能在某個發布的版本有支援這些功能,但是在下一次作業系統更新時,這些支援可能會被製造商予以移除。
AOA 2簡化配件設計流程
隨著Android 4.1--Jelly Bean版本的發布,Android開放配件(Android Open Accessory, AOA)協定也更新為版本2。AOA 2增加兩項重要的新特點協助配件的開發作業。第一個主要的特點是增加數位音訊輸出的支援,將使打造Android設備的音訊底座變得更為容易。在AOA 1時,雖然也是可設計音訊底座,但需要設計人員建立自有的客製化協定,並且使用客製化的應用程式,目前任何標準的應用程式依然僅是將音訊輸出到頭戴式耳機/揚聲器,而在AOA 2,Android設備的核心音訊會全部被傳送到USB接口,如此一來,可以讓音訊在任何應用程式或在設備的特點上運作。在AOA 2中,音訊介面在接上底座時,不管有無啟動應用程式皆可被存取。若要在接上底座時不啟動任何配件,配件只須在進入配件模式前,不要傳送製造商或產品型號的字串給Android設備即可。
AOA 2中增加的第二新特點就是來自於配件模式下HID的控制。先前HID介面的控制僅在USB主機模式下才可使用。有AOA 2後,配件可傳送HID報告到有關聯的Android設備上,並傳送到作業系統控制使用者的輸入。這對於音訊底座的控制很有幫助,同樣的也能創建對設備的控制,這些設備包括像是滑鼠、鍵盤、搖桿、輔助設備或其他輸入類型的設備,這些都可用來改變使用者與Android行動電話或平板裝置的互動介面。
為讓工程師設計與製造的硬體能成為Android配件,有許多選項及障礙是必須知道的。對開發人員而言,在決定應該如何開發配件介面時,知道什麼是可利用的,以及知道什麼是限制所在很重要。從開發硬體/韌體轉型到開發Android的配件,包括相關聯的應用程式時,這樣的轉變令人生畏,不過為讓這樣的轉型變得較為容易,目前市面上已有USB業者提供不同的參考設計予設計人員,進行不同類型的Android配件開發作業。
(本文作者任職於微芯)