Android JNI開發筆記三:靜態註冊和動態註冊

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本地庫

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章