2015年12月2日 星期三

[轉] Android車載服務平台的通訊機制

Android車載服務平台的通訊機制 
文/圖 徐士欽.責任編輯/洪羿漣 

Android為目前當紅的手機開發平台,在Google的用心下,將其打造為一個易用、穩定且順暢的作業系統,而其免費且開放原始碼的特性,更是讓所有開發商欣喜不已,紛紛採用為新手機的作業系統。比起以往各家作業系統的遙不可及,現在一般的手機玩家只要有一點JAVA程式基礎,便可輕易的在自己的Android手機上開發屬於自己的應用程式,打造專屬自己的手機。 

欲開發程式時,非常重要的一個技術便是程序間的溝通(Inter Process Communication, IPC),IPC的技術隨著不同的平台上會有著不同的做法,一般常見通用的有Socket、資料庫、檔案等手段。

而Android上面除了這些通用的手法之外,還自行設計了幾種不同的通訊方式,可因應不同情況使用不同的做法。在這篇文章中,我們將針對Android自己獨有的IPC機制做一個介紹,使讀者能夠有個全面的了解。


元件與程序 
開始介紹IPC之前,必須得要對應用程式在系統中的地位有個基本了解。當我們在Android系統上面設計應用程式時,最常接觸的便是其介面裡的基本元件,如Activity、Service、Provider等,其中Activity為我們最常用到的基礎元件,它實際上便是代表著我們所看到的使用者介面,元件的內容並非此篇文章的主題,在這邊我們就不深入介紹,但是在本文中只要了解一件事情即可,這些元件是構成應用程式的主體們。

一般情況裡,我們撰寫的應用程式(也就是在手機上點兩下可以執行的東西),在系統中是由一個Process(程序)來執行,而每個Process裡面一定會附帶一個Thread(線程)。有些時候,應用程式裡的主Process會自己產生更多的Process,也有可能會自己產生更多的Thread;更多的狀況下是在自己開發的應用程式中要對其他的Process做一些命令的下達或者是交換資料。

由於Process存在不同的記憶體區塊內而且是由系統進行控管,所以我們無法直接從程式裡面寫個「x=10;」這樣的命令,就將資料交遞給其他的Process,而是得要透過作業系統來幫我們達到這樣的目的。

此時就會用到系統設計的IPC機制,當然也可以在兩個不同的Process約定好,直接使用存取檔案或者Socket的方式來達到資料交換的效果,不過本文中就不去探討這樣的做法。

在圖1中就說明了一種常見的情形,有2個應用程式,編號1中包含了2個Process,而這2個Process則分別包含了一個Thread,其中一個Thread中有一個Activity的元件,另一個則是Service的元件。同樣的,編號2也是類似的架構。

圖1 IPC示意圖。 


在這樣的架構下,當不同的Process之間要交換資料時,便要使用IPC的溝通機制。像編號1這樣的架構,是常常被使用的,最常見的例子便是MP3播放器。

由於MP3音樂是要被持續播放的,所以我們會使用Service這樣的元件,來達到背景執行的效果,但是使用者仍需要另一個操作介面來對音樂做暫停、快轉等控制動作,所以又得要有另一個Activity的元件來實現操作介面,於是便會形成了編號1這樣的架構,而其中的通訊也就成了無可避免的課題。


Intent與IPC 
Android的IPC機制,廣義的來說包含好幾種不同的方式,總共包含以下幾種做法:
1.使用Intent元件。
2.透過Binder綁定機制進行遠端呼叫。
3.使用Android Interface Definition Language(AIDL)介面機制。

Intent,中文譯為目的或意圖,這是一個很方便且聰明的機制。而不同的方法有著不同的優缺點以及其限制,使用時選擇最適合自己的機制可以達到最佳的效率。但無論是哪種語言,無論在哪種平台,我們常常會考慮的一個通訊架構便是「事件觸發」。

所謂事件觸發,指的是我們發出一個消息,告訴處理中心說:「現在我有個事件,請你幫我處理」,然後處理中心便會依據我們發出的事件類型,尋找適合的機制來進行處理。拿現實生活來說,便是類似服務台的機制。

那這個部分跟我們的IPC有甚麼關係呢?我們換個角度來想想,某人(Process A)發出了一個「事件(Intent)」,將其給予了服務中心(Android作業系統),服務中心找出處理這事件的方法(另一個Process B),接著將我們的問題交給專人(Process B)處理,最後完成事務。我們現在介紹的第一個Intent元件,便是使用這樣的設計架構實現出來的機制。

下面我們使用一個簡單的喚起其他Service做為範例,來了解Intent機制使用步驟:
1.首先在AndroidManifest.xml檔案中,對要負責接收此Intent的Service部分加上說明。圖2中我們可以看到,針對IIIService這個Service的定義加上對於Intent的過濾條件,也就是說,之後只要符合Intent的定義,就會跟此Service進行關聯的動作。

圖2 只要符合Intent的定義,就會跟此Service進行關聯。 


2.生成一個Intent並設定其行為與資料,如圖3範例中,我們僅設定行為(action)的部分,可以看到圖3與圖2的設定是互相對照。

圖3 生成一個Intent並設定其行為與資料。 


3.丟出Intent,我們呼叫startService直接將Intent送出去,在這邊我們直接寫成一行。到這邊我們的工作便完成了,可以在程式裡看到另一個Service被呼叫起來。

Intent最常被使用的時機,是用在喚起其他的Activity,並將資料攜帶傳遞過去,例如我們希望在應用程式中,按下一個按鈕之後能喚起瀏覽器,自動連到某一個網頁;或者是按下按鈕後,跳出一個新的視窗程式。

