一.動態註冊和靜態註冊
註冊native方法有兩種方式,動態註冊和靜態註冊。靜態註冊是在編譯時進行註冊,而且在java中宣告的native方法和c/c++中的本地方法的對應關係是恆定的;比如說在com.example.test包下的Test類中宣告了一個stringFromJNI()的native方法,那麼它對應的c/c++中的本地方法名就是Java_com_example_test_Test_stringFromJNI();並且這個方法名不能做任何的修改,在java中呼叫stringFromJNI()函數時,就會按包名_類名_方法名的形式找到對應的方法並呼叫。而動態註冊是在執行時進行註冊的,而且本地方法的名字可以按自己的喜好隨意取,只要說明了java中宣告的native方法和c/c++中的本地方法的對應關係即可。下面用程式碼的形式來演示一下動態註冊的使用步驟。
二.動態註冊的步驟
1.在java中宣告native方法,並在靜態程式碼塊中載入動態庫:
public class Test { static{ System.loadLibrary("dynamic"); //載入動態庫 }
//宣告native方法 native String stringFromJNI(); native static int add(int a,int b); }
2.註冊函數:在java中載入動態庫的時候,虛擬機器器會呼叫JNI庫中的JNI_Onload()函數,動態註冊就是在這個函數中進行的。動態註冊使用的是RegisterNatives()方法,這個方法接收3個引數,分別是:
1.jclass clazz 宣告native方法的java類
2.const JNINativeMethod* methods JNINativeMethod型別的結構體陣列,我們就是在這個結構體陣列中說明java方法和本地方法的對應關係的
3.jint nMethods 第二個引數methods所指向的結構體陣列的大小
JNINativeMethod結構體的定義如下:
typedef struct { const char* name;//java中的方法名 const char* signature;//jni簽名 void* fnPtr;//本地函數的指標 } JNINativeMethod;
下面給出這部分的程式碼:
#include <jni.h> #include <string> using namespace std; /**
*實現本地方法,名字可以任取,方法的前兩個引數是固定的,後面的引數就是實際的引數
*/ jstring native_stringFromJNI(JNIEnv *env,jobject thiz){ return env->NewStringUTF("nihao"); } jint native_add(JNIEnv *env,jobject thiz,jint a,jint b){ return a+b; } static const JNINativeMethod nativeMethod[]={ {"stringFromJNI","()Ljava/lang/String;", (void *) native_stringFromJNI}, {"add","(II)I",(void *)native_add} }; static int registerNativeMethod(JNIEnv *env){ int result=-1; jclass clazz=env->FindClass("com/example/dynamicregister/Test"); if(env->RegisterNatives(clazz,nativeMethod,sizeof(nativeMethod)/sizeof(nativeMethod[0]))==JNI_OK){ result=0; } return result; } jint JNI_OnLoad(JavaVM *vm,void *reserved){//這個方法是一個override方法,在載入動態庫時,會自動呼叫,一般用來做一些初始化操作,動態註冊的程式碼就可以寫在這 JNIEnv *env= nullptr; if(vm->GetEnv((void **)&env,JNI_VERSION_1_4)==JNI_OK){//首先需要獲取JNIEnv *env指標,因為registerNativeMethod方法會用到 if(registerNativeMethod(env)==0){ return JNI_VERSION_1_4; //返回值代表動態庫需要的jni版本 } } return -1; }
3.在java中呼叫native函數:
public class MainActivity extends AppCompatActivity { private TextView tv_display; @SuppressLint("MissingInflatedId") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_display=findViewById(R.id.tv_display); tv_display.setText(Test.add(1,10)+""); } }
三.JNI簽名
下面我把和簽名有關的程式碼單獨拿出來進行說明:
static const JNINativeMethod nativeMethod[]={ {"stringFromJNI","()Ljava/lang/String;", (void *) native_stringFromJNI}, {"add","(II)I",(void *)native_add} };
比如第一個函數的簽名:()Ljava/lang/String;其實表示的是java中的stringFromJNI函數的形參為空,返回值型別為String。
第二個函數的簽名(II)I表示java中的add函數的形參列表是(int,int),返回值型別也是int型別。
那這個簽名有什麼作用呢?其實是為了解決java中的函數過載問題。比如,如果java中還宣告了一個方法,native String stringFromJNI(String str);那麼如果沒有函數簽名的話,就不知道c/c++中的native_stringFromJNI()對應的是java中的哪個stringFromJNI函數。
接下來,通過一個表格來說明JNI基本型別的簽名以及參照型別的簽名:
特別需要注意的是,參照型別簽名後面的分號;一定不能省略,否則編譯通過不了。