Android中JNI&NDK入門(四) 之 使用CMake方式編譯NDK

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/

 

 

 

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