一窜骄、前言
在Eclipse
的時(shí)代,我們進(jìn)行NDK
的開發(fā)一般需要通過(guò)手動(dòng)執(zhí)行NDK
腳本生成*.so
文件摆屯,再將.so
文件放到對(duì)應(yīng)的目錄之后邻遏,之后再進(jìn)行打包。
而如果使用的是Android Studio
進(jìn)行NDK
開發(fā)虐骑,在2.2
的版本以后准验,我們可以不需要手動(dòng)地運(yùn)行NDK
腳本來(lái)生成*.so
文件,而是將這一過(guò)程作為Gradle
構(gòu)建過(guò)程的依賴項(xiàng)廷没,事先編寫好編譯的腳本文件糊饱,然后在build.gradle
中指定編譯腳本文件的路徑就可以一次性完成生成原生庫(kù)并打包成APK
的過(guò)程。
目前這種AS + Gradle
的NDK
開發(fā)方式又可以分為三種:ndk-build
颠黎、CMake
和Experimental Gradle
:
-
ndk-build
:和上面談到的傳統(tǒng)方式相比另锋,它們兩個(gè)的目錄結(jié)構(gòu)相同,Gradle
腳本其實(shí)最終還是依賴于Android.mk
文件盏缤,對(duì)于使用傳統(tǒng)方式的項(xiàng)目來(lái)說(shuō)砰蠢,比較容易過(guò)度蓖扑。 -
CMake
:Gradle
腳本依賴的是CMakeLists.txt
文件唉铜。 -
Experimental Gradle
:需要引入實(shí)驗(yàn)性的gradle
插件,全部的配置都可以通過(guò)build.gradle
來(lái)完成律杠,不再需要編寫Android.mk
或者CMakeLists.txt
潭流,可能坑比較多竞惋,對(duì)于舊的項(xiàng)目來(lái)說(shuō)過(guò)度困難。
目前灰嫉,Android Studio
已經(jīng)將CMake
作為默認(rèn)的NDK
實(shí)現(xiàn)方式拆宛,并且官網(wǎng)上對(duì)于NDK
的介紹也是基于CMake
,聲稱要永久支持讼撒。按照官方的教程使用下來(lái)浑厚,感覺這種方式有幾點(diǎn)好處:
- 不需要再去通過(guò)
javah
根據(jù)java
文件生成頭文件,并根據(jù)頭文件生成的函數(shù)聲明編寫cpp
文件 - 當(dāng)在
Java
文件中定義完native
接口根盒,可以在cpp
文件中自動(dòng)生成對(duì)應(yīng)的native
函數(shù)钳幅,所需要做的只是補(bǔ)全函數(shù)體中的內(nèi)容 - 不需要手動(dòng)執(zhí)行
ndk-build
命令得到so
,再將so
拷貝到對(duì)應(yīng)的目錄 - 在編寫
cpp
文件的過(guò)程中炎滞,可以有提示了 -
CMakeLists.txt
要比Android.mk
更加容易理解
下面敢艰,我們就來(lái)介紹一下如何使用CMake
進(jìn)行簡(jiǎn)單的NDK
開發(fā),整個(gè)內(nèi)容主要包括兩個(gè)方面:
- 創(chuàng)建支持
C/C++
的全新項(xiàng)目 - 在現(xiàn)有的項(xiàng)目中添加
C/C++
代碼
二册赛、創(chuàng)建支持C/C++
的全新項(xiàng)目
2.1 安裝組件
在新建項(xiàng)目之前钠导,我們需要通過(guò)SDK Manager
安裝一些必要的組件:
NDK
CMake
LLDB
2.2 創(chuàng)建工程
在安裝完必要的組件之后,我們創(chuàng)建一個(gè)全新的工程森瘪,這里需要記得勾選include C++ Support
選項(xiàng):
接下來(lái)一路
Next
牡属,在最后一步我們會(huì)看見如下的幾個(gè)選項(xiàng),它們的含義為:
-
C++ Standard
:選擇C++
的標(biāo)準(zhǔn)柜砾,Toolchain Default
表示使用默認(rèn)的CMake
配置湃望,這里我們選擇默認(rèn)。 -
Excptions Support
:如果您希望啟用對(duì)C++
異常處理的支持痰驱,請(qǐng)選中此復(fù)選框证芭。如果啟用此復(fù)選框,Android Studio
會(huì)將-fexceptions
標(biāo)志添加到模塊級(jí)build.gradle
文件的cppFlags
中担映,Gradle
會(huì)將其傳遞到CMake
废士。 -
Runtime Type information Support
:如果您希望支持RTTI
,請(qǐng)選中此復(fù)選框蝇完。如果啟用此復(fù)選框官硝,Android Studio
會(huì)將-frtti
標(biāo)志添加到模塊級(jí)build.gradle
文件的cppFlags
中,Gradle
會(huì)將其傳遞到CMake
短蜕。
在新建工程完畢之后氢架,我們得到的工程的結(jié)構(gòu)如下圖所示:
與傳統(tǒng)的工程相比,它有如下幾點(diǎn)區(qū)別:
(1) cpp 文件夾
用于存放C/C++
的源文件朋魔,在磁盤上對(duì)應(yīng)于app/src/main/cpp
文件夾岖研,當(dāng)新建工程時(shí),它會(huì)生成一個(gè)native-lib.cpp
的事例文件,其內(nèi)容如下:
#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 腳本
構(gòu)建腳本孙援,在磁盤上對(duì)應(yīng)于app/
目錄下的txt
文件害淤,其內(nèi)容為如下圖所示,這里面涉及到的CMake
語(yǔ)法包括下面四種拓售,關(guān)于CMake
的語(yǔ)法窥摄,可以查看 官方的 API 說(shuō)明
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 腳本
與傳統(tǒng)的項(xiàng)目相比,該模塊所對(duì)應(yīng)的build.gradle
需要在里面指定CMakeList.txt
所在的路徑础淤,也就是下面externalNativeBuild
對(duì)應(yīng)的選項(xiàng)崭放。
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
中,我們加載原生庫(kù)鸽凶,并調(diào)用原生庫(kù)中的方法獲取了一個(gè)字符串展示在界面上:
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) 運(yùn)行結(jié)果
最后莹菱,我們運(yùn)行一下這個(gè)工程,會(huì)得到下面的結(jié)果:
通過(guò)
APK Analyzer
工具吱瘩,我們可以看到在APK
當(dāng)中道伟,增加了libnative-lib.so
文件:2.3 原理
下面,我們來(lái)解釋一下這一過(guò)程:
- 首先使碾,在構(gòu)建時(shí)蜜徽,通過(guò)
build.gradle
中path
所指定的路徑,找到CMakeList.txt
票摇,解析其中的內(nèi)容拘鞋。 - 按照腳本中的命令,將
src/main/cpp/native-lib.cpp
編譯到共享的對(duì)象庫(kù)中矢门,并將其命名為libnative-lib.so
盆色,隨后打包到APK
中。 - 當(dāng)應(yīng)用運(yùn)行時(shí)祟剔,首先會(huì)執(zhí)行
MainActivity
的static
代碼塊的內(nèi)容隔躲,使用System.loadLibrary()
加載原生庫(kù)。 - 在
onCreate()
函數(shù)中物延,調(diào)用原生庫(kù)的函數(shù)得到字符串并展示宣旱。
2.4 小結(jié)
當(dāng)通過(guò)CMake
來(lái)對(duì)應(yīng)用程序增加C/C++
的支持時(shí),對(duì)于應(yīng)用程序的開發(fā)者叛薯,只需要關(guān)注以下三個(gè)方面:
-
C/C++
源文件 -
CMakeList.txt
腳本 - 在模塊級(jí)別的
build.gradle
中通過(guò)externalNativeBuild/cmake
進(jìn)行配置
三浑吟、在現(xiàn)有的項(xiàng)目中添加C/C++
代碼
下面,我們演示一下如何在現(xiàn)有的項(xiàng)目中添加對(duì)于C/C++
代碼的支持耗溜。
(1) 創(chuàng)建一個(gè)工程
和第二步不同组力,這次創(chuàng)建的時(shí)候,我們不勾選nclude C++ Support
選項(xiàng)抖拴,那么會(huì)得到下面這個(gè)普通的工程:
(2) 定義接口
這一次燎字,我們將它定義在一個(gè)單獨(dú)的文件當(dāng)中:
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);
}
這時(shí)候因?yàn)闆]有找到對(duì)應(yīng)的本地方法,因此會(huì)有如下的錯(cuò)誤提示:
(3) 定義 cpp 文件
在模塊根目錄下的src/main/
新建一個(gè)文件夾cpp
,在其中新增一個(gè)calculator.cpp
文件轩触,這里,我們先只引入頭文件:
#include <jni.h>
(4) 定義 CMakeLists.txt
在模塊根目錄下新建一個(gè)CMakeLists.txt
文件:
cmake_minimum_required(VERSION 3.4.1)
add_library(calculator SHARED src/main/cpp/calculator.cpp)
(5) 在 build.gradle 中進(jìn)行配置
之后家夺,我們需要讓Gradle
腳本確定CMakeLists.txt
所在的位置脱柱,我們可以在CMakeLists.txt
上點(diǎn)擊右鍵,之后選擇Link C++ Project with Gradle
:
那么在該模塊下的
build.gradle
就會(huì)新增下面這句:在這步完成之后拉馋,我們選擇
Build -> Clean Project
榨为。
**(6) 實(shí)現(xiàn) C++ **
在配置完上面的信息之后,會(huì)發(fā)現(xiàn)一個(gè)神奇的地方煌茴,之前我們定義native
接口的地方随闺,多出了一個(gè)選項(xiàng),它可以幫助我們直接在對(duì)應(yīng)的C++
文件中生成函數(shù)的定義:
點(diǎn)擊
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) {
}
下面我們?cè)诤瘮?shù)體當(dāng)中添加實(shí)現(xiàn):
#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) 調(diào)用本地函數(shù)
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)));
}
}
最終運(yùn)行的結(jié)果為:
四矩乐、小結(jié)
以上就是使用Android Studio 2.2
以上版本,通過(guò)CMake
來(lái)進(jìn)行NDK
開發(fā)的一個(gè)簡(jiǎn)單例子回论,主要是學(xué)習(xí)一下開發(fā)的流程散罕,下一篇文章,要學(xué)習(xí)一下CMakeLists.txt
中的語(yǔ)法傀蓉。
五欧漱、參考文獻(xiàn)
(1) 向您的項(xiàng)目添加 C 和 C++ 代碼
(2) NDK筆記(二) - 在Android Studio中使用 ndk-build
(3) NDK開發(fā) 從入門到放棄(一:基本流程入門了解)
(4) NDK開發(fā) 從入門到放棄(七:Android Studio 2.2 CMAKE 高效 NDK 開發(fā))
(5) The new NDK support in Android Studio
(6) Google NDK 官方文檔
(7) Android Studio 2.2 對(duì) CMake 和 ndk-build 的支持
(8) Android開發(fā)學(xué)習(xí)之路--NDK、JNI之初體驗(yàn)
(9) NDK- JNI實(shí)戰(zhàn)教程(一) 在Android Studio運(yùn)行第一個(gè)NDK程序
(10) cmake-commands
(11) CMake
(12) 開發(fā)自己的 NDK 程序
(13) NDK開發(fā)-Android Studio+gradle-experimental 開發(fā) ndk
(14) Android NDK 開發(fā)入門指南