NDK運行時庫簡介

一般的Android應用程序都是用Java語言編寫的,在Dalvik虛擬機或ART虛擬機中運行的。

但是,出於對性能的考慮,android也允許使用JNI接口,直接調用原生(Native)程序。這些程序都是直接被編譯成平臺支持的彙編指令,效率自然比在虛擬機中運行的要高。不過,現在ART虛擬機出現了,JNI調用在性能方面的優勢被大大縮減。

一般情況下,自己編寫的JNI程序都是使用C語言編寫的。但有時候,程序的邏輯過於複雜,也會選擇用C++語言,使用面向對象的方法編寫。或者,要程序中要使用一個複雜的現成代碼庫,而它是用C++編寫的。

Google提供的Android NDK本身是支持C++語言的。那麼,編譯器是怎麼知道你的代碼是用c語言,還是用C++語言編寫的呢?答案是看你源程序文件的擴展名。默認情況下,如果你的程序是以“.cpp”結尾的話,就用C++編譯器對其進行編譯;而如果是以“.c”結尾的話,則用C編譯器進行編譯。你也可以通過修改Android.mk文件中的變量“LOCAL_CPP_EXTENSION”來指定特定後綴的源代碼文件,必須使用C++編譯器編譯。例如:

  1. LOCAL_CPP_EXTENSION := .cxx .cpp .cc  

如果在你模塊中的Android.mk文件中,包含以上變量賦值語句的話,那麼所有以“.cxx”、“.cc”和“.cpp”擴展名結尾的源碼文件,都將使用C++編譯器編譯。

不過,編譯過後的C++程序想要執行,還必須要有C++運行環境(C++ Runtime)的支持。

默認情況下,Android NDK會使用一個非常迷你的C++運行環境,稱做“libstdc++”。這個運行環境幾乎什麼都沒有,不支持異常和RTTI(RunTime Type Information,即運行時類型識別),甚至連C++標準庫也沒有。

不過除了這個缺省的C++運行環境之外,Android NDK還帶了一些其它的C++運行環境,它們各有各的特長,在使用的時候也經常分不清到底最好要使用哪個。本文下面的部分,將對它們各自的特點做一個詳細的說明。

但在正式介紹之前,先說幾個基本概念。

首先是所謂的C++標準庫。C++標準除了定義了語法之外,還包含了一個包羅萬象的標準庫,方便程序的開發者調用,從而使用簡單代碼就可以實現複雜的功能。最簡單的,比如string就是屬於C++標準庫的,C++自己的語法中並沒有string關鍵字,只有char。這個標準庫非常龐大,包含的功能如下圖所示:


接着,是所謂的C++運行時類型信息(RTTI)。它有點類似Java語言中反射的概念,就是可以在程序運行時,可以動態的判斷某一個對象所屬的類是什麼。

然後是C++異常機制,這個簡單,就是try和catch嘛。不過要想實現C++的異常捕獲機制,需要C++編譯器和運行時庫一起配合纔行,兩者缺一不可。運行時庫支持,但是C++編譯器不支持也不行,反之亦然。

最後是所謂的動態庫和靜態庫的概念。所謂動態庫是程序運行時動態加載進進程內的,它的好處是如果程序中有很多獨立的模塊都需要使用同樣的一個動態庫,那麼只需要在內存中加載一次就可以了。而靜態庫,是在模塊編譯的時候,在鏈接的過程中,將程序庫中所需要的代碼“拷貝”到模塊內部實現的,它的壞處就是同樣的代碼會在各個模塊中都存在,浪費內存和磁盤存儲空間,甚至不同模塊間的庫函數代碼會相互干擾,出現詭異的行爲。

除去默認的那個C++運行時庫,Android NDK中還帶了其它的四個不同的C++運行時庫,它們所支持的C++特性有可能都不一樣,而且每個運行時庫都分有動態庫和靜態庫。下面這張表,總結了一下所有C++運行時庫的特點:

下面簡單介紹一下各個運行時庫的特點:

1)libstdc++(默認運行時庫)

前面也提到了,這個系統默認的C++運行時庫功能非常的弱,基本上都不支持所有的C++高級特性。

