Android Studio(2.2+)構(gòu)建native庫(kù)可以使用原生構(gòu)建工具包ndk-build,也可以使用外部構(gòu)建工具CMake,搭配Gradle插件可以方便的構(gòu)建原生庫(kù),進(jìn)行Android JNI的開(kāi)發(fā)。使用Android Studio創(chuàng)建的native工程默認(rèn)使用的是CMake構(gòu)建工具,本文從零開(kāi)始介紹使用CMake搭建一個(gè)JNI工程。
為了闡述方便,我們以創(chuàng)建一個(gè)默認(rèn)的Android工程為例,不使用創(chuàng)建向?qū)Ю锏腎nclude C++ Support或者創(chuàng)建C++工程?,F(xiàn)在我們?cè)趎ative代碼(C++)中實(shí)現(xiàn)一個(gè)獲取字符串并返回的操作,然后使用java jni來(lái)調(diào)用。
一、下載NDK和構(gòu)建工具
打開(kāi)Android Studio -> Perferences -> Appearance&Behavoir -> System Setting -> Android SDK,或者直接在左側(cè)搜索Android SDK,選擇SDK Tools,下載NDK、CMake、LLDB這三個(gè)工具包。

新建的native工程(Include C++ Support)在local.properties中都會(huì)配置ndk的默認(rèn)的路徑:
ndk.dir=/Users/derek/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/derek/Library/Android/sdk
如果沒(méi)有,或者ndk在別的目錄下,需要手動(dòng)添加或修改路徑。
二、在java類中聲明native方法
在java中聲明要使用的native方法,這些方法以native前綴,只需聲明,無(wú)需實(shí)現(xiàn)。這些方法可以聲明為static或非static方法,可以是任何訪問(wèn)權(quán)限。
package com.tsia.example.jnitest;
...
public class MainActivity extends Activity {
...
public native String stringFromJNI(String str);
}
三、添加C/C++代碼
在c++代碼中需添加和native方法對(duì)應(yīng)的函數(shù),注意如下幾點(diǎn):
- 文件名稱可隨意指定,可以只有源文件
- 頭文件或源文件中要#include <jni.h>
- 方法聲明要和java中的native方法對(duì)應(yīng):
- 方法名稱。Java_包名_類名_方法名,包名也使用_分隔。
- 參數(shù)。
- 第一個(gè)參數(shù)為JNIEnv *
- 如果為static方法,第二個(gè)參數(shù)為jclass;如果非static方法,第二個(gè)參數(shù)為jobject
- 從第三個(gè)參數(shù)開(kāi)始,和java的native方法的參數(shù)類型和順序要一一對(duì)應(yīng)
- 返回值。
- 返回值類型要對(duì)應(yīng)java中的類型
- 需要JNIEXPORT、JNICALL前綴
方法格式可以概括為:JNIEXPORT <返回類型> JNICALL Java_<包名><類名><方法名>(JNIEnv *, jobject,<參數(shù)>); 包名中的“.”用下劃線“_”代替。
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL Java_com_tsia_example_jnitest_MainActivity_stringFromJNI
(JNIEnv *env, jobject jobj, jstring str) {
const char* c_name = env->GetStringUTFChars(str, NULL);
std::string hello = "Hello, ";
std::string name(c_name);
return env->NewStringUTF((hello+name).c_str());
}
如果擔(dān)心函數(shù)聲明寫錯(cuò),可以使用命令行生成native方法對(duì)應(yīng)的C++函數(shù)頭文件,只需要到j(luò)ava文件所在的包名目錄下執(zhí)行javah命令,比如在com所在目錄下執(zhí)行:
tsias-MacBook-Pro:java tsia$ javah -jni com.tsia.example.jnitest.MainActivity
執(zhí)行后會(huì)在該目錄下生成一個(gè)頭文件:com_tsia_example_jnitest_MainActivity.h,自動(dòng)生成的格式為包名+類名.h,中間會(huì)使用_分隔。(這個(gè)文件名為命令自動(dòng)生成的格式,可以隨意修改)
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_tsia_example_jnitest_MainActivity */
#ifndef _Included_com_tsia_example_jnitest_MainActivity
#define _Included_com_tsia_example_jnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_tsia_example_jnitest_MainActivity
* Method: stringFromJNI
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_tsia_example_jnitest_MainActivity_stringFromJNI
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
可以在.h文件中看到所有native方法的聲明,格式就是我們上文講的一樣。手動(dòng)編寫時(shí)也可參考自動(dòng)生成頭文件的格式,避免出錯(cuò)。
在調(diào)用native方法的時(shí)候會(huì)匹配函數(shù)名和參數(shù),需要按照格式書寫,不可隨意修改。
四、編寫CMakeLists.txt文件
接下來(lái)就是創(chuàng)建CMake的構(gòu)建腳本,它是一個(gè)純文本文件,必須命名為CMakeLists.txt。構(gòu)建腳本用來(lái)告訴CMake將如何創(chuàng)建一個(gè)so庫(kù),例子中我們要將c++代碼編譯成一個(gè)名為native-lib的庫(kù),給jni調(diào)用。
cmake_minimum_required(VERSION 3.4.1)
add_library(
# 設(shè)置so文件名稱.
native-lib
# 設(shè)置這個(gè)so文件為動(dòng)態(tài)庫(kù)(SHARED)。靜態(tài)庫(kù)使用STATIC
SHARED
# c/c++源文件的相對(duì)路徑(相對(duì)于CMakeLists.txt)
src/main/java/jnitest.cpp)
當(dāng)工程編譯的時(shí)候,Gradle會(huì)自動(dòng)將動(dòng)態(tài)庫(kù)native-lib庫(kù)打包到APK中。腳本中指定的庫(kù)名稱為native-lib,但實(shí)際CMake生成的名稱為libnative-lib.so。
CMake 使用以下規(guī)范來(lái)為庫(kù)文件命名:
lib庫(kù)名稱.so
五、配置Gradle關(guān)聯(lián)
在構(gòu)建應(yīng)用時(shí),Gradle 會(huì)以依賴項(xiàng)的形式運(yùn)行CMake,并將共享的庫(kù)打包到的 APK中,因此我們需要提供一個(gè)指向 CMake腳本文件的路徑。
手動(dòng)配置
將 externalNativeBuild {} 塊添加到模塊級(jí) build.gradle 文件中,并使用 cmake {} 對(duì)其進(jìn)行配置
android {
...
defaultConfig {...}
buildTypes {...}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
這里的path為相對(duì)于build.gradle文件的路徑,需正確配置。
使用Android Studio配置關(guān)聯(lián)
從IDE左側(cè)打開(kāi) Project 窗格并選擇 Android視圖,右鍵點(diǎn)擊您想要關(guān)聯(lián)到原生庫(kù)的模塊(例如 app 模塊),并從菜單中選擇 Link C++ Project with Gradle。

選擇使用CMake構(gòu)建,并制定CMakeLists的路徑。

完成后可以看到模塊級(jí)
build.gradle 文件中會(huì)增加externalNativeBuild {} 塊配置,和我們手動(dòng)配置的結(jié)果是一樣的。因?yàn)間radle配置有修改,sync project下。
此時(shí)執(zhí)行build -> Make Module 'app',可以看到所有架構(gòu)的so都會(huì)打到APK的lib目錄下。

