Android JNI的使用方法


前言

JNI 是Java Native Interface的缩写,表示Java本地调用,通过JNI技术可以实现Java调用C程序和C程序调用Java代码。


一、在Java中调用C库函数案例

1.静态注册:

	//Java 层代码JniSdk.java
    public class NativeDemo {
        static {
            System.loadLibrary("jnisdk");
        }
        public native String showJniMessage();
    }

    //Native层代码 jnidemo.c
    JNIEXPORT jstring JNICALL Java_com_dong_example_NativeDemo_showJniMessage  // Java+包名+类名+方法名
      (JNIEnv* env, jobject job) {
        return (*env)->NewStringUTF(env, "hello world");
    }
JNIEXPORT :在Jni编程中所有本地语言实现Jni接口的方法前面都有一个"JNIEXPORT",这个可以看做是Jni的一个标志,至今为止没发现它有什么特殊的用处。

jstring :这个学过编程的人都知道,当然是方法的返回值了,对应java的String类型,无返回值就是void

JNICALL :这个可以理解为Jni 和Call两个部分,和起来的意思就是 Jni调用XXX(后面的XXX就是JAVA的方法名)。

Java_NativeDemo_sayHello:这个就是被上一步中被调用的部分,也就是Java中的native 方法名:包名+类名+方法名。

JNIEnv * env:这个env可以看做是Jni接口本身的一个对象,jni.h头文件中存在着大量被封装好的函数,这些函数也是Jni编程中经常被使用到的,要想调用这些函数就需要使用JNIEnv这个对象,例如:(*env)->GetObjectClass()。

jobject obj:代表着native方法的调用者,本例即new NativeDemo();但如果native是静态的,那就是NativeDemo类。
  • 静态注册的方式我们平时用的比较多。我们通过javac和javah编译出头文件,然后再实现对应的c文件的方式就是属于静态注册的方式。这种调用的方式是由于JVM按照默认的映射规则来匹配对应的native函数,如果没匹配,则会报错。

  • 优点: 简单明了

  • 缺点

  • 1、书写很不⽅便,因为JNI层函数的名字必须遵循特定的格式,且名字特别⻓

  • 2、程序运⾏效率低,因为初次调⽤native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建⽴对应关系,这个过程⽐较耗时。

  • 3、运⾏效率低,这个才是为啥不推荐使⽤静态注册的原因,因为使⽤JNI的绝⼤部分情况是为了提供效率。

2.动态注册:

 	//Java 层代码JniSdk.java
    public class JniSdk {
        static {
            System.loadLibrary("jnisdk");
        }
        public static native int numAdd(int a, int b);
        public native void dumpMessage();
    }

    //Native层代码 jnidemo.c
    JNINativeMethod g_methods[] = {
            {"numAdd", "(II)I", (void*)add},
            {"dumpMessage","()V",(void*)dump},
    };

    jint JNI_OnLoad(JavaVM *vm, void *reserved) {
        j_vm = vm;
        JNIEnv *env = NULL;
        if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            return -1;
        }
        jclass clazz = (*env)->FindClass(env, "com/dong/example/JnidemoClass");
        //第一个参数clazz:是对应的类名的完整路径,(把.换成/) 
        //第一个参数g_methods:是定义的全局变量     
        //第一个参数2:是g_methods的数组长度,也可以用sizeof(g_methods)/sizeof(g_methods[0])
        jint ret = (*env)->RegisterNatives(env, clazz, g_methods, 2);
        if (ret != 0) {
            LOGI("register native methods failed");
        }
        return JNI_VERSION_1_4;
    }
  • 上面的JNI_OnLoad函数是在我们通过System.loadlibrary函数的时候,JVM会回调的一个函数,我们就是在这里做的动态注册的事情,通过(*env)->RegisterNatives注册。

二、在C代码中运行Java类案例

  // java层代码
  public class JniSdk {
        private int mIntArg = 5;
        public int getArg() {
            return mIntArg;
        }
    }
    
	// native 层代码,c代码
    void dump(JNIEnv *env, jobject obj) {
        LOGI("this is dump message call: %p", obj);
        jclass jc = (*env)->GetObjectClass(env, obj);
        jmethodID  jmethodID1 = (*env)->GetMethodID(env, jc,"getArg","()I");  // 对应java类中的方法
        jfieldID  jfieldID1 = (*env)->GetFieldID(env, jc,"mIntArg","I"); // 对应java类中的属性
        jint  arg1 = (*env)->GetIntField(env, obj,jfieldID1);
        jint arg = (*env)->CallIntMethod(env, obj, jmethodID1);
        LOGI("show int filed: %d, %d",arg, arg1);
    }
    /* 
    1、c代码中创建JNIEnv *env
    2、一个java线程拥有一个JNIEnv,c代码调用java方法时,通常情况和java调用c代码时不在同一个
    线程,所以在c线程中创建新的JNIEnv *env
    */
    JNIEnv *g_env;
	void *func1(void* arg) {
	    //进入另一个新线程
	    //使用全局保存的g_env,进行操作java对象的时候程序会崩溃
	    // jmethodID  jmethodID1 = g_env->GetMethodID(jc,"getArg","()I");
	    // jint arg = g_env->CallIntMethod(obj, jmethodID1);
	
	    //通过这种方法获取的env,然后再进行获取方法进行操作不会崩溃
	    JNIEnv *env;
	    (*j_vm)->AttachCurrentThread(jvm,&env,NULL);
	}
	// java调用c时启用
	void dumpArg(JNIEnv *env, jobject call_obj, jobject arg_obj) {
	    //打印线程
	    LOGI("on dump arg function, env :%p", env);
	    g_env = env;
	    pthread_t *thread;
	    pthread_create(thread,NULL, func1, NULL);
	    (*j_vm)->DetachCurrentThread(j_vm);
	}

三、c库和c++库使用JNI区别

c库和c++库还是有些区别的,下面要注意的几点,不然编译不通过

  • 1、头部引用
C语言 #include "jni.h"
C++   #include <jni.h>
  • 2、c++需要添加extern “C”
// c++代码需要按照c的规则编译,添加extern "C"
extern "C" JNIEXPORT jstring JNICALL Java_com_dong_example_NativeDemo_showJniMessage
  • 3、JNIEnv指针引用和FindClass函数参数有区别
//C语言,FindClass,反射,通过类的名字反射
jclass mainActivityCls = (*env)->FindClass(env, "com/bob/nativelib/JNITools");
    
//C++语言,FindClass,反射,通过类的名字反射
jclass mainActivityCls = env->FindClass("com/bob/nativelib/JNITools2");
  • 4、jobject和jclass区别
 	// jobject:实例引⽤(C++的说法:对象引⽤),java中native前面没有static
 	//jclass:   类引⽤ (static native), java中native前面有static
	//Java 层代码JniSdk.java
    public static class NativeDemo {
        static {
            System.loadLibrary("jnisdk");
        }
        public native String showJniMessage();
    }

    //Native层代码 jnidemo.c
    JNIEXPORT jstring JNICALL Java_com_dong_example_NativeDemo_showJniMessage
      (JNIEnv* env, jclass jcl) {
        return (*env)->NewStringUTF(env, "hello world");
    }

四、JavaVM 和 Env 的关系

  1. JavaVm是虚拟机在jni层的代表,⼀个进程只有⼀个JavaVm,所有线程共⽤⼀个JavaVM。
  2. JNIEnv 是⼀个线程相关的结构体,它代表了java的运⾏环境 。每⼀个线程都会有⼀个,不同的线程中 不能相互调⽤,每个JNIEnv都是线程专有的。 jni中可以拥有很多个JNIEnv,可以使⽤它来进⾏java层 和native层的调⽤。
  3. JNIEnv 是⼀个指针,指向⼀个线程相关的结构,线程相关结构指向了JNI函数指针数组。这个数组⾥⾯ 定义了⼤量的JNI函数指针。
  4. 在同⼀个线程中,多次调⽤JNI层⽅法,传⼊的JNIEnv都是相同的。
  5. 在java层定义的本地⽅法,可以在不同的线程中调⽤,因此是可以接受不同的JNIEnv。

五、JNIEnv操作Java端的代码,主要方法:

函数名称作用
NewObject创建Java类中的对象
NewString创建Java类中的String对象
NewArray创建类型为Type的数组对象
GetField获得类型为Type的static的字段
SetField创建Java类中的对象
GetStaticField创建Java类中的对象
SetStaticField设置类型为Type的static的字段
CallMethod调用返回值类型为Type的static方法
CallStaticMethod调用返回值类型为Type的static方法

六、JNI数据类型

1、基本数据类型

Java Launage TypeJNI Native TypeC/C++ LaunageTypeType Description
booleanjbooleanunsigned charunsigned 8 bits
bytejbytesigned char signed8 bits
charjcharunsigned short unsigned16 bits
shortjshortsigned short signed16 bits
intjintsigned int signed32 bits
longjlongsigned long signed64 bits
floatjfloatfloat32 bits
doublejdoubledouble64 bits

2、引用数据类型

Java Launage TypeJNI Native TypeType Description
java.lang.Objectjobject可以表示任何Java的对象,或者没有JNI对应类型的Java对象(实例⽅法的强制参数)
java.lang.StringjstringJava的String字符串类型的对象
java.lang.ClassjclassJava的Class类型对象(静态⽅法的强制参数)
Object[]jobjectArrayJava任何对象的数组
boolean[]jbooleanArrayJava boolean型数组
byte[]jbyteArrayJava byte型数组
char[]jcharArrayJava char型数组
short[]jshortArrayJava short型数组
int[]jintArrayJava int型数组
long[]jlongArrayJava long型数组
float[]jfloatArrayJava float型数组
double[]jdoubleArrayJava double型数组
java.lang.ThrowablejthrowableJava的Throwable类型,表示异常的所有类型和⼦类
voidvoidN/A

七、域描述符、方法描述符

1、 基本类型域描述符

Java Launage TypeField Description备注
intIint的⾸字⺟、⼤写
longLlong的⾸字⺟、⼤写
byteBbyte的⾸字⺟、⼤写
shortSshort的⾸字⺟、⼤写
charCchar的⾸字⺟、⼤写
doubleDdouble的⾸字⺟、⼤写
booleanZ因B已被byte使⽤,所以JNI规定使⽤Z
voidV⽆返回类型
objectL+类全名+;String 如: Ljava/lang/String
array[+类型描述符int[] 如:[I
Method(参数)返回值int add(int a, int b) 如:(II)I
java类中类使用$Landroid/media/MediaCodec$CryptoInfo

2、⽅法描述符

Java MethodMethod Description
String fun()()Ljava/lang/String;
int fun(int i, Object object)(ILjava/lang/Object;)I
void fun(byte[ ] bytes)([B)V
int fun(byte data1, bytedata2)(BB)I
void fun()()V

总结

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值