1 前言
我們在前面幾篇文章中對JNK/NDK做了一個入門的介紹,其中使用了Android.mk和Application.mk本地配置的方式進行NDK開發。但是其實在Android Studio 2.2之後便加入了CMake方式來編譯NDK代碼。
2 CMake
CMake是一個跨平臺的安裝(編譯)工具,可以用簡單的語句來描述所有平臺的安裝(編譯過程)。他能夠輸出各種各樣的makefile或者project文件,能測試編譯器所支持的C++特性,類似UNIX下的automake。谷歌從Android Studio2.2及更高版本使用NDK和CMake將C及C++代碼編譯到原生庫中,其中通過Gradle便可方便地將SO庫封裝到APK中去。
3 Hello world
如果你是首次使用CMake,還要跟前面安裝NDK一樣,在Android Studio中的SDK管理頁面勾選CMake項進行下載,操作如下面圖:
待安裝完畢後,便可以創建Native C++項目了。在【File】 – 【New Project】彈窗中,勾上【Include C++ support】項,如下圖:
項目創建好以後我們可以看到和普通Android項目有幾下不同地方,如下圖:
1. app目錄下多出一個.externalNativeBuidl目錄。
2. main目錄下多出一個cpp目錄,其中裏頭有一個native-lib.cpp文件,這便是放置C/C++代碼地方。
3. app目錄下的buile.gradle內容裏多出兩項。能看出,第一項便上我們在新建項目時選擇的C++版本和勾上的-fexceptions和-frtti項,它們分別是異常支持(-fexceptions)和運行時類型信息支持(-frtti); 第二項便是指定CMakeLists.txt文件。
4. app目錄下還多出一個CMakeLists.txt文件,其內容如下:
3.1 CMakeLists.txt解說
我們看回上面CMakeLists.txt文件內容,裏面去除註釋就剩4行有效代碼,我們來看看它們的含義。
cmake_minimum_required(VERSION major[.minor[.patch[.tweak]]][FATAL_ERROR])
設置工程所需要的最低CMake版本,如上述最低版本是3.4.1。
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
添加一個庫。如上述是:編譯出一個動態庫 native-lib,源文件只有 src/main/cpp/native-lib.cpp。參數說明:
<name> 表示添加一個指定名稱的庫文件。
[STATIC | SHARED | MODULE] 指定要創建的庫的類型,STATIC對應的靜態庫(.a文件,編譯時需要,相當於Windows中的lib文件)、SHARED對應共享動態庫(.so文件,運行時需要,相當於Windows中的dll文件)、MODULE對應工程內的module。
[EXCLUDE_FROM_ALL] 若指定此屬性,則對應的一些屬性會在目錄被創建時被設置,詳細請查閱相應文檔。
source1 source2 ... sourceN 指定源文件。
find_library(<VAR> name1 [path1 path2 ...])
查找一個庫文件。如上述是:查找預編譯庫log_lib。
target_link_libraries(<name> lib1 lib2 lib3)
將給定的庫鏈接到一個目標上。如上述是:找到預編譯庫 log_lib 並link到動態庫 native-lib中。
3.2 代碼解說
native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_zyx_cmakedemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
非常簡單的代碼,在MainActivity在靜態塊中進行加載libnative-lib.so,然後定義了一個native 方法stringFromJNI。接着在native-lib.cpp中實現stringFromJNI方法,函數名稱也是遵循規則:Java_包名_類名_方法名。
3.3運行
執行編譯運行,這時就會在app\build\intermediates\cmake\debug\obj目錄下生成對應的so文件。然後將程序運行到手機上便會看到Java成功調用了C++代碼返回了結果。
4 引用外部so庫
在實際開發過程中,往往C++工程是跟Android工程分離,或者Android工程中直接引用外部提供現成的so庫文件。現在我們就來模擬一下這種情況的發生。
首先將上述編譯好的so文件拷貝到app\src\main\jniLibs中,如下圖:
接着修改CMakeLists.txt內容,如下:
cmake_minimum_required(VERSION 3.4.1)
add_library(native-lib SHARED IMPORTED)
set_target_properties(native-lib PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libnative-lib.so)
下面兩行代碼意思是:添加一個動態庫,然後設置so庫的路徑,其中 ${CMAKE_SOURCE_DIR} 是CMakeLists.txt所在的路徑, ${ANDROID_ABI} 是標識cup類型。
修改完後重新編譯運行即可,顯示結果和上面源碼集成是一樣的。
5 更多資料
更多關於NDK和CMake的資料,可參考:
https://developer.android.com/ndk/guides/index.html
https://developer.android.com/ndk/guides/cmake.html.
https://www.zybuluo.com/khan-lau/note/254724
https://github.com/googlesamples/android-ndk
https://cmake.org/cmake-tutorial/