五、在java文件中加載so庫(kù)
在Java代碼中加載so庫(kù)時(shí),請(qǐng)使用您在 CMake 構(gòu)建腳本中指定的名稱。如CMake生成的而文件是libnative-lib.so,只要指定加載native-lib即可。
package com.tsia.example.jnitest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String ret = stringFromJNI("world");
Log.i("jnitest", ret+"");
}
public native String stringFromJNI(String str);
}
然后在代碼中調(diào)用native方法打印結(jié)果。運(yùn)行后打印結(jié)果: Hello, world
上述就是一個(gè)簡(jiǎn)單的jni工程搭建步驟,如下是在構(gòu)建和運(yùn)行時(shí)的基本過(guò)程:
- Gradle調(diào)用外部構(gòu)建腳本CMakeLists.txt
- CMake按照構(gòu)建腳本中的命令將 C++源文件jnitest.cpp 編譯到共享的對(duì)象庫(kù)中,并命名為libnative-lib.so,Gradle隨后在編譯的時(shí)候會(huì)將其打包到APK中。
- 運(yùn)行時(shí),應(yīng)用的
MainActivity會(huì)使用System.loadLibrary()加載原生庫(kù)。這時(shí)候應(yīng)用可以使用庫(kù)的原生函數(shù)stringFromJNI(String str) -
MainActivity.onCreate()調(diào)用stringFromJNI("world"),這將返回“Hello, world”并打印。
參考:https://developer.android.com/studio/projects/add-native-code