標籤:android style blog http io ar color os 使用
在安卓平台上開發應用,通用的語言是 Java ,而對於從其它平台遷移到安卓的項目、產品,或者對於慣用 C/C++ 編程的開發人員來講,會希望複用已有的 C/C++ 代碼。安卓平台提供了複用 Native 代碼的途徑,也提供了編譯 C 代碼的環境和工具鏈: NDK 。 NDK 是一套工具鏈,有了它,在安卓上使用 C 語言成為可能。其實安卓原本是在 Linux 上套了個 Java 環境,要說不能用C 那才是不可思議的事兒,只是 Google 沒完全開放而已(話說我到現在都在腹黑,為麼不能讓 C 程式員在安卓上活得自在些呢,簡直是人為製造障礙)。
安卓平台上服用 C 代碼有兩種方式:
- JNI
- 原生 C 可執行程式
JNI 方式
JNI 原本是Java 提供的一種複用 C 代碼的架構,安卓又對此進行了一些擴充,加了個 AIDL 用在服務架構中,搞了一套工具,在使用 Android.mk 編譯時間可以根據 AIDL 檔案自動產生對應的 Java 代碼並編譯。
使用 JNI 主要是把 C 代碼編譯成動態庫,在 Java 中調用。使用的步驟大概是這樣的:
- 在 Java 代碼中聲明 native 方法
- 在 JNI(橋接 C 代碼的這部分 C 代碼稱之為 JNI 層)層按照命名規則實現與 Java 層對應的本地方法
- 在 Java 層載入 C 動態庫
JNI 方式的例子,如 Qt on Android ,Vitamio ,還有安卓架構本身中的一些例子,如 ServiceManager , android.util.Log 。
原生 C 可執行程式
安卓本是 Linux ,調用 Native 可執行程式是自然而然的途徑。 Java 也提供了語言層面的支援,Runtime.exec() 函數就是幹這個的。通過 exec() 啟動進程,可以讀取 Native 進程的標準輸出,可以向 Native 進程的標準輸入寫入資料。
調用原生可執行程式的方式的例子, WifiManager 在串連無線網路時會通過控制介面和 wpa_supplicant (用於無線串連的 wpa_suppliacant ,是原生C可執行程式)通訊傳遞諸如掃瞄存取點、選擇網路、串連網路等指令。
還有,有些安卓系統機頂盒上內建的寬頻撥號(PPPoE)程式,也是 C 可執行程式, Java 層的 PPPoEService 最終通過調用pppd/ppppoe 這樣一些程式來執行實際的撥號過程。
還有,我們常說的 root ,其實也是通過調用一個叫 su 的程式來實現的。
我們在實際開發中也可以這麼用,比如想看某個目錄下都有什麼檔案,可以直接調用 ls 命令,讀取它的標準輸出。
Java 和 C 程式通訊問題
我們在安卓上通過 Java 調用 C 代碼時還會遇到進程或線程間通訊的問題。
這裡專門說下 Java 進程和 C 進程、Java 進程和 C 共用庫的通訊問題。
有時我們的需求很簡單,阻塞式調用 C 代碼,拿到計算結果就達到目的了。比如你通過 JNI 調用 C 共用庫的一個 Hash 函數,又比如你通過 Runtime.exec() 調用一個 Native 可執行程式來計算 Hash 讀取其標準輸出獲得結果。這些情境足夠簡單,你可以不考慮 Java 和 C 共用庫或者 C Native 可執行程式間的通訊,反正是一鎚子買賣也沒啥狀態要維護的。
但是還有一些複雜的應用,我們必須在 Java 和 C 之間建立一種長期的通訊機制。
還是舉無線串連的例子好了,有興趣的讀者可以瀏覽 wpa_supplicant 的原始碼,它提供了基於 dbus、unix domain socket 兩種方式的控制介面。
其實 Linux 常用的處理序間通訊機制,如 管道pipe 、socket 、訊號,也都可以用於 Java 層和 C 層的通訊,而且安卓架構就這麼用了。舉個例子,我們都很熟悉的安卓世界的第一個進程 Zygote(實際是 app_process ,啟動後更名為 Zygote ),有一個功能就是啟動 Java 進程,當我們要啟動一個 APK ,該 APK 的進程幾經輾轉最終是由 Zygote 啟動的(可以進程間共用 Java 類庫、C 共用庫,大大節省資源也加快進程啟動過程),而 Zygote 正是通過 socket 接受命令的。
再舉個訊號的例子,我們看到很多進程管理類的應用,其實都是使用 android.os.Process 來殺進程,而 Process.kill() ,實際上就是給目標進程發送了一個訊號,非常標準的 Linux 機制。
還有一個常用的 Java 和 C Native 可執行程式處理序間通訊的機制:標準輸入輸出。我們可以向一個進程的標準輸入寫入資料,也可以從它的標準輸出讀取資料。那麼我們就可以定義一套控制協議用來通訊。
像管道 pipe、socket 既可以用於處理序間通訊(Java 和 C Native 可執行程式),也可以用於進程內通訊( Java 和 C 共用庫)。那麼什麼情境下我們會這麼用呢?前面的無線串連服務程式 wpa_supplicant 可以給我們一些提示。
假如我們用 C 實現了一個服務,而 Java 層需要經常訪問這個服務並且需要得到反饋,那麼就可以這麼幹。
試著說一個情境,我們用 C 實現了一個可以支援並行下載的模組(單線程 select 模型),而 Java 層會頻繁地下載圖片(安卓上 Java 層貌似沒有簡單易用佔用資源又少的http 下載庫),比如一個雲相簿類的應用或視頻類應用,我們就可以把下載動作委託給 C 進程。
處理序間通訊,安卓架構中還有一個在 Linux IPC 架構之上擴充出來的新架構 Binder ,很多 Native 系統服務使用 Binder 架構,比如 AudioFlinger ,我們在 Java 層調用 AudioManager 設定音量的功能時,最終就是通過 Binder 架構使用了 Native 系統服務 AudioFlinger 的功能,是典型的跨進程調用,但是作為 Java 程式,完全不用關心這個。
想詳細瞭解 Binder 的讀者,可以進一步學習。這裡提一下 Binder 的限制:不是所有 C 程式都可以使用 Binder 來註冊服務,只有被授權的服務如 media.player ,media.camera 之類的 Native 系統服務才可以,如果你是系統開發,可以通過修改授權列表(通過 UID 控制)來允許你的 C 程式註冊服務,而作為應用開發人員,就別妄想了,還是考慮使用管道pipe、socket 或者標準輸入輸出穩妥些。
編譯 C 可執行程式
為安卓平台編譯 C 可執行程式,有幾個方法:
- 使用 NDK ,使用 Android.mk
- 手動使用先行編譯的 gcc 編譯
- 使用 Qt 5.2 ,參考《Windows下Qt 5.2 for Android開發入門》和《Windows下Qt for Android 編譯安卓C語言可執行程式》
在 APK 中整合和使用 C 可執行程式
怎樣在 APK 中整合和使用一個 C 可執行程式呢?遵循下列步驟即可:
- 可以把可執行程式放在 assets 目錄下,這樣打包成 APK 時會自動打包
- APK 運行時訪問 assets 檔案夾內的資源,釋放可執行程式,添加可執行許可權
- 使用 Runtime.exec() 啟動可執行程式
Android調用C程式的七葷八素