Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android調用C程序的七葷八素

Android調用C程序的七葷八素

編輯:關於Android編程

在安卓平台上開發應用,通用的語言是 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() 啟動可執行程序


好啦,總算說了個大概,希望對你有所幫助。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved