JNI

JNI编程

JNI是一种本地编程接口。它允许运行在JAVA虚拟机中的JAVA代码和用其他编程语言,诸如C语言、C++、汇编,写的应用和库之间的交互操作。

第一个JNI程序

#引入jni头文件支持include_directories("C:/Program Files/Java/jdk1.8.0_171/include")# windows:win32  Mac:darwininclude_directories("C:/Program Files/Java/jdk1.8.0_171/include/win32")# 修改生成动态库add_library(lsn7jni SHARED lsn7_jni.cpp)

最终你的动态库会生成在下面位置( 可以修改)

dll目录.png

然后打开一个Java IDE (AS/ECLIPSE/文本文档 都行)

例子:在AS的java单元测试下进行编写(此时使用的是电脑的java环境)

@Testpublicvoidaddition_isCorrect(){        assertEquals(4,2+2);//加载指定动态库 注意:当前我的电脑系统是64位System.load("xxxx/lsn7jni.dll");//调用jni方法System.out.println(test(1,"22",3.1f));}nativeStringtest(inti,String j,floatk);

然后在VS中编写

// lsn6_jni.cpp: 定义应用程序的入口点。//#include"jni.h"//c++中需要以c的方式编译extern"C"//JNIEnv: 由Jvm传入与线程相关的变量。定义了JNI系统操作、java交互等方法。//jobject: 表示当前调用对象,即 this , 如果是静态的native方法,则获得jclassJNIEXPORT jstring JNICALLJava_com_dongnao_jniTest_ExampleUnitTest_test(JNIEnv *env, jobject, jint i, jstring j, jfloat k){// 获得c字符串  // 开闭内存x,拷贝java字符串到x中 返回指向x的指针// 参数2 isCopy://提供一个boolean(int)指针,用于接收jvm传给我们的字符串是否是拷贝的。//通常,我们不关心这个,一般传个NULL就可以constchar* str = env->GetStringUTFChars(j, JNI_FALSE);charreturnStr[100];//格式化字符串sprintf(returnStr,"C++ string:%d,%s,%f",i,str,k);//释放掉内存 xenv->ReleaseStringUTFChars(j,str);// 返回java字符串returnenv->NewStringUTF(returnStr);}

现在第一个jni程序就已经能够运行了,在AS中运行单元测试进行jni方法的测试

对于不熟悉的同学可以,使用javah获得方法该如何声明。javah是JDK中提供的工具(Java环境变量不用说了吧?)

javah -o [输出文件名]  [全限定名]

D:\Lance\ndk\lsn7_jni\JniTest\app\src\test\java> javah -o ExampleUnitTest.h com.dongnao.jniTest.ExampleUnitTest

JNI数据类型

JNIEXPORT 和 JNICALL,定义在jni_md.h头文件中。

JNIEXPORT:

​  Windows 中,定义为__declspec(dllexport)。因为Windows编译 dll 动态库规定,如果动态库中的函数要被外部调用,需要在函数声明中添加此标识,表示将该函数导出在外部可以调用。

​ 在 Linux/Unix/Mac os/Android 这种 Like Unix系统中,定义为__attribute__ ((visibility ("default")))

​ GCC 有个visibility属性,  启用这个属性gcc -fvisibility=xx:

当-fvisibility=hidden时

动态库中的函数默认是被隐藏的即 hidden. 除非显示声明为__attribute__((visibility("default"))).

当-fvisibility=default时

动态库中的函数默认是可见的.除非显示声明为__attribute__((visibility("hidden"))).

JNICALL:

​ 在类Unix中无定义,在Windows中定义为:_stdcall,一种函数调用约定

类Unix系统中这两个宏可以省略不加。

Java类型本地类型描述

booleanjbooleanC/C++8位整型

bytejbyteC/C++带符号的8位整型

charjcharC/C++无符号的16位整型

shortjshortC/C++带符号的16位整型

intjintC/C++带符号的32位整型

longjlongC/C++带符号的64位整型

floatjfloatC/C++32位浮点型

doublejdoubleC/C++64位浮点型

Objectjobject任何Java对象,或者没有对应java类型的对象

ClassjclassClass对象

Stringjstring字符串对象

Object[]jobjectArray任何对象的数组

boolean[]jbooleanArray布尔型数组

byte[]jbyteArray比特型数组

char[]jcharArray字符型数组

short[]jshortArray短整型数组

int[]jintArray整型数组

long[]jlongArray长整型数组

float[]jfloatArray浮点型数组

double[]jdoubleArray双浮点型数组

C/C++中获取java的数组时:extern"C"JNIEXPORT jstring JNICALLJava_com_dongnao_jnitest_MainActivity_test(JNIEnv *env, jobject instance, jobjectArray a_,jintArray b_){//1、 获得字符串数组//获得数组长度int32_tstr_length = env->GetArrayLength(a_);    LOGE("字符串 数组长度:%d",str_length);//获得字符串数组的数据for(inti =0; i < str_length; ++i) {        jstring str =static_cast(env->GetObjectArrayElement(a_, i));constchar* c_str =  env->GetStringUTFChars(str,0);        LOGE("字符串有:%s",c_str);//使用完释放env->ReleaseStringUTFChars(str,c_str);    }//2、获得基本数据类型数组int32_tint_length = env->GetArrayLength(b_);    LOGE("int 数组长度:%d",int_length);//对应的有 GetBoolean 、GetFloat等jint *b = env->GetIntArrayElements(b_,0);for(inti =0; i < int_length; i++) {        LOGE("int 数据有:%d",b[i]);    }    env->ReleaseIntArrayElements(b_, b,0);returnenv->NewStringUTF("222");}

C/C++反射Java

反射方法

在C/C++中反射创建Java的对象,调用Java的方法

packagecom.dongnao.jnitest;importandroid.util.Log;publicclassHelper{privatestaticfinalString TAG ="Helper";//private和public 对jni开发来说没任何区别 都能反射调用publicvoidinstanceMethod(String a,intb,booleanc){        Log.e(TAG,"instanceMethod a="+a +" b="+b+" c="+c );    }publicstaticvoidstaticMethod(String a,intb,booleanc){        Log.e(TAG,"staticMethod a="+a +" b="+b+" c="+c);    }}extern"C"JNIEXPORTvoidJNICALLJava_com_dongnao_jnitest_MainActivity_invokeHelper(JNIEnv *env, jobject instance){    jclass clazz = env->FindClass("com/dongnao/jnitest/Helper");//获得具体的静态方法 参数3:签名(下方说明)//如果不会填 可以使用javapjmethodID staticMethod = env->GetStaticMethodID(clazz,"staticMethod","(Ljava/lang/String;IZ)V");//调用静态方法jstring staticStr= env->NewStringUTF("C++调用静态方法");    env->CallStaticVoidMethod(clazz,staticMethod,staticStr,1,1);//获得构造方法 <init>:构造方法写法jmethodID constructMethod = env->GetMethodID(clazz,"<init>","()V");//创建对象jobject  helper = env->NewObject(clazz,constructMethod);    jmethodID instanceMethod = env->GetMethodID(clazz,"instanceMethod","(Ljava/lang/String;IZ)V");    jstring instanceStr= env->NewStringUTF("C++调用实例方法");    env->CallVoidMethod(helper,instanceMethod,instanceStr,2,0);//释放env->DeleteLocalRef(clazz);    env->DeleteLocalRef(staticStr);    env->DeleteLocalRef(instanceStr);    env->DeleteLocalRef(helper);}

基本数据类型的签名采用一系列大写字母来表示, 如下表所示:

Java类型签名

booleanZ

shortS

floatF

byteB

intI

doubleD

charC

longJ

voidV

引用类型L + 全限定名 + ;

数组[+类型签名

可以使用javap来获取反射方法时的签名

#cd 进入 class所在的目录 执行: javap -s 全限定名,查看输出的 descriptorD:\Lance\ndk\lsn7_jni\JniTest\app\build\intermediates\classes\debug>javap -s com.dongnao.jnitest.HelperCompiled from"Helper.java"publicclasscom.dongnao.jnitest.Helper{publiccom.dongnao.jnitest.Helper();    descriptor: ()VpublicvoidinstanceMethod(java.lang.String,int, boolean);    descriptor: (Ljava/lang/String;IZ)VpublicstaticvoidstaticMethod(java.lang.String,int, boolean);    descriptor: (Ljava/lang/String;IZ)V}

反射属性

inta =10;staticString b ="java字符串";privatestaticfinalString TAG ="Helper";publicvoidtestReflect(){        Log.e(TAG,"修改前 : a = "+a +" b="+b);        reflectHelper();        Log.e(TAG,"修改后 : a = "+a +" b="+b);}publicnativevoidreflectHelper();extern"C"JNIEXPORTvoidJNICALLJava_com_dongnao_jnitest_Helper_reflectHelper(JNIEnv *env, jobject instance){//instance 就是 helperjclass clazz = env->GetObjectClass(instance);//获得int a的标示jfieldID a = env->GetFieldID(clazz,"a","I");intavalue = env->GetIntField(instance,a);    LOGE("获得java属性a:%d",avalue);//修改属性值env->SetIntField(instance,a,100);    jfieldID b = env->GetStaticFieldID(clazz,"b","Ljava.lang.String;");//获取值jstring bstr = static_cast(env->GetStaticObjectField(clazz, b));constchar* bc_str = env->GetStringUTFChars(bstr,0);    LOGE("获得java属性b:%s",bc_str);//修改jstring new_str = env->NewStringUTF("C++字符串");    env->SetStaticObjectField(clazz,b,new_str);    env->ReleaseStringUTFChars(bstr,bc_str);    env->DeleteLocalRef(new_str);    env->DeleteLocalRef(clazz);}

JNI引用

在 JNI 规范中定义了三种引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。

局部引用

大多数JNI函数会创建局部引用。NewObject/FindClass/NewStringUTF 等等都是局部引用。

局部引用只有在创建它的本地方法返回前有效,本地方法返回后,局部引用会被自动释放。

因此无法跨线程、跨方法使用。

extern"C"JNIEXPORT jstring JNICALLxxx(JNIEnv *env, jobject instance){//错误//不能在本地方法中把局部引用存储在静态变量中缓存起来供下一次调用时使用。// 第二次执行 str依然有值,但是其引用的 “C++字符串” 已经被释放staticjstring str;if(str ==NULL){        str = env->NewStringUTF("C++字符串");    }returnstr;}

释放一个局部引用有两种方式:

1、本地方法执行完毕后VM自动释放;

2、通过DeleteLocalRef手动释放;

VM会自动释放局部引用,为什么还需要手动释放呢?

因为局部引用会阻止它所引用的对象被GC回收。

为什么需要手动释放局部引用.png

全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效 。

由 NewGlobalRef  函数创建

extern"C"JNIEXPORT jstring JNICALLJava_com_dongnao_jnitest_MainActivity_test1(JNIEnv *env, jobject instance){//正确staticjstring globalStr;if(globalStr ==NULL){        jstring str = env->NewStringUTF("C++字符串");//删除全局引用调用  DeleteGlobalRefglobalStr =static_cast(env->NewGlobalRef(str));//可以释放,因为有了一个全局引用使用str,局部str也不会使用了env->DeleteLocalRef(str);    }returnglobalStr;}

弱引用

与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象 。

在对Class进行弱引用是非常合适(FindClass),因为Class一般直到程序进程结束才会卸载。

在使用弱引用时,必须先检查缓存过的弱引用是指向活动的对象,还是指向一个已经被GC的对象

extern"C"JNIEXPORT jclass JNICALLJava_com_dongnao_jnitest_MainActivity_test1(JNIEnv *env, jobject instance){staticjclass globalClazz =NULL;//对于弱引用 如果引用的对象被回收返回 true,否则为false//对于局部和全局引用则判断是否引用java的null对象jboolean isEqual = env->IsSameObject(globalClazz,NULL);if(globalClazz ==NULL|| isEqual) {        jclass clazz = env->GetObjectClass(instance);//删除使用 DeleteWeakGlobalRefglobalClazz =static_cast(env->NewWeakGlobalRef(clazz));        env->DeleteLocalRef(clazz);    }returnglobalClazz;}

JNI_OnLoad

调用System.loadLibrary()函数时, 内部就会去查找so中的 JNI_OnLoad 函数,如果存在此函数则调用。

JNI_OnLoad会:

告诉 VM 此 native 组件使用的 JNI 版本。

​ 对应了Java版本,android中只支持JNI_VERSION_1_2 、JNI_VERSION_1_4、JNI_VERSION_1_6

​ 在JDK1.8有 JNI_VERSION_1_8。

jintJNI_OnLoad(JavaVM* vm,void* reserved){// 2、4、6都可以returnJNI_VERSION_1_4;}

动态注册

在此之前我们一直在jni中使用的 Java_PACKAGENAME_CLASSNAME_METHODNAME 来进行与java方法的匹配,这种方式我们称之为静态注册。

而动态注册则意味着方法名可以不用这么长了,在android aosp源码中就大量的使用了动态注册的形式

//Java:nativevoiddynamicNative();nativeStringdynamicNative(inti);//C++:voiddynamicNative1(JNIEnv *env, jobject jobj){    LOGE("dynamicNative1 动态注册");}jstringdynamicNative2(JNIEnv *env, jobject jobj,jint i){returnenv->NewStringUTF("我是动态注册的dynamicNative2方法");}//需要动态注册的方法数组staticconstJNINativeMethod mMethods[] = {        {"dynamicNative","()V", (void*)dynamicNative1},        {"dynamicNative","(I)Ljava/lang/String;", (jstring *)dynamicNative2}};//需要动态注册native方法的类名staticconstchar* mClassName ="com/dongnao/jnitest/MainActivity";jintJNI_OnLoad(JavaVM* vm,void* reserved){    JNIEnv* env = NULL;//获得 JniEnvintr = vm->GetEnv((void**) &env, JNI_VERSION_1_4);if( r != JNI_OK){return-1;    }    jclass mainActivityCls = env->FindClass( mClassName);// 注册 如果小于0则注册失败r = env->RegisterNatives(mainActivityCls,mMethods,2);if(r  != JNI_OK )    {return-1;    }returnJNI_VERSION_1_4;}

native线程调用Java

native调用java需要使用JNIEnv这个结构体,而JNIEnv是由Jvm传入与线程相关的变量。

但是可以通过JavaVM的AttachCurrentThread方法来获取到当前线程中的JNIEnv指针。

JavaVM* _vm =0;jobject  _instance =0;jint JNI_OnLoad(JavaVM* vm, void* reserved){    _vm = vm;returnJNI_VERSION_1_4;}void *task(void *args){    JNIEnv *env;//将本地当前线程附加到jvm,并获得jnienv//成功则返回0_vm->AttachCurrentThread(&env,0);        jclass clazz = env->GetObjectClass(_instance);//获得具体的静态方法 参数3:方法签名//如果不会填 可以使用javapjmethodID staticMethod = env->GetStaticMethodID(clazz,"staticMethod","(Ljava/lang/String;IZ)V");//调用静态方法jstring staticStr= env->NewStringUTF("C++调用静态方法");    env->CallStaticVoidMethod(clazz,staticMethod,staticStr,1,1);//获得构造方法jmethodID constructMethod = env->GetMethodID(clazz,"<init>","()V");//创建对象jobject  helper = env->NewObject(clazz,constructMethod);    jmethodID instanceMethod = env->GetMethodID(clazz,"instanceMethod","(Ljava/lang/String;IZ)V");    jstring instanceStr= env->NewStringUTF("C++调用实例方法");    env->CallVoidMethod(helper,instanceMethod,instanceStr,2,0);//释放env->DeleteLocalRef(clazz);    env->DeleteLocalRef(staticStr);    env->DeleteLocalRef(instanceStr);    env->DeleteLocalRef(helper);//分离_vm->DetachCurrentThread();return0;}//Helper 类方法extern"C"JNIEXPORT void JNICALLJava_com_dongnao_jnitest_Helper_nativeThread(JNIEnv *env, jobject instance) {    pthread_t  pid;//这里注意释放_instance = env->NewGlobalRef(instance);  pthread_create(&pid,0,t

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容