前言
大家好旭斥!我又來了垂券,這次準(zhǔn)備著手寫一個(gè)JNI開發(fā)系列,畢竟算芯,現(xiàn)在JNI開發(fā)也是在各個(gè)公司越來越重要了凳宙,如果項(xiàng)目畢竟大,可能涉及的模塊較多届囚,比如你作為應(yīng)用層的開發(fā)削葱,難免避免不了需要使用一些庫,一些加密操作等等昔字,一般都會(huì)放在本地方法里面首繁,比較安全弦疮,人家丟給你so文件或者靜態(tài)a文件。咏尝。你不會(huì)用豈不是很尷尬。網(wǎng)上資料比較雜胎食,而且很亂允懂,大部分還是在用.mk的方法,本系列就基于CMake形式粥航,希望能夠帶著一些希望學(xué)習(xí)JNI開發(fā)的小伙伴一起學(xué)會(huì)JNI開發(fā)~
從一個(gè)栗子說起
注意:要支持CMake递雀,此時(shí)我們需要勾選 Include C++ support置侍,然后點(diǎn)擊Next--->Finish,完成工程的創(chuàng)建。
創(chuàng)建完成后,我們打開工程目錄,發(fā)現(xiàn)增加了幾個(gè)不一樣的地方:
發(fā)現(xiàn)AS已經(jīng)幫我們生產(chǎn)一個(gè)cpp目錄以及一個(gè)native-lib.cpp的c++文件秕衙,在根目錄下据忘,也多了一個(gè)CMakeLists.txt文件搞糕,我們主要來關(guān)注CMakeLists.txt里面的東東
#定義cmake支持的最小版本號(hào)
cmake_minimum_required(VERSION 3.4.1)
add_library( # 設(shè)置生成so庫的文件名稱,例如此處生成的so庫文件名稱應(yīng)該為:libnative-lib.so
native-lib
# 設(shè)置生成的so庫類型汉规,類型只包含兩種:
# STATIC:靜態(tài)庫驹吮,為目標(biāo)文件的歸檔文件碟狞,在鏈接其他目標(biāo)的時(shí)候使用
# SHARED:動(dòng)態(tài)庫,會(huì)被動(dòng)態(tài)鏈接频祝,在運(yùn)行時(shí)被加載
SHARED
# 設(shè)置源文件的位置,可以是很多個(gè)源文件沽一,都要添加進(jìn)來窟绷,注意相對(duì)位置
src/main/cpp/native-lib.cpp )
# 從系統(tǒng)里查找依賴庫,可添加多個(gè)
find_library( # 例如查找系統(tǒng)中的log庫liblog.so
log-lib
# liblog.so庫指定的名稱即為log,如同上面指定生成的libnative-lib.so庫名稱為native-lib一樣
log )
# 配置目標(biāo)庫的鏈接攘残,即相互依賴關(guān)系
target_link_libraries( # 目標(biāo)庫(最終生成的庫)
native-lib
# 依賴于log庫歼郭,一般情況下辐棒,如果依賴的是系統(tǒng)中的庫,需要加 ${} 進(jìn)行引用泰涂,
# 如果是第三方庫辐怕,可以直接引用庫名寄疏,例如:
# 引用第三方庫libthird.a,引用時(shí)直接寫成third;注意陕截,引用時(shí)农曲,每一行只能引用一個(gè)庫
${log-lib} )
這里我把注釋進(jìn)行了縮減,標(biāo)注了中文注釋罚渐,比較詳細(xì)驯妄,不明白的可以看下每個(gè)的作用,當(dāng)然還有很多API可以使用青扔,后續(xù)再詳細(xì)說明,也可以看看官方文檔谈息,[戳我戳我]
(https://developer.android.google.cn/ndk/guides/cmake)
我們新建一個(gè)Helper類來編寫native方法
public class NativeHelper {
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
public native int add(int a,int b);
}
打開我們的MainActivity
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
@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());
}
}
可以看到最上面侠仇,靜態(tài)代碼塊引用了native-lib這個(gè)庫逻炊,然后直接調(diào)用native本地方法,將C++中返回的字符串拿到進(jìn)行顯示豹休。然后看看C++具體是怎么是實(shí)現(xiàn)的
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
JNICALL
Java_com_example_hik_cmake_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
代碼很簡單桨吊,引用兩個(gè)頭文件视乐,然后定義了一個(gè)方法,返回“Hello from C++”這個(gè)字符串佑淀,那有的朋友要問了渣聚,為什么java層直接調(diào)用stringFromJNI()方法能夠直接映射到C++里面的方法呢僧叉,細(xì)心的小伙伴可能發(fā)現(xiàn)了瓶堕,C++里面的這個(gè)方法名很長而且很熟悉。谭梗。這不是Java包名加上方法名拼湊而成的字符串嗎宛蚓,這種方式呢叫做靜態(tài)注冊(cè),這樣就能通過這個(gè)映射方式找到C++中的方法远舅。
有的朋友又要說了,這么長方法名也太麻煩了序六,雖然可以自動(dòng)生成蚤吹,但是多不美觀裁着,多不優(yōu)雅。爆土。是滴诸蚕!有靜態(tài)注冊(cè)背犯,那當(dāng)然就有動(dòng)態(tài)注冊(cè)了~我們來改一改代碼:
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "JNI_"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
JNICALL
jstring backStringToJava(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
//動(dòng)態(tài)注冊(cè)
jint registerMethod(JNIEnv *env) {
jclass clz = env->FindClass("com/example/taolin/jni_project/NativeHelper");
if (clz == NULL) {
LOGD("con't find class: com/example/taolin/jni_project/NativeHelper");
}
JNINativeMethod jniNativeMethod[] = {{"stringFromJNI", "()Ljava/lang/String;", (void *) backStringToJava}};
return env->RegisterNatives(clz,jniNativeMethod, sizeof(jniNativeMethod)/ sizeof(jniNativeMethod[0]));
}
jint JNI_OnLoad(JavaVM *vm, void *reserved){
JNIEnv * env = NULL;
if (vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
return JNI_ERR;
}
jint result = registerMethod(env);
LOGD("RegisterNatives result: %d", result);
return JNI_VERSION_1_6;
}
這里呢漠魏,為了在C++中打印日志,我們需要引入log.h頭文件哪自,然后我們把之前的方法名改成backStringToJava禁熏,然后因?yàn)闆]有了靜態(tài)注冊(cè)的規(guī)則瞧毙,Java層調(diào)用的使用當(dāng)然就找不到我們對(duì)應(yīng)的方法了,我們定義一個(gè)動(dòng)態(tài)注冊(cè)的方法矩动,將Java中的方法和C++中的方法進(jìn)行動(dòng)態(tài)的綁定:
- 通過env指針释漆,拿到MainActivity的class對(duì)象灵汪,具體env指針后續(xù)會(huì)詳細(xì)說明
- 定義一系列的方法對(duì)象柑潦,每個(gè)包含三個(gè)參數(shù)渗鬼,第一個(gè)是java中的方法名荧琼,第二個(gè)是方法對(duì)應(yīng)的簽名,第三個(gè)是C++中的方法名
- 在JNI_OnLoad方法中堰乔,調(diào)用動(dòng)態(tài)注冊(cè)綁定方法進(jìn)行綁定
有些朋友可能對(duì)方法簽名不太明白脐恩,后續(xù)語法會(huì)詳細(xì)說明驶冒,這里先簡單說下,方法簽名也就是一個(gè)方法唯一性的一個(gè)標(biāo)準(zhǔn)崇猫,上面的()Ljava/lang/String;就是stringFromJNI的簽名需忿,前面的括號(hào)里面是參數(shù)的簽名屋厘,因?yàn)檫@里沒有參數(shù),所以為空澈魄,緊接著后面是返回值得簽名仲翎,規(guī)則是溯香,如果是基本數(shù)據(jù)類型就是相對(duì)應(yīng)的基本數(shù)據(jù)類型浓恶,如果不是基本數(shù)據(jù)類型,那么就是L+對(duì)象包名+“;”湿镀,注意這里的分號(hào)不可省略C愠铡!根據(jù)這個(gè)規(guī)則瀑罗,下面那個(gè)方法的簽名就是(II)I雏掠,依次類推乡话,沒明白的也沒關(guān)系,后面會(huì)詳細(xì)對(duì)JNI中的語法詳細(xì)解釋诬像,先知道有這么回事就好了时迫。
public native String stringFromJNI();
//簽名:()Ljava/lang/String;
public native int add(int a int b)
//簽名:"(II)I"
然后我們直接運(yùn)行APP掠拳,可以發(fā)現(xiàn)頁面上顯示出來了“Hello from C++”字符串,然后看看我們生成的so文件在哪:
OK喊熟!大功告成芥牌,我們的第一步就完成了聂使,成功的完成了Java調(diào)用C++的方法,但別高興的太早弃理,這只是第一步痘昌,好了,看到這的獎(jiǎng)勵(lì)自己跟辣條吧~算灸,溜了溜了驻啤。街佑。