Android JNI基礎篇(一)

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,語法就是${依賴的庫的名字}

 JavaJNI類型對照表

這裏詳細介紹一下Java類型和C/C++類型的對照關係,方便我們下面的學習,這一部分知識很基礎,也很重要。

JavaJNI基本類型對照表

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位雙精度浮點值

 

JavaJNI引用類型對照表

與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基礎知識。

加油,看好你們。

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