1.C++调用Java
-
从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象。
-
获取类的默认构造方法ID。
-
查找实例方法的ID。
-
创建该类的实例。
-
调用对象的实例方法。
-
1).找到类
2).找到实例
3).调用方法和属性
/**
* 调用java的方法
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_xfhy_ndkdemo_CallJava_callVoidMethod(JNIEnv *env, jobject instance) {
//通过反射调用java中的方法
//找class 使用FindClass方法,参数就是要调用的函数的类的完全限定名,但是需要把点换成/
jclass clazz = env->FindClass("com/xfhy/ndkdemo/CallJava");
//获取对应的函数: 参数1:类class,参数2:方法名,参数3:方法签名
//ps:方法签名的获取:进入build->intermediates->classes->debug目录下,使用javap -s 类的完全限定名,就能获得函数签名
jmethodID method = env->GetMethodID(clazz, "hello", "()V");
//实例化该class对应的实例 使用AllocObject方法,使用clazz创建该class的实例。
jobject object = env->AllocObject(clazz);
//调用方法
env->CallVoidMethod(object, method);
}
——————————————
2.Java调用C函数
Java调用C++
- 在Java中声明Native方法(即需要调用的本地方法)
- 编译上述 Java源文件javac(得到 .class文件) 3。 通过 javah 命令导出JNI的头文件(.h文件)
- 使用 Java需要交互的本地代码 实现在 Java中声明的Native方法
- 编译.so库文件
- 通过Java命令执行 Java程序,最终实现Java调用本地代码
- CMake:一个跨平台的编译构建工具,替代 Android.mk
- LLDB:一个高效的 C/C++ 的调试工具
- NDK:即我们需要下载的工具,会生成到 SDK 根目录下的 ndk-bundle 目录下
3.JNIEnv分析我们知道 ,JNIEnv是JNINativeInterface_
结构体的指针别名 , 在JNINativeInterface_
结构体中 , 定义很多操作函数 。例如:<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c">jstring (JNICALL <span style="color:#67cdcc">*</span>NewStringUTF) (JNIEnv <span style="color:#67cdcc">*</span>env, <span style="color:#cc99cd">const</span> <span style="color:#cc99cd">char</span> <span style="color:#67cdcc">*</span>utf); jsize (JNICALL <span style="color:#67cdcc">*</span>GetStringUTFLength) (JNIEnv <span style="color:#67cdcc">*</span>env, jstring str); <span style="color:#cc99cd">const</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> (JNICALL <span style="color:#67cdcc">*</span>GetStringUTFChars)(JNIEnv <span style="color:#67cdcc">*</span>env, jstring str, jboolean <span style="color:#67cdcc">*</span>isCopy); <span style="color:#cc99cd">void</span> (JNICALL <span style="color:#67cdcc">*</span>ReleaseStringUTFChars)(JNIEnv <span style="color:#67cdcc">*</span>env, jstring str, <span style="color:#cc99cd">const</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> chars);</code></span></span></span>
由上述函数可以看出,每个函数都需要一个JNIEnv指针,但是为什么需要呢 ?
有两点:
第一:函数需要 , 在函数中仍然需要JNINativeInterface_结构体中的函数做处理第二:区别对待C和C++
我们知道 , jni是支
由上可知 , 在C和C++两个环境中 , 使用了两个不同的JNIEnv , 一个是JNIEnv二级指针 , 一个是JNIEnv一级指针 。基本用法:<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"> <span style="color:#999999">// 得到jclass</span> jclass jcls <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectClass</span>(env, jobj); </code></span></span></span>
<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"><span style="color:#999999">// com.zeno.jni_HelloJNI.h</span> JNIEXPORT <span style="color:#cc99cd">void</span> JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField (JNIEnv <span style="color:#67cdcc">*</span>, jobject); <span style="color:#999999">// Hello_JNI.c</span> <span style="color:#999999">/*C语言访问java String类型字段*/</span> JNIEXPORT <span style="color:#cc99cd">void</span> JNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField (JNIEnv <span style="color:#67cdcc">*</span>env, jobject jobj) { <span style="color:#999999">// 得到jclass</span> jclass jcls <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectClass</span>(env, jobj); <span style="color:#999999">// 得到字段ID</span> jfieldID jfID <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetFieldID</span>(env, jcls, <span style="color:#7ec699">"name"</span>, <span style="color:#7ec699">"Ljava/lang/String;"</span>); <span style="color:#999999">// 得到字段的值</span> jstring jstr <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectField</span>(env, jobj, jfID); <span style="color:#999999">// 将jstring类型转换成字符指针</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> cstr <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetStringUTFChars</span>(env, jstr, JNI_FALSE); <span style="color:#999999">//printf("is vaule:%s\n", cstr);</span> <span style="color:#999999">// 拼接字符</span> <span style="color:#cc99cd">char</span> text[<span style="color:#f08d49">30</span>] <span style="color:#67cdcc">=</span> <span style="color:#7ec699">" xiaojiu and "</span>; <span style="color:#f08d49">strcat</span>(text, cstr); <span style="color:#999999">//printf("modify value %s\n", text);</span> <span style="color:#999999">// 将字符指针转换成jstring类型</span> jstring new_str <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">NewStringUTF</span>(env, text); <span style="color:#999999">// 将jstring类型的变量 , 设置到java 字段中</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">SetObjectField</span>(env, jobj, jfID, new_str); }</code></span></span></span>
JNIEnvJNIEnv 表示 Java 调用 native 语言的环境,是一个封装了几乎全部 JNI 方法的指针。JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的 JNIEnv 彼此独立。native 环境中创建的线程,如果需要访问 JNI,必须要调用 AttachCurrentThread 关联,并使用 DetachCurrentThread 解除链接。二、两种代码风格(C/C++)JavaVM 和 JNIEnv 在 C 语言环境下和 C++ 环境下调用是有区别的,主要表现在:C风格:(*env)->NewStringUTF(env, “Hellow World!”);C++风格:env->NewStringUTF(“Hellow World!”);建议使用 C++ 风格,这也是大部分代码使用的形式。注意:C++ 风格其实只是封装了 C 风格,使得调用更加简介方便。————————————————C 函数访问java字段:
为什么要得到jclass呢 ?
因为 ,我们要获取字段ID , 在JNI中 , 获取java字段与方法都需要签名。而签名是在类加载的时候完成 , 所以在获取字段ID的时候需要传入jclass 。:类似于反射一样,先得到jclass。然后得到字段。<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"> <span style="color:#999999">// 得到jclass</span> jclass jcls <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectClass</span>(env, jobj); <span style="color:#999999">// 得到字段ID</span> jfieldID jfID <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetFieldID</span>(env, jcls, <span style="color:#7ec699">"name"</span>, <span style="color:#7ec699">"Ljava/lang/String;"</span>); <span style="color:#999999">// 得到字段的值</span> jstring jstr <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetObjectField</span>(env, jobj, jfID);</code></span></span></span>
C 调用Java对象的构造方法
C调用Java的构造方法与调用普通方法略有不同 , 其不同之处在于方法名称上 , 普通方法直接使用方法名
, 构造方法则不是使用类名 , 而使用一个固定写法<init>
。<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-c"> <span style="color:#999999">// 得到构造方法id</span> jmethodID dateConstructMid <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetMethodID</span>(env, dateCls, <span style="color:#7ec699">"<init>"</span>, <span style="color:#7ec699">"()V"</span>);</code></span></span></span>
native方法为static
在java中声明的JNI的native方法静态方法和非静态,对于底层的C/C++代码来说是有区别的:
1,JNI函数的参数也由三部分组成:首先是JNIEnv*,是一个指向JNI运行环境的指针;2,第二个参数随本地方法是静态还是非静态而有所不同一一非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用;
3,其余的参数对应通常Java方法的参数,参数类型需要根据一定规则进行映射。
4.jni语法:基本数据类型
以下是java的基本数据类型和jni中的基本数据类型的比较,及各类型的字节。
- 传递字符串
①, Java字符串转为c字符串<span style="color:#000000"><span style="color:#404040"><span style="color:#cccccc"><code class="language-cpp"><span style="color:#f8c555">#<span style="color:#cc99cd">include</span> <span style="color:#7ec699"><jni.h></span></span> <span style="color:#999999">//因为下面用到的NULL在stdlib库中,所以导入该库</span> <span style="color:#f8c555">#<span style="color:#cc99cd">include</span> <span style="color:#7ec699"><stdlib.h></span> </span> <span style="color:#999999">/** * 把一个jstring转换成一个c语言的char* 类型. */</span> <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> <span style="color:#f08d49">_JString2CStr</span>(JNIEnv<span style="color:#67cdcc">*</span> env, jstring jstr) { <span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span> rtn <span style="color:#67cdcc">=</span> <span style="color:#f8c555">NULL</span>; jclass clsstring <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">FindClass</span>(env, <span style="color:#7ec699">"java/lang/String"</span>); jstring strencode <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">NewStringUTF</span>(env,<span style="color:#7ec699">"GB2312"</span>); jmethodID mid <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetMethodID</span>(env, clsstring, <span style="color:#7ec699">"getBytes"</span>, <span style="color:#7ec699">"(Ljava/lang/String;)[B"</span>); jbyteArray barr <span style="color:#67cdcc">=</span> (jbyteArray)(<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">CallObjectMethod</span>(env, jstr, mid, strencode); <span style="color:#999999">// String .getByte("GB2312");</span> jsize alen <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetArrayLength</span>(env, barr); jbyte<span style="color:#67cdcc">*</span> ba <span style="color:#67cdcc">=</span> (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">GetByteArrayElements</span>(env, barr, JNI_FALSE); <span style="color:#cc99cd">if</span>(alen <span style="color:#67cdcc">></span> <span style="color:#f08d49">0</span>) { rtn <span style="color:#67cdcc">=</span> (<span style="color:#cc99cd">char</span><span style="color:#67cdcc">*</span>)<span style="color:#f08d49">malloc</span>(alen<span style="color:#67cdcc">+</span><span style="color:#f08d49">1</span>); <span style="color:#999999">//"\0"</span> <span style="color:#f08d49">memcpy</span>(rtn, ba, alen); rtn[alen]<span style="color:#67cdcc">=</span><span style="color:#f08d49">0</span>; } (<span style="color:#67cdcc">*</span>env)<span style="color:#67cdcc">-></span><span style="color:#f08d49">ReleaseByteArrayElements</span>(env, barr, ba,<span style="color:#f08d49">0</span>); <span style="color:#cc99cd">return</span> rtn; } </code></span></span></span>
语法参考:
https://www.jianshu.com/p/1229580b2356其他:混淆的时候:
如何加载NDK 库 ?如何在JNI 中注册Native 函数,有几种注册方法 ?参考回答:public class JniTest{//加载NDK 库static{System.loadLirary("jni-test");}}注册JNI 函数的两种方法o 静态方法谈谈你对JNIEnv 和JavaVM 理解?1.JavaVmJavaVM 是虚拟机在JNI 层的代表,一个进程只有一个JavaVM,所有的线程共用一个JavaVM。2.JNIEnvJNIEnv 表示Java 调用native 语言的环境,是一个封装了几乎全部JNI 方法的指针。JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的JNIEnv 彼此独立。native 环境中创建的线程,如果需要访问JNI,必须要调用AttachCurrentThread关联,并使用DetachCurrentThread 解除链接。so 的加载流程是怎样的,生命周期是怎样的?
这个要从 java 层去看源码分析,是从 ClassLoader 的 PathList 中去找到目标路径加载的,同时 so 是通过 mmap 加载映射到虚拟空间的。生命周期加载库和卸载库时分别调用 JNI_OnLoad 和 JNI_OnUnload() 方法。