JNI的開發中,Java層的方法和C/C++層的函數之間的對應關係是通過註冊來實現的,要不然它怎麼知道java的方法到了c/c++去找哪個對應的方法呢?JNI的方法註冊分爲靜態註冊和動態註冊。
3.1 靜態註冊
靜態註冊使用Java_PACKAGENAME_CLASSNAME_METHODNAME 來進行與java方法的匹配
一般步驟如下:
1)編寫java類,假如是JniTest.java
2)在命令行下輸入 javac JniTest.java 生成JniTest.class文件
3) 在 JniTest.class 目錄下 通過 javah xxx.JniTest(全類名)生成 xxx_JniTest.h 頭文件
4)編寫xxx_JniTest.c 源文件,並拷貝xxx_JniTest.h 下的函數,並實現這些函數,且在其中添加jni.h頭文件;
5)編寫 cmakelist.txt 文件,編譯生成動態/靜態鏈接庫
例如,下面就是靜態註冊的cpp文件的大概的模板:
#include <jni.h>
#include <string>
#include <iostream>
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_ndk_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_ndk_MainActivity_sayHello(
JNIEnv *env,
jclass clazz,
jstring str) {
const char* helloText = env->GetStringUTFChars(str, JNI_FALSE);
std::string str1 = "I receive your words:";
std::string result = str1 + helloText;
env->ReleaseStringUTFChars(str, helloText);
return env->NewStringUTF(result.c_str());
}
extern "C" JNIEXPORT jstring JNICALL是固定的,其中extern "c"的是意思是告訴編譯器按照C語言的方式去編譯函數,這是爲了C/C++混合編程,JNICALL是表示這是一個被JNI調用的函數,Java+方法的全路徑名就是靜態註冊的方法名。
JNI的方法有兩個固定參數:
1. 如果java層是普通方法,前面的兩個參數固定爲(JNIEnv *env, jobject thiz)
2. 如果java層是靜態方法,前面的兩個參數固定爲 (JNIEnv *env, jclass clazz)
JNIEnv表示是JNI的上下文環境,它裏面封裝了很多JNI的方法,jobject表示的java層調用的這個對象的this指針,jclass表示的java的類,因爲靜態方法不屬於某個對象,而是屬於類。
3.2. 動態註冊
我們在上面代碼中使用的 Java_PACKAGENAME_CLASSNAME_METHODNAME 來進行與java方法的匹配,這
種方式我們稱之爲靜態註冊。而動態註冊則意味着方法名可以不用這麼長了,在android aosp源碼中就大量的使用了動態註冊的形式
//Java:
native void dynamicNative();
native String dynamicNative(int i);
//C++:
void dynamicNative1(JNIEnv *env, jobject jobj){
LOGE("dynamicNative1 動態註冊");
}
jstring dynamicNative2(JNIEnv *env, jobject jobj,jint i){
return env->NewStringUTF("我是動態註冊的dynamicNative2方法");
}
//需要動態註冊的方法數組
static const JNINativeMethod mMethods[] = {
{"dynamicNative","()V", (void *)dynamicNative1},
{"dynamicNative", "(I)Ljava/lang/String;", (jstring *)dynamicNative2}
};
//需要動態註冊native方法的類名
static const char* mClassName = "com/dongnao/jnitest/MainActivity";
jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
//獲得 JniEnv
int r = vm->GetEnv((void**) &env, JNI_VERSION_1_4);
if( r != JNI_OK){
return -1;
}
jclass mainActivityCls = env->FindClass( mClassName);
// 註冊 如果小於0則註冊失敗
r = env->RegisterNatives(mainActivityCls,mMethods,2);
if(r != JNI_OK ) {
return -1;
}
return JNI_VERSION_1_4;
}
JNI_OnLoad方法是在我們調用System.loadLibrary的時候調用的,這相當於在加載so庫的時候,把我們的java方法和JNI層的方法做了一個映射,這樣就不需要寫一串很長的方法名了,使用也靈活。
3.3 system.load()/system.loadLibrary() 區別
最後說一下 system.load()/system.loadLibrary()的區別
System.load
System.load 參數必須爲庫文件的絕對路徑,可以是任意路徑,例如: System.load("C:\Documents and
Settings\TestJNI.dll"); //Windows
System.load("/usr/lib/TestJNI.so"); //Linux
System.loadLibrary
System.loadLibrary 參數爲庫文件名,不包含庫文件的擴展名。
System.loadLibrary ("TestJNI"); //加載Windows下的TestJNI.dll本地庫
System.loadLibrary ("TestJNI"); //加載Linux下的libTestJNI.so本地庫