一、前言
在Eclipse
的時代,我們進行NDK
的開發一般需要通過手動執行NDK
指令碼生成*.so
檔案,再將.so
檔案放到對應的目錄之後,之後再進行打包。
而如果使用的是Android Studio
進行NDK
開發,在2.2
的版本以後,我們可以不需要手動地執行NDK
指令碼來生成*.so
檔案,而是將這一過程作為Gradle
構建過程的依賴項,事先編寫好編譯的指令碼檔案,然後在build.gradle
中指定編譯指令碼檔案的路徑就可以一次性完成生成原生庫並打包成APK
的過程。
目前這種AS + Gradle
的NDK
開發方式又可以分為三種:ndk-build
、CMake
和Experimental Gradle
:
ndk-build
:和上面談到的傳統方式相比,它們兩個的目錄結構相同,Gradle
指令碼其實最終還是依賴於Android.mk
檔案,對於使用傳統方式的專案來說,比較容易過度。CMake
:Gradle
指令碼依賴的是CMakeLists.txt
檔案。Experimental Gradle
:需要引入實驗性的gradle
外掛,全部的配置都可以通過build.gradle
來完成,不再需要編寫Android.mk
或者CMakeLists.txt
,可能坑比較多,對於舊的專案來說過度困難。
目前,Android Studio
已經將CMake
作為預設的NDK
實現方式,並且官網上對於NDK
的介紹也是基於CMake
,聲稱要永久支援。按照官方的教程使用下來,感覺這種方式有幾點好處:
- 不需要再去通過
javah
根據java
檔案生成標頭檔案,並根據標頭檔案生成的函式宣告編寫cpp
檔案 - 當在
Java
檔案中定義完native
介面,可以在cpp
檔案中自動生成對應的native
函式,所需要做的只是補全函式體中的內容 - 不需要手動執行
ndk-build
命令得到so
,再將so
拷貝到對應的目錄 - 在編寫
cpp
檔案的過程中,可以有提示了 CMakeLists.txt
要比Android.mk
更加容易理解
下面,我們就來介紹一下如何使用CMake
進行簡單的NDK
開發,整個內容主要包括兩個方面:
- 建立支援
C/C++
的全新專案 - 在現有的專案中新增
C/C++
程式碼
二、建立支援C/C++
的全新專案
2.1 安裝元件
在新建專案之前,我們需要通過SDK Manager
安裝一些必要的元件:
NDK
CMake
LLDB
2.2 建立工程
在安裝完必要的元件之後,我們建立一個全新的工程,這裡需要記得勾選include C++ Support
選項:
Next
,在最後一步我們會看見如下的幾個選項,它們的含義為:
C++ Standard
:選擇C++
的標準,Toolchain Default
表示使用預設的CMake
配置,這裡我們選擇預設。Excptions Support
:如果您希望啟用對C++
異常處理的支援,請選中此核取方塊。如果啟用此核取方塊,Android Studio
會將-fexceptions
標誌新增到模組級build.gradle
檔案的cppFlags
中,Gradle
會將其傳遞到CMake
。Runtime Type information Support
:如果您希望支援RTTI
,請選中此核取方塊。如果啟用此核取方塊,Android Studio
會將-frtti
標誌新增到模組級build.gradle
檔案的cppFlags
中,Gradle
會將其傳遞到CMake
。
(1) cpp 資料夾
用於存放C/C++
的原始檔,在磁碟上對應於app/src/main/cpp
資料夾,當新建工程時,它會生成一個native-lib.cpp
的事例檔案,其內容如下:
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_demo_lizejun_cmakenewdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
複製程式碼
(2) 增加 CMakeList.txt 指令碼
構建指令碼,在磁碟上對應於app/
目錄下的txt
檔案,其內容為如下圖所示,這裡面涉及到的CMake
語法包括下面四種,關於CMake
的語法,可以檢視 官方的 API 說明
cmake_minimum_required
add_library
find_library
target_link_libraries
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
複製程式碼
(3) build.gradle 指令碼
與傳統的專案相比,該模組所對應的build.gradle
需要在裡面指定CMakeList.txt
所在的路徑,也就是下面externalNativeBuild
對應的選項。
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.demo.lizejun.cmakenewdemo"
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
testCompile 'junit:junit:4.12'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
}
複製程式碼
(4) 列印字串
在MainActivity
中,我們載入原生庫,並呼叫原生庫中的方法獲取了一個字串展示在介面上:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) 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();
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
}
複製程式碼
(5) 執行結果
最後,我們執行一下這個工程,會得到下面的結果:
通過APK Analyzer
工具,我們可以看到在APK
當中,增加了libnative-lib.so
檔案:
2.3 原理
下面,我們來解釋一下這一過程:
- 首先,在構建時,通過
build.gradle
中path
所指定的路徑,找到CMakeList.txt
,解析其中的內容。 - 按照指令碼中的命令,將
src/main/cpp/native-lib.cpp
編譯到共享的物件庫中,並將其命名為libnative-lib.so
,隨後打包到APK
中。 - 當應用執行時,首先會執行
MainActivity
的static
程式碼塊的內容,使用System.loadLibrary()
載入原生庫。 - 在
onCreate()
函式中,呼叫原生庫的函式得到字串並展示。
2.4 小結
當通過CMake
來對應用程式增加C/C++
的支援時,對於應用程式的開發者,只需要關注以下三個方面:
C/C++
原始檔CMakeList.txt
指令碼- 在模組級別的
build.gradle
中通過externalNativeBuild/cmake
進行配置
三、在現有的專案中新增C/C++
程式碼
下面,我們演示一下如何在現有的專案中新增對於C/C++
程式碼的支援。
(1) 建立一個工程
和第二步不同,這次建立的時候,我們不勾選nclude C++ Support
選項,那麼會得到下面這個普通的工程:
(2) 定義介面
這一次,我們將它定義在一個單獨的檔案當中:
public class NativeCalculator {
private static final String SELF_LIB_NAME = "calculator";
static {
System.loadLibrary(SELF_LIB_NAME);
}
public native int addition(int a, int b);
public native int subtraction(int a, int b);
}
複製程式碼
這時候因為沒有找到對應的本地方法,因此會有如下的錯誤提示:
(3) 定義 cpp 檔案
在模組根目錄下的src/main/
新建一個資料夾cpp
,在其中新增一個calculator.cpp
檔案,這裡,我們先只引入標頭檔案:
#include <jni.h>
複製程式碼
(4) 定義 CMakeLists.txt
在模組根目錄下新建一個CMakeLists.txt
檔案:
cmake_minimum_required(VERSION 3.4.1)
add_library(calculator SHARED src/main/cpp/calculator.cpp)
複製程式碼
(5) 在 build.gradle 中進行配置
之後,我們需要讓Gradle
指令碼確定CMakeLists.txt
所在的位置,我們可以在CMakeLists.txt
上點選右鍵,之後選擇Link C++ Project with Gradle
:
build.gradle
就會新增下面這句:
在這步完成之後,我們選擇Build -> Clean Project
。
**(6) 實現 C++ **
在配置完上面的資訊之後,會發現一個神奇的地方,之前我們定義native
介面的地方,多出了一個選項,它可以幫助我們直接在對應的C++
檔案中生成函式的定義:
Create function
之後,我們的calculator.cpp
檔案變成了下面這樣:
#include <jni.h>
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_addition(JNIEnv *env, jobject instance, jint a, jint b) {
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_subtraction(JNIEnv *env, jobject instance, jint a, jint b) {
}
複製程式碼
下面我們在函式體當中新增實現:
#include <jni.h>
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_addition(JNIEnv *env, jobject instance, jint a, jint b) {
return a + b;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_demo_lizejun_cmakeolddemo_NativeCalculator_subtraction(JNIEnv *env, jobject instance, jint a, jint b) {
return a - b;
}
複製程式碼
(9) 呼叫本地函式
public class MainActivity extends AppCompatActivity {
private NativeCalculator mNativeCalculator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNativeCalculator = new NativeCalculator();
Log.d("Calculator", "11 + 12 =" + (mNativeCalculator.addition(11,12)));
Log.d("Calculator", "11 - 12 =" + (mNativeCalculator.subtraction(11,12)));
}
}
複製程式碼
最終執行的結果為:
四、小結
以上就是使用Android Studio 2.2
以上版本,通過CMake
來進行NDK
開發的一個簡單例子,主要是學習一下開發的流程,下一篇文章,要學習一下CMakeLists.txt
中的語法。
五、參考文獻
(1) 向您的專案新增 C 和 C++ 程式碼
(2) NDK筆記(二) - 在Android Studio中使用 ndk-build
(3) NDK開發 從入門到放棄(一:基本流程入門瞭解)
(4) NDK開發 從入門到放棄(七:Android Studio 2.2 CMAKE 高效 NDK 開發)
(5) The new NDK support in Android Studio
(6) Google NDK 官方文件
(7) Android Studio 2.2 對 CMake 和 ndk-build 的支援
(8) Android開發學習之路--NDK、JNI之初體驗
(9) NDK- JNI實戰教程(一) 在Android Studio執行第一個NDK程式
(10) cmake-commands
(11) CMake
(12) 開發自己的 NDK 程式
(13) NDK開發-Android Studio+gradle-experimental 開發 ndk
(14) Android NDK 開發入門指南