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方法被调用!