Android JNI基礎篇
前言
JNI學習其實並不難,在這裏,我將引導大家學習JNI的基礎知識,認真學完本教程,你將更加堅信我說的話。來吧,我們一起學習!
JNI基礎
JNI是什麼?
JNI的全稱就是Java Native Interface,顧名思義,就是Java和C/C++相互通信的接口,就好比買賣房子都需要找中介一樣,這裏的JNI就是Java和C/C++通信的中介,一箇中間人。
JNI頭文件
JNI開發前提是要引入jni.h頭文件,這個文件Android NDK目錄下面
示例如下:
#include<jni.h>
怎麼加載so庫?
Android提供了3個實用的函數用來加載JNI庫,分別是System.loadLibrary(libname),Runtime.getRuntime().loadLibrary(libname),以及Runtime.getRuntime().load(libFilePath)。
用loadLibrary函數加載
用System.loadLibrary(libname)和Runtime.getRuntime().loadLibrary(libname)這兩個函數加載so庫,不需要指定so庫的路徑,Android會默認從系統的共享庫目錄裏面去查找,Android的共享庫目錄就是vendor/lib和system/lib,如果在共享庫路徑裏面找到指定名字的so庫,就會立即加載這個so庫,所以我們給so庫起名的時候要儘量避免和Android共享庫裏面的so庫同名。如果在共享庫目錄裏面查找不到,就會在APP的安裝目錄裏面查找APP的私有so庫,如果查找到,會立即加載這個so庫。
用load函數加載
Runtime.getRuntime().load(libFilePath)用這個函數加載so庫,需要指定完整的so庫路徑,優點是加載速度快,並且不會加載錯誤的so庫,缺點就是需要指定完整的so庫路徑,有時候並不方便,大家常用的加載so庫的方式還是用loadLibrary函數來加載。
加載so庫示例
static { System.loadLibrary("native-lib"); //用這種方式加載so庫和System.loadLibrary函數加載so庫的效果是一樣的 //Runtime.getRuntime().loadLibrary("native-lib"); //String soLibFilePath; //用這種方式加載so庫需要指定完整的so庫路徑 //Runtime.getRuntime().load(soLibFilePath); }
Android Studio so庫配置
Android Studio通過CMakeLists.txt文件配置需要生成的so庫,下面詳細給大家介紹一下這個CMakeLists.txt文件如何配置。
Android Studio通過cmake命令來生成so庫。
CMakeLists.txt文件配置詳解
add_library
add_library函數用來配置要生成的so庫的基本信息,比如庫的名字,要生成的so庫是靜態的還是共享的,so庫的C/C++源文件列表
示例如下:
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/native-lib2.cpp
src/main/cpp/native-lib3.cpp)
第一個參數是so庫的名字
第二個參數是要生成的so庫的類型,靜態so庫是STATIC,共享so庫是SHARED
第三個參數是C/C++源文件,可以包括多個源文件
find_library
find_library函數用來從NDK目錄下面查找特定的so庫
示例如下:
find_library( log-lib
log )
第一個參數是我們給要查找的so庫起的名字,名字可以隨便寫
第二個參數是要查找的so庫的名字,這個名字是從真實的so庫的名字去掉前綴和後綴後的名字,比如liblog.so這個so庫的名字就是log
target_link_libraries
target_link_libraries函數用來把要生成的so庫和依賴的其它so庫進行鏈接,生成我們需要的so庫文件
示例如下:
target_link_libraries( native-lib
${log-lib} )
第一個參數是我們要生成的so庫的名字去掉前綴和後綴後的名字,在這個例子中,要生成的真實的so庫的名字是libnative-lib.so
第二個參數是鏈接我們用find_library函數定義的查找的依賴庫的名字log-lib,語法就是${依賴的庫的名字}
Java和JNI類型對照表
這裏詳細介紹一下Java類型和C/C++類型的對照關係,方便我們下面的學習,這一部分知識很基礎,也很重要。
Java和JNI基本類型對照表
Java的基本類型可以直接與C/C++的基本類型映射,因此Java的基本類型對開發人員是透明的。
Java類型 |
JNI類型 |
C/C++類型 |
大小 |
Boolean |
jboolean |
unsigned char |
無符號8位 |
Byte |
jbyte |
char |
有符號8位 |
Char |
jchar |
unsigned short |
無符號16位 |
Short |
jshort |
short |
有符號16位 |
Integer |
jint |
int |
有符號32位 |
Long |
jlong |
long long |
有符號64位 |
Float |
jfloat |
float |
32位浮點值 |
Double |
jdouble |
double |
64位雙精度浮點值 |
Java和JNI引用類型對照表
與Java基本類型不同,引用類型對開發人員是不透明的。Java類的內部數據結構並不直接向原生代碼公開。也就是說原生C/C++代碼並不能直接訪問Java代碼的字段和方法。
Java類型 |
C/C++類型 |
java.lang.Class |
jclass |
java.lang.Throwable |
jthrowable |
java.lang.String |
jstring |
java.lang.Object |
jobject |
java.util.Objects |
jobjects |
java.lang.Object[] |
jobjectArray |
Boolean[] |
jbooleanArray |
Byte[] |
jbyteArray |
Char[] |
jcharArray |
Short[] |
jshortArray |
int[] |
jintArray |
long[] |
jlongArray |
float[] |
jfloatArray |
double[] |
jdoubleArray |
通用數組 |
jarray |
說明任何Java數組在JNI裏面都可以使用jarray來表示,比如Java的int[]數組,用JNI可以表示爲jintArray,也可以表示爲jarray
JNI函數詳解
JNI字符串相關的函數
C/C++字符串轉JNI字符串
NewString函數用來生成Unicode JNI字符串
NewStringUTF函數用來生成UTF-8 JNI字符串
示例如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv*
env, jobject
thiz,jstring
jstr) {
char
*str="helloboy";
jstring
jstr2=env->NewStringUTF(str);
const
jchar
*jchar2=env->GetStringChars(jstr,NULL);
size_t
len=env->GetStringLength(jstr);
jstring
jstr3=env->NewString(jchar2,len);
}
JNI字符串轉C/C++字符串
GetStringChars函數用來從jstring獲取Unicode C/C++字符串
GetStringUTFChars函數用來從jstring獲取UTF-8 C/C++字符串
示例如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv*
env, jobject
thiz,jstring
jstr) {
const char
*str=env->GetStringUTFChars(jstr,NULL);
const
jchar
*jchar2=env->GetStringChars(jstr,NULL);
}
釋放JNI字符串
ReleaseStringChars函數用來釋放Unicode C/C++字符串
ReleaseStringUTFChars函數用來釋放UTF-8 C/C++字符串
示例如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv*
env, jobject
thiz,jstring
jstr) {
const char
*str=env->GetStringUTFChars(jstr,NULL);
env->ReleaseStringUTFChars(jstr,str);
const
jchar
*jchar2=env->GetStringChars(jstr,NULL);
env->ReleaseStringChars(jstr,jchar2);
}
JNI字符串截取
GetStringRegion函數用來截取Unicode JNI字符串
GetStringUTFRegion函數用來截取UTF-8 JNI字符串
示例如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv*
env, jobject
thiz,jstring
jstr) {
const char
*str=env->GetStringUTFChars(jstr,NULL);
char
*subStr=new char;
env->GetStringUTFRegion(jstr,0,3,subStr);
env->ReleaseStringUTFChars(jstr,str);
const
jchar
*jchar2=env->GetStringChars(jstr,NULL);
jchar
*subJstr=new
jchar;
env->GetStringRegion(jstr,0,3,subJstr);
env->ReleaseStringChars(jstr,jchar2);
}
獲取JNI字符串的長度
GetStringLength用來獲取Unicode JNI字符串的長度
GetStringUTFLength函數用來獲取UTF-8 JNI字符串的長度
示例如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJString(JNIEnv*
env, jobject
thiz,jstring
jstr) {
jsize
len=env->GetStringLength(jstr);
jsize
len2=env->GetStringUTFLength(jstr);
}
JNI數組相關的函數
JNI數組相關的類
JNI類 |
備註 |
jbooleanArray |
對應Java的boolean[] |
jbyteArray |
對應Java的byte[] |
jcharArray |
對應Java的char[] |
jshortArray |
對應Java的short[] |
jintArray |
對應Java的int[] |
jlongArray |
對應Java的long[] |
jfloatArray |
對應Java的float[] |
jdoubleArray |
對應Java的double[] |
jobjectArray |
對應Java的對象數組object[] |
JNI基本類型數組
獲取JNI基本類型數組元素
Get<Type>ArrayElements函數用來獲取基本類型JNI數組的元素,這裏面的<Type>需要被替換成實際的類型,比如GetIntArrayElements,GetLongArrayElements等
示例代碼如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv*
env, jobject
thiz,jintArray
array) {
jint
*intArray=env->GetIntArrayElements(array,NULL);
int
len=env->GetArrayLength(array);
for(int
i=0;i<len;i++){
jint
item=intArray[i];
}
}
獲取JNI基本類型數組的子數組
Get<Type>ArrayRegion函數用來獲取JNI數組的子數組,這裏面的<Type>需要被替換成實際的類型,比如GetIntArrayRegion,GetLongArrayRegion等
示例如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv*
env, jobject
thiz,jintArray
array) {
jint
*subArray=new
jint;
env->GetIntArrayRegion(array,0,3,subArray);
}
設置JNI基本類型數組的子數組
Set<Type>ArrayRegion函數用來獲取JNI基本類型數組的子數組,這裏面的<Type>需要被替換成實際的類型,比如SetIntArrayRegion,SetLongArrayRegion等
示例如下:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv*
env, jobject
thiz,jintArray
array) {
jint
*subArray=new
jint;
env->GetIntArrayRegion(array,0,3,subArray);
env->SetIntArrayRegion(array,0,3,subArray);
}
JNI對象數組
GetObjectArrayElement函數用來獲取JNI對象數組元素
SetObjectArrayElement函數用來設置JNI對象數組元素
示例如下:
extern "C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJObjectArray(JNIEnv*
env, jobject
thiz,jobjectArray
array) {
int
len=env->GetArrayLength(array);
for(int
i=0;i<len;i++)
{
jobject
item=env->GetObjectArrayElement(array,i);
}
}
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJStringArray(JNIEnv*
env, jobject
thiz,jobjectArray
array) {
int
len=env->GetArrayLength(array);
for(int
i=0;i<len;i++)
{
jstring
item=(jstring)env->GetObjectArrayElement(array,i);
}
}
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJObjectArray(JNIEnv*
env, jobject
thiz,jobjectArray
array) {
jobject
obj;
env->SetObjectArrayElement(array,1,obj);
}
獲取JNI數組的長度
GetArrayLength用來獲取數組的長度
示例如下:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_testJObjectArray(JNIEnv* env, jobject thiz,jobjectArray array) { int len=env->GetArrayLength(array); }
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testJIntArray(JNIEnv*
env, jobject
thiz,jintArray
array) {
int
len=env->GetArrayLength(array);
}
JNI NIO緩衝區相關的函數
使用NIO緩衝區可以在Java和JNI代碼中共享大數據,性能比傳遞數組要快很多,當Java和JNI需要傳遞大數據時,推薦使用NIO緩衝區的方式來傳遞。
NewDirectByteBuffer函數用來創建NIO緩衝區
GetDirectBufferAddress函數用來獲取NIO緩衝區的內容
GetDirectBufferCapacity函數用來獲取NIO緩衝區的大小
示例代碼如下:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_testDirectBuffer(JNIEnv* env, jobject thiz) { const char *data="hello world"; int len=strlen(data); jobject obj=env->NewDirectByteBuffer((void*)data,len); long capicity=env->GetDirectBufferCapacity(obj); char *data2=(char*)env->GetDirectBufferAddress(obj); }
JNI訪問Java類的方法和字段
Java類型簽名映射表
JNI獲取Java類的方法ID和字段ID,都需要一個很重要的參數,就是Java類的方法和字段的簽名,這個簽名需要通過下面的表來獲取,這個表很重要,建議大家一定要記住。
Java類型 |
簽名 |
Boolean |
Z |
Byte |
B |
Char |
C |
Short |
S |
Integer |
I |
Long |
J |
Float |
F |
Double |
D |
Void |
V |
任何Java類的全名 |
L任何Java類的全名; 比如Java String類對應的簽名是Ljava/lang/String; |
type[] |
type[ 這個就是Java數組的簽名,比如Java int[]的簽名是[I,Java long[]的簽名就是[J,Java String[]的簽名是 [Ljava/lang/String; |
方法類型 |
(參數類型)返回值 類型, 比如Java方法void hello(String msg,String msg2)對應的簽名就是(Ljava/lang/String; Ljava/lang/String;)V 再比如Java方法String getNewName(String name)對應的簽名是(Ljava/lang/String;) Ljava/lang/String; 再比如Java方法long add(int a,int b)對應的簽名是(II)J |
JNI訪問Java類方法相關的函數
JNI訪問Java類的實例方法
GetObjectClass函數用來獲取Java對象對應的類類型
GetMethodID函數用來獲取Java類實例方法的方法ID
Call<Type>Method函數用來調用Java類實例特定返回值的方法,比如CallVoidMethod,調用java沒有返回值的方法,CallLongMethod用來調用Java返回值爲Long的方法,等等。
示例如下:
Java代碼:
public native void callJavaHelloWorld2();
public void helloWorld2(String msg){ Log.i("hello","hello world "+msg); }
JNI代碼:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld2(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorld2_methodID=env->GetMethodID(clazz,"helloWorld2","(java/lang/String;)V"); if(helloWorld2_methodID==NULL) return; const char *msg="hello world"; jstring jmsg=env->NewStringUTF(msg); env->CallVoidMethod(thiz,helloWorld2_methodID,jmsg); }
JNI訪問Java類的靜態方法
GetObjectClass函數用來獲取Java對象對應的類類型
GetStaticMethodID函數用來獲取Java類靜態方法的方法ID
CallStatic<Type>Method函數用來調用Java類特定返回值的靜態方法,比如CallStaticVoidMethod,調用java沒有返回值的靜態方法,CallStaticLongMethod用來調用Java返回值爲Long的靜態方法,等等。
示例如下:
Java代碼:
public native void callStaticJavaHelloWorld2();
public static void helloWorldStatic2(String msg){ Log.i("hello","hello world static "+msg); }
JNI代碼:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld2(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorldStatic2_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic2","(java/lang/String;)V"); if(helloWorldStatic2_methodID==NULL) return; const char *msg="hello world"; jstring jmsg=env->NewStringUTF(msg); env->CallStaticVoidMethod(clazz,helloWorldStatic2_methodID,msg); }
JNI訪問Java類字段相關的函數
JNI訪問Java類實例字段
GetFieldID函數用來獲取Java字段的字段ID
Get<Type>Field用來獲取Java類字段的值,比如用GetIntField函數獲取Java int型字段的值,用GetLongField函數獲取Java long字段的值,用GetObjectField函數獲取Java引用類型字段的值
示例如下:
Java代碼:
public class Person{ public String name; public int age; } public native void getJavaObjectField(Person person);
private void test(){ Person person=new Person(); person.name="wubb"; person.age=20; getJavaObjectField(person); }
JNI代碼:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_getJavaObjectField(JNIEnv* env, jobject thiz,jobject person) { jclass clazz=env->GetObjectClass(person); jfieldID name_fieldID=env->GetFieldID(clazz,"name","Ljava/lang/String;"); jstring name=(jstring) env->GetObjectField(person,name_fieldID); jfieldID age_fieldID=env->GetFieldID(clazz,"age","I"); jint age=env->GetIntField(person,age_fieldID); }
JNI訪問Java類靜態字段
GetStaticFieldID函數用來獲取Java靜態字段的字段ID
GetStatic<Type>Field用來獲取Java類靜態字段的值,比如用GetStaticIntField函數獲取Java 靜態int型字段的值,用GetStaticLongField函數獲取Java 靜態long字段的值,用GetStaticObjectField函數獲取Java靜態引用類型字段的值
示例如下:
Java代碼:
public class Person { public String name; public int age; public static String name_static; public static int age_static; }
public native void getJavaObjectStaticField(Person person);
private void test(){ Person.name_static="wubb"; Person.age_static=20; Person person=new Person(); getJavaObjectStaticField(person); }
JNI代碼:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_getJavaObjectStaticField(JNIEnv* env, jobject thiz,jobject person) { jclass clazz=env->GetObjectClass(person); jfieldID name_fieldID=env->GetStaticFieldID(clazz,"name_static","Ljava/lang/String;"); jstring name=(jstring) env->GetStaticObjectField(clazz,name_fieldID); jfieldID age_fieldID=env->GetStaticFieldID(clazz,"age_static","I"); jint age=env->GetStaticIntField(clazz,age_fieldID); }
JNI線程同步相關的函數
JNI可以使用Java對象進行線程同步
MonitorEnter函數用來鎖定Java對象
MonitorExit函數用來釋放Java對象鎖
示例如下:
Java代碼:
jniLock(new Object());
JNI代碼:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_jniLock(JNIEnv* env, jobject thiz,jobject obj) { env->MonitorEnter(obj); //do something env->MonitorExit(obj); }
JNI異常相關的函數
JNI處理Java異常
當JNI函數調用的Java方法出現異常的時候,並不會影響JNI方法的執行,但是我們並不推薦JNI函數忽略Java方法出現的異常繼續執行,這樣可能會帶來更多的問題。我們推薦的方法是,當JNI函數調用的Java方法出現異常的時候,JNI函數應該合理的停止執行代碼。
ExceptionOccurred函數用來判斷JNI函數調用的Java方法是否出現異常
ExceptionClear函數用來清除JNI函數調用的Java方法出現的異常
請看如下示例:
Java代碼
public void helloWorld(){ throw new NullPointerException("null pointer occurred"); //Log.i("hello","hello world"); }
C++代碼
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorld_methodID=env->GetMethodID(clazz,"helloWorld","()V"); if(helloWorld_methodID==NULL) return; env->CallVoidMethod(thiz,helloWorld_methodID); if(env->ExceptionOccurred()!=NULL){ env->ExceptionClear(); __android_log_print(ANDROID_LOG_VERBOSE,"hello","%s","program end with java exception"); return; } __android_log_print(ANDROID_LOG_VERBOSE,"hello","%s","program end normallly"); }
JNI拋出Java類型的異常
JNI通過ThrowNew函數拋出Java類型的異常
示例如下:
Java代碼
try { testNativeException(); } catch (NullPointerException e){ e.printStackTrace(); }
C++代碼
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_testNativeException(JNIEnv* env, jobject thiz) { jclass clazz=env->FindClass("java/lang/NullPointerException"); if(clazz==NULL) return; env->ThrowNew(clazz,"null pointer exception occurred"); }
JNI對象的全局引用和局部引用
我們知道Java代碼的內存是由垃圾回收器來管理,而JNI代碼則不受Java的垃圾回收器來管理,所以JNI代碼提供了一組函數,來管理通過JNI代碼生成的JNI對象,比如jobject,jclass,jstring,jarray等,對於這些對象,我們不能簡單的在JNI代碼裏面聲明一個全局變量,然後把JNI對象賦值給全局變量,我們需要採用JNI代碼提供的專有函數來管理這些全局的JNI對象。
JNI對象的局部引用
在JNI接口函數中引用JNI對象的局部變量,都是對JNI對象的局部引用,一旦JNI接口函數返回,所有這些JNI對象都會被自動釋放。不過我們也可以採用JNI代碼提供的DeleteLocalRef函數來刪除一個局部JNI對象引用
請看下面的示例代碼:
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testDeleteLocalRef(JNIEnv*
env, jobject
thiz) {
jclass
clazz=env->GetObjectClass(thiz);
if(clazz==NULL)
return;
jmethodID
helloWorld_methodID=env->GetMethodID(clazz,"helloWorld","()V");
if(helloWorld_methodID==NULL)
return;
env->CallVoidMethod(thiz,helloWorld_methodID);
env->DeleteLocalRef(clazz);
}
JNI對象的全局引用
對於JNI對象,絕對不能簡單的聲明一個全局變量,在JNI接口函數裏面給這個全局變量賦值這麼簡單,一定要使用JNI代碼提供的管理JNI對象的函數,否則代碼可能會出現預想不到的問題。JNI對象的全局引用分爲兩種,一種是強全局引用,這種引用會阻止Java的垃圾回收器回收JNI代碼引用的Java對象,另一種是弱全局引用,這種全局引用則不會阻止垃圾回收器回收JNI代碼引用的Java對象。
強全局引用
NewGlobalRef用來創建強全局引用的JNI對象
DeleteGlobalRef用來刪除強全局引用的JNI對象
示例如下:
jobject
gThiz;
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testStrongGlobalRef(JNIEnv*
env, jobject
thiz) {
//gThiz=thiz;//不能這樣給全局JNI對象賦值,要採用下面這種方式
gThiz=env->NewGlobalRef(thiz);//生成全局的JNI對象引用,這樣生成的全局的JNI對象纔可以在其它函數中使用
env->DeleteGlobalRef(gThiz);//假如我們不需要gThiz這個全局的JNI對象引用,我們可以把它刪除掉
}
弱全局引用
NewWeakGlobalRef用來創建弱全局引用的JNI對象
DeleteWeakGlobalRef用來刪除弱全局引用的JNI對象
IsSameObject用來判斷兩個JNI對象是否相同
示例如下:
jobject
gThiz;
extern
"C"
JNIEXPORT
void JNICALL
Java_com_kgdwbb_jnistudy_MainActivity_testWeakGlobalRef(JNIEnv*env,
jobject
thiz) {
//gThiz=thiz;//不能這樣給全局JNI對象賦值,要採用下面這種方式
gThiz=env->NewWeakGlobalRef(thiz);//生成全局的JNI對象引用,這樣生成的全局的JNI對象纔可以在其它函數中使用
if(env->IsSameObject(gThiz,NULL)){
//弱全局引用已經被Java的垃圾回收器回收
}
env->DeleteWeakGlobalRef(gThiz);//假如我們不需要gThiz這個全局的JNI對象引用,我們可以把它刪除掉
}
Java代碼和JNI代碼通信
Java通過JNI接口調用C/C++方法
首先我們需要在Java代碼裏面聲明Native方法原型,比如:
public native void helloJNI(String msg);
其次我們需要在C/C++代碼裏面聲明JNI方法原型,比如:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv* env, jobject thiz,jstring msg) { //do something }
現在這段JNI函數聲明代碼採用的是C++語言寫的,所以需要添加extern "C"聲明,如果源代碼是C語言,則不需要添加這個聲明。
JNIEXPORT 這個關鍵字說明這個函數是一個可導出函數,學過C/C++的朋友都知道,C/C++ 庫裏面的函數有些可以直接被外部調用,有些不可以,原因就是每一個C/C++庫都有一個導出函數列表,只有在這個列表裏面的函數纔可以被外部直接調用,類似Java的public函數和private函數的區別。
JNICALL 說明這個函數是一個JNI函數,用來和普通的C/C++函數進行區別,實際發現不加這個關鍵字,Java也是可以調用這個JNI函數的。
Void 說明這個函數的返回值是void,如果需要返回值,則把這個關鍵字替換成要返回的類型即可。
Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv*env, jobject thiz,jstring msg)這是完整的JNI函數聲明,JNI函數名的原型如下:
Java_ + JNI方法所在的完整的類名,把類名裏面的”.”替換成”_” + 真實的JNI方法名,這個方法名要和Java代碼裏面聲明的JNI方法名一樣+ JNI函數必須的默認參數(JNIEnv* env, jobjectthiz)
env參數是一個指向JNIEnv函數表的指針,
thiz參數代表的就是聲明這個JNI方法的Java類的引用
msg參數就是和Java聲明的JNI函數的msg參數對於的JNI函數參數
JNI函數的原型
[extern “C”]JNIEXPORT 函數返回值 JNICALL 完整的函數聲明(JNIENV *env, jobject thiz, …)
其中extern “C”根據需要動態添加,如果是C++代碼,則必須要添加extern “C”聲明,如果是C代碼,則不用添加
靜態JNI方法和實例JNI方法區別
先看一個示例:
Java代碼:
public native void showHello(); public native static void showHello2();
C++代碼:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_showHello(JNIEnv* env, jobject thiz) { //do something } extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_showHello2(JNIEnv* env, jclass thiz) { //do something }
相信明眼的同學很快就能發現這兩個JNI函數的區別,對就是這個區別,普通的JNI方法對應的JNI函數的第二個參數是jobject類型,而靜態的JNI方法對應的JNI函數的第二個參數是jclass類型
常見的Java JNI方法聲明和JNI函數聲明示例
Java Native方法聲明:
public class Person{ public String name; public int age; } public native void helloJNI(String msg); public native int func1(int a,int b); public native String func2(String str); public native void func3(boolean b); public native void func4(Person person);
public native static void func5();
C++JNI函數聲明:
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_helloJNI(JNIEnv* env, jobject thiz,jstring msg) { //do something } extern "C" JNIEXPORT jint JNICALL Java_com_kgdwbb_jnistudy_MainActivity_func1(JNIEnv* env, jobject thiz,jint a,jint b) { //do something } extern "C" JNIEXPORT jstring JNICALL Java_com_kgdwbb_jnistudy_MainActivity_func2(JNIEnv* env, jobject thiz,jstring str) { //do something } extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_func3(JNIEnv* env, jobject thiz,jboolean b) { //do something } extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_func4(JNIEnv* env, jobject thiz,jobject person) { //do something }
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_func5(JNIEnv* env, jclass thiz) { //do something }
所有的Java類對象在JNI函數裏面都使用jobject來表示
JNI代碼和Java代碼通信
C++調用Java實例方法示例
Java代碼
public native void callJavaHelloWorld(); public native void callJavaHelloWorld2(); public native void callJavaHelloWorld3();
public void
helloWorld(){
Log.i("hello","helloworld");
}
public void
helloWorld2(String msg){
Log.i("hello","helloworld
"+msg);
}
public void
helloWorld3(inta,int
b){
int
c=a+b;
Log.i("hello","helloworld
c="+c);
}
C++代碼
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorld_methodID=env->GetMethodID(clazz,"helloWorld","()V"); if(helloWorld_methodID==NULL) return; env->CallVoidMethod(thiz,helloWorld_methodID); } extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld2(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorld2_methodID=env->GetMethodID(clazz,"helloWorld2","(java/lang/String;)V"); if(helloWorld2_methodID==NULL) return; const char *msg="hello world"; jstring jmsg=env->NewStringUTF(msg); env->CallVoidMethod(thiz,helloWorld2_methodID,jmsg); } extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callJavaHelloWorld3(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorld3_methodID=env->GetMethodID(clazz,"helloWorld3","(II)V"); if(helloWorld3_methodID==NULL) return; env->CallVoidMethod(clazz,helloWorld3_methodID,2,3); }
C++調用Java靜態方法示例
Java代碼
public native void callStaticJavaHelloWorld(); public native void callStaticJavaHelloWorld2(); public native void callStaticJavaHelloWorld3();
public static void
helloWorldStatic(){
Log.i("hello","helloworld
static");
}
public static void
helloWorldStatic2(String msg){
Log.i("hello","helloworld
static "+msg);
}
public static void
helloWorldStatic3(inta,int
b){
int
c=a+b;
Log.i("hello","helloworld
static c="+c);
}
C++代碼
extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorldStatic_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic","()V"); if(helloWorldStatic_methodID==NULL) return; env->CallStaticVoidMethod(clazz,helloWorldStatic_methodID); } extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld2(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorldStatic2_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic2","(java/lang/String;)V"); if(helloWorldStatic2_methodID==NULL) return; const char *msg="hello world"; jstring jmsg=env->NewStringUTF(msg); env->CallStaticVoidMethod(clazz,helloWorldStatic2_methodID,msg); } extern "C" JNIEXPORT void JNICALL Java_com_kgdwbb_jnistudy_MainActivity_callStaticJavaHelloWorld3(JNIEnv* env, jobject thiz) { jclass clazz=env->GetObjectClass(thiz); if(clazz==NULL) return; jmethodID helloWorldStatic3_methodID=env->GetStaticMethodID(clazz,"helloWorldStatic3","(II)V"); if(helloWorldStatic3_methodID==NULL) return; env->CallStaticVoidMethod(clazz,helloWorldStatic3_methodID,2,3); }
本篇結束語
到這裏,相信大家對JNI基礎知識都會有一個清晰的認識,現在大家就可以運用本篇學到的知識進行JNI開發了,你們都是好樣的。
JNI開發真的很簡單,大家只要多加練習,肯定能快速掌握,靈活運用本篇學到的JNI基礎知識。
加油,看好你們。