不過,在真實的設備上,只自帶了缺省的C++運行時庫(一般該文件位於/system/lib/libstdc++.so),在Android NDK中只包含了這個運行時庫所需要的一些頭文件,並沒有真實的靜態庫和動態庫。所以,也就是說,Android系統中默認的那個C++運行時庫是沒有靜態庫版本的,而且由於系統自帶了,所以在你的程序中也不需要再包含這個庫的.so文件了,可以減小安裝文件的大小。

2)gabi++

這個庫也不支持C++標準庫,但它加入了異常和RTTI的支持。

在Android NDK中,包含有這個庫的靜態和動態兩個預先編譯好的版本,位於<NDK Folder>/sources/cxx-stl/gabi++/目錄下。

3)stlport

這個庫提供了對C++所有特性的完整支持。它是開源項目STLPort(http://www.stlport.org)的一個Android移植版本。

在Android NDK中,也包含有這個庫的靜態和動態兩個版本,位於<NDK Folder>/sources/cxx-stl/stlport/目錄下。

同時,Android NDK中還包含了這個C++運行時庫的所有源碼,你可以在Application.mk文件中,加上下面的賦值語句來強制Android NDK通過源碼編譯這個庫,而不要用預編譯的版本:

  1. STLPORT_FORCE_REBUILD := true  

4)gnustl

這就是GNU標準C++運行時庫(libstdc++-v3)。同樣,它支持C++的所有特性,且在Android NDK中包括了靜態和動態兩個預編譯的版本,位於<NDK Folder>/sources/cxx-stl/gnu-libstdc++/目錄下。

5)llvm-libc++

最後一個就是llvm-libc++,它是LLVM libc++(http://libcxx.llvm.org/)的一個Android移植版本。也支持C++的所有特性,且也有靜態和動態的兩個預編譯的版本,位於<NDK Folder>/sources/cxx-stl/llvm-libc++/目錄下。

這兩個預編譯的版本都是使用Clang 3.4編譯的。不過,和stlport一樣,在Android NDK中也帶有它的實現源碼,可以在Application.mk文件中,加上下面的賦值語句來強制Android NDK通過源碼編譯這個庫,而不要用預編譯的版本:

  1. LIBCXX_FORCE_REBUILD := true  

那麼多個C++運行時庫,程序怎麼知道到底用哪個呢?剛纔說了,默認情況下,也就是不做任何特殊設置的時候,是使用libstdc++。如果想用別的C++運行時庫,需要在你程序中的Application.mk文件中,對變量APP_STL進行賦值,想用哪個C++運行時庫,就直接將運行時庫的名字賦值給這個變量,而運行時庫的名字就是上表第一列中的字符串。例如,如果想換用共享的gnustl庫的話,需要在Application.mk文件中,加上下面的語句:

  1. APP_STL := gnustl_static  

還要特別說明的是,默認情況下,Android NDK的編譯器的選項是不包含對C++異常和RTTI的支持的。剛纔也提到過了,對異常和RTTI的支持,必須是編譯器和C++運行時庫相互配合才能完成的。如果編譯器本身不支持,即使運行時庫支持也沒用。

所以,如果想讓你的程序支持異常和RTTI,必須先讓編譯器支持,且選擇合適的運行時庫纔行。

那麼怎麼讓編譯器支持異常和RTTI呢?如果你是想讓你程序中的所有模塊都支持,只需要在Application.mk文件中,對APP_CXXFLAGS變量賦值即可。例如,如果想全部程序加入對異常的支持,可以這樣賦值:

  1. APP_CPPFLAGS += -fexceptions  

如果你只想讓你程序中的某一個模塊支持異常或RTTI,可以在模塊所屬的Android.mk文件中,對LOCAL_CPPFLAGS變量賦值即可。例如,下面的語句將會在編譯你的指定模塊時加上對RTTI的支持:

  1. LOCAL_CPPFLAGS += -frtti  

上面的語句也等效於下面這條語句:

  1. LOCAL_CPP_FEATURES += rtti  

最後,還有一點需要注意,如果你的JNI功能都是包含在一個模塊(也就是程序中只有一個.so文件)中的話,可以考慮用靜態庫C++運行時庫的形式。而如果你的程序中包含了多個模塊,請儘量使用動態C++運行時庫。

在用動態C++運行時庫的時候,相應的動態庫.so文件(默認的C++運行時庫除外)會隨你的程序一起發佈出去,位於apk文件中的lib目錄下。

如果你的目標Android系統時4.3之前,那麼你必須要在加載所有你自己的模塊之前,顯式的加載C++動態運行時庫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章