在上述的狀況中,通常都會使用Intent做為媒介,因為這是最直接也最容易處理的方式。在Android的系統中,Intent有著各式各樣的使用方法,你可以使用intent.setClass()來設定Intent發送給特定的class,也可以使用startActivity()來喚起其他元件,並傳送資料。在本文中就不詳加介紹各式各樣的Intent用法,但是它是一個最常被使用的IPC機制,如果你的應用程式用於簡單的訊息傳遞、啟動或者切換畫面,使用Intent便可以解決大部分的問題。


Binder與IPC 
Binder機制是Android應用框架裡面一個很重要的機制,它最主要的工作是將各個不同的Service做一個Bind(連結)的動作,也就像是一個管理著所有Service的中心。

拿一個現實中的例子來說,我們將Service當作是一輛一輛的車,而Binder機制中的Binder Driver這個東西,就像是停車場。若想找到自己的車,只要到停車場去找,就可以找到了。透過這樣的機制,我們得以取得其他Service的介面,藉此與他進行溝通的動作。

我們先來了解一下Binder與Service的關係。Service主要可以分為2種,一種是我們在元件裡面定義的Service,而另一種是在底層建立起來的系統Service。系統Service簡單來說,就是已經寫好的一些底層操控API。

若想要播放音樂,不可能從最底層的Audio Driver開始實現,我們通常的作法是直接使用SDK裡面提供的API,但是終究還是要有人來把底層的部分做好對吧,所以雖然我們使用的是SDK的API,但深入去看實際運作的部份,還是使用底層的系統Service,圖4中就說明了此種架構。

圖4 Service與Binder關係圖。 


圖4中我們可以看到自行創建的Service,會使用系統提供的MediaPlayer相關API,而API則是透過了JNI(Java Native Interface)的介面,來直接呼叫底層的C++相關函式。其函式則是透過了MediaPlayerService所提供的介面,來實際對硬體進行操作的動作。

我們也看到Binder機制裡面的Binder Driver,在此扮演了記錄各種不同的系統Service角色,記錄著不同Service的連接接口。在這邊要補充說明的是,Binder並不只是記載系統Service的接口,而是記錄著「所有」的Service的接口,我們自定義的OurService也會被記錄在其中。


採用方法的考慮因素 
有寫過Service的讀者應該會發現,當建立自己的元件時,需要複寫一個名為onBind的Method,這便是跟Binder相關的Method。圖5為一個簡單的Service範例,其中便有前文中所提及的onBind的Method。

圖5 簡單的Service範例。 


這邊必須先介紹一下Service Management,如果我們將Binder Driver比擬為停車場,那Service Management就像是停車場的管理員了,管理員管理著整個停車場,當我們找車找不到時便可以連絡管理員,請求協助。

所以在Binder機制裡,實際上控管Service的是Service Management,我們要找某一個Service也都是向他請求,而他自己本身,也是一個Service,當系統開機時,便會將Service Management載入到Binder Driver裡面,成為第一個Service,之後其他的Service加入時再向其登錄資料。

當我們在程式中想要透過Binder來與其他Service通信時,首先得要向Service Management取得目的Service的Binder物件,接著使用Transact() Method,便可以透過Binder Driver將消息傳遞到目標Service的OnTransact() Method,然後執行相關動作,達到IPC的效果。

但是在Binder裡面能夠用於傳輸的只有OnTransact()與Transact()這一對的函式,在使用上會有較多的限制,所以若應用程式中要用到的功能很少,也沒有要存取對方物件的Method,使用此機制是不錯的選擇。

然而上述的方式與Intent差異在哪呢?Intent是上層的資料交換,當你使用到Binder的時候,便是直接透過JNI與底層的Binder作互動;相比之下,使用Intent只要輕鬆的設定一些東西即可,所以一般狀況下使用Intent是比較輕鬆的選擇。但是若溝通的目標Service是想要直接與底層的Service溝通,那使用Binder會是比較好的做法。


AIDL與IPC 
在前面我們介紹了Binder的做法,這樣的做法不免讓人覺得限制頗多,最主要是因為只有Transact()這個Method可以使用,當你想使用對方的其他Method時,便顯得較不方便。當然你可以在連結成功後,再使用本地的Method,但是繞這麼大一圈,若有很多Method要使用,便會顯得繁複。

為了解決這樣的問題,於是有了AIDL的機制。AIDL,全名為Android Interface Definition Language,如其字面翻譯,它是一個讓你可以自行定義你要開放的介面的工具。原理很簡單,它背後也是使用Binder的機制完成,我們所要做的,就是使用它的AIDL工具,來產生相關的檔案,然後實現你希望開放的介面。

再來就是將你打算開放的介面開放出來給其他程式使用,實際上的操作就不在這邊介紹。這種做法適合用在程式中希望能有不同的介面可以直接存取時,不過在撰寫上需要多一些步驟。


以車載裝置服務平台為例 
依照不同情形選擇適當的機制會達到最好的效益,以資策會開發中的車載裝置服務平台來說,我們開發基於OSGi(Open Service Gateway Initiative)各式核心處理程序,以達到動態加載服務及卸載的動作。然後結合TR-069的通訊系統進行遠端管控的動作。

由於遠端控管到了本地端時,使用了另一個程式來對OSGi作控制的動作,其中的IPC通訊因為是處於上層的JAVA部分,且僅負責傳遞命令,所以選用了Intent的IPC傳輸方式;而我們在底層使用的方法,即為上期文章中所介紹的HAL技術,實作了接受車輛上OBD的相關服務,因為這是使用底層的服務,所以我們使用AIDL的機制,來讓OBD的服務與OSGI的Framework進行溝通,再將資訊遞送到內部的各個不同的核心程式Bundle去運作。透過這樣的機制,讓整個應用系統更加彈性也較易於維護。 

沒有留言:

張貼留言