JNI原理框圖
Java是跑在虛擬機上的,是與平臺無關的,但是有時候不得不採用本地代碼來執行,像一些對運行效率比較高的功能,與底層相關的一些功能都需要採用本地代碼執行。
JNI基本類型
Java代碼中的數據可能需要傳遞到Jni層c/cpp中,那麼就需要進行數據類型的轉換JAVA類型 | Jni層的類型 | 各個平臺對jni類型的定義 | 佔用的字節數 |
boolean | jboolean | unsigned char | 1 |
byte | jbyte | char | 1 |
char | jchar | unsigned short | 2 |
short | jshort | short | 2 |
int | jint | int | 4 |
long | jlong | long long | 8 |
float | jfloat | float | 4 |
double | jdouble | double | 8 |
所有的引用類型 | jobject | void* | 4 |
數組類型都被定義爲jarray,而jarray又被定義爲jobject,也就是說所有數組類型都是void*
因此,我們可以把所有的數組類型的變量理解成一個對象,指針指向的內容不同,Java虛擬機就將其解釋爲不同的對象。
類型 | 定義 |
jarray | typedef jobject jarray; |
jobjectArray | typedef jarray jobjectArray; |
jbooleanArray | typedef jarray jbooleanArray; |
jbyteArray | typedef jarray jbyteArray; |
jcharArray | typedef jarray jcharArray; |
jshortArray | typedef jarray jshortArray; |
jintArray | typedef jarray jintArray; |
jlongArray | typedef jarray jlongArray; |
jfloatArray | typedef jarray jfloatArray; |
jdoubleArray | typedef jarray jdoubleArray; |
特殊類型 我們要操作某個類的屬性或方法,則首先要根據類名或某個類對象的實例來獲取這個類的ID,然後根據類的ID獲取該類的屬性ID或方法ID,擁有這些ID後我們便可以輕鬆的唯一標識jvm虛擬機中的某個類、某個對象、某個方法、某個屬性等信息。
類型 | 定義 | 說明 |
jclass | jobject | 標識Java中的某個類ID |
jobject | void*指針 | 標識一個類的對象實例 |
jfieldID | struct _jfieldID* | 標識Java類中的屬性ID |
jmethodID | struct _jmethodID* | 標識Java類中的方法ID |
在Java中,所有的引用類型都通過jobject類型表示。jobject表示一個對象的實例,通過這個實例,我們可以獲取這個實例所對應的類的ID,我們也可以通過”包名/類名”來獲取類的ID
jclass classID = env->GetObjectClass( jobject obj );//其中obj爲類的實例 jclass classID = env->FindClass( “包名/類名” );//需要將包名中的”.”替換爲“/” |
JNI類型簽名
因爲Java是支持方法重載的,那麼相同名字的方法名可能有多個,我們怎麼區分這些方法呢?我們可以根據方法的參數和返回值信息來區分這些具有相同名字的方法。爲了在Java中標識這些方法,可以jni類型簽名來標識。
Java類型 | 類型簽名 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
類的簽名方式 : L + 包名/類名 + ; | |
java.long.String | Ljava/lang/String; |
數組類型 :中括號+類型簽名 | |
int[ ] | [I |
方法(函數)簽名方式
格式:(參數類型簽名)返回值類型簽名
例如:void setValue(int x, long y); 簽名爲:(IJ)V
int getValue(String name,boolean isTrue); 簽名爲:(Ljava/lang/String;Z)I
JNI中操作Java類的屬性、方法
要操作Java中某個類的屬性、方法,需要先獲得這個類的ID,再根據屬性名、屬性簽名、方法名、方法簽名獲取對應的屬性ID和方法ID。注意區分這裏的靜態屬性、靜態方法、成員屬性、成員方法。
獲取類的成員屬性、成員方法 jfieldID fieldID = env->GetFieldID( classID, “屬性名”, “屬性簽名”); jmethodID methodID = env->GetMethodID( classID, “方法名”, “方法簽名”); 獲取類的靜態屬性、靜態方法 jfieldID fieldID = env->GetStaticFieldID( classID, “屬性名”, “屬性簽名”); jmethodID methodID = env->GetStaticMethodID( classID, “方法名”, “方法簽名”); |
有了這些屬性ID和方法ID之後,我們就可以操作某個類對象的屬性和方法了。
獲取、設置類的成員屬性 jint intValue = env->GetIntField( obj , fieldID );//obj爲對象實例,Int爲屬性的類型 env->SetIntField( obj , fieldID , 屬性值 );//屬性值爲要設置的值 獲取、設置類的靜態屬性 jint intValue = env->GetStaticIntField( classID , fieldID ); env->SetStaticIntField( classID , fieldID , 屬性值 );
env->CallVoidMethod( obj , methodID , … );//調用類方法,Void爲返回值類型,“…”爲參數 env->CallStaticVoidMethod( classID, methodID , … );//調用類的靜態方法,“…”爲參數 |
代碼實踐
package com.fox.test;
import android.util.Log;
/**
* Created by fox on 2018/5/24.
*/
//注意這裏的包名一定要與cpp代碼中的函數名對應起來,否則會鏈接不到jni層的函數
public class JNITest {
private int age;//類的成員變量
private static float price;//類的靜態變量
public native void setAge( int _age);// 類的成員函數
public native int getAge();// 類的成員函數
public native static void setPrice( float _price);// 類的靜態方法
public native static float getPrice();// 類的靜態方法
public native void testMethod();//用來測試在jni中調用print方法
public void print(){
Log.d("TTT","print方法被調用!");
}
public static void sprint(){
Log.d("TTT","sprint方法被調用!");
}
static {
System.loadLibrary("native-lib");
}
}
c/cpp實現代碼#include <jni.h>
extern "C" {
JNIEXPORT void JNICALL
Java_com_fox_test_JNITest_setAge(JNIEnv *env, jobject instance, jint _age) {
jclass JNITest_ID = env->GetObjectClass(instance);//根據實例對象獲取其類ID
jfieldID ageID = env->GetFieldID(JNITest_ID,"age","I");//根據類ID、屬性名,屬性的類型簽名,獲取屬性ID
env->SetIntField(instance,ageID,_age);//設置屬性的值
}
JNIEXPORT jint JNICALL
Java_com_fox_test_JNITest_getAge(JNIEnv *env, jobject instance) {
jclass JNITest_ID = env->GetObjectClass(instance);
jfieldID ageID = env->GetFieldID(JNITest_ID,"age","I");
jint age = env->GetIntField(instance,ageID);//獲取屬性的值
return age;
}
JNIEXPORT void JNICALL
Java_com_fox_test_JNITest_setPrice(JNIEnv *env, jclass type, jfloat _price) {
// jclass type = env->FindClass("com/fox/test/JNITest");//若是操作靜態方法、靜態屬性,我們可以直接根據包名獲取其類ID
jfieldID priceID = env->GetStaticFieldID(type,"price","F");
env->SetStaticFloatField(type,priceID,_price);//注意這裏的"Static"字段
}
JNIEXPORT jfloat JNICALL
Java_com_fox_test_JNITest_getPrice(JNIEnv *env, jclass type) {
// jclass type = env->FindClass("com/fox/test/JNITest");
jfieldID priceID = env->GetStaticFieldID(type,"price","F");
jfloat price = env->GetStaticFloatField(type,priceID);//注意這裏的"Static"字段
return price;
}
JNIEXPORT void JNICALL
Java_com_fox_test_JNITest_testMethod(JNIEnv *env, jobject instance) {
jclass JNITest_ID = env->GetObjectClass(instance);
//調用JNITest類的成員方法
jmethodID printID = env->GetMethodID(JNITest_ID,"print","()V");//根據類ID、方法名 、方法簽名,獲取方法ID
env->CallVoidMethod(instance,printID);//調用方法
//調用JNITest類的靜態方法
jmethodID sprintID = env->GetStaticMethodID(JNITest_ID,"sprint","()V");//注意這裏的“Static”
env->CallStaticVoidMethod(JNITest_ID,sprintID);//調用 靜態方法
}
}
測試代碼package com.fox.test;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JNITest jniTest = new JNITest();
//測試調用成員方法
jniTest.setAge(100);
int age = jniTest.getAge();
Log.d("TTT","age : "+ age);
//測試調用靜態方法
JNITest.setPrice(25.5f);
float price = JNITest.getPrice();
Log.d("TTT","price : "+ price);
//測試在jni中調用Java層代碼
jniTest.testMethod();
}
}
調試運行會在Log中打印如下信息05-24 17:36:02.172 24608-24608/com.fox.test D/TTT: age : 100
05-24 17:36:02.172 24608-24608/com.fox.test D/TTT: price : 25.5
05-24 17:36:02.173 24608-24608/com.fox.test D/TTT: print方法被調用!
05-24 17:36:02.173 24608-24608/com.fox.test D/TTT: sprint方法被調用!