相对于NDK来说SDK里面有更多API可以调用,有时候我们在做NDK开发的时候,需要在JNI直接Java中的方法和变量,比如callback,系统信息等....
如何在JNI中调用Java方法呢?就需要先了解FindClassGetMethodID了。

FindClass和GetMethodID

在JNI中可以通过FindClass可以找到Java类,得到jclass,例如:

jclass clz=(*env)->FindClass(env,"com/jjz/JniHandle");

FindClass的第二个参数需要传入类的完整包名。

使用GetMethodID可以获取类的方法,得到jmethodID,例如:

jmethodID getStringFromJava=(*env)->GetMethodID(env,class,"getStringForJava","()V");

如果调用的是静态方法需要使用GetStaticMethodID获取。通过FindeClass可以在JNI中找到需要调用的类,GetMethodID可以找到对应的方法,这样就可以在JNI中调用Java的方法了。
在GetMethodID中,第四个参数是()V,这个是方法签名。那么方法签名的规则又是怎么样呢?

方法签名

GetMethodID中第四个参数()V就是方法签名,Java是支持重载的,所以需要标明方法的传参和返回值,这就是方法的签名。它是用来保证方法的唯一性。其中()代表不传参数,V代表返回值为void
方法签名对于Java的类型都有一一对应的值。方法签名中用大写的字母对应了java的基本数据类型:

  • Z -> boolean
  • B -> byte
  • C -> char
  • S -> short
  • I -> int
  • J -> long
  • F -> float
  • D -> double

其实就是有两个比较特殊的:boolean对应的是Z,long对应的J,其他的都是首个字母的大写即可。

数组的表示方法,以[为标志,一个[标识一维数组,[[表示二维数组,例如:

  • byte[] -> [B
  • int[][] -> [[I

引用类型的表示方法,需要以L开头,以;结束,中间对应类型的包名加类名,例如:

  • String -> Ljava/lang/String;
  • Object -> Ljava/lang/Object;

自定义类的表示方法,比如包名为jjz.example,类名为JniHandle的表示方法:

  • jjz.example.JniHandle ->Ljjz/example/JniHandle;

除了手动输入类名和方法签名以外,JDK还提供了直接生成方法签名的工具javap
build之后可以在路径../app/build/intermediates/classes/debug下可以找到build之后生成的.class文件,运行命令:

javap -s com/jjz/JniHandle

就可以得到这个类的所有的方法签名:

Compiled from "JniHandle.java"
public class com.jjz.JniHandle {
public com.jjz.JniHandle();
descriptor: ()V public static java.lang.String getStringFromStatic();
descriptor: ()Ljava/lang/String; public java.lang.String getStringForJava();
descriptor: ()Ljava/lang/String;
}

有了类的引用,和方法的签名就可以直接在JNI中调用Java方法了,下面分别介绍下静态方法和类方法的调用。

静态方法的调用

调用类的静态方法,首先要得到类的引用,再调用类的静态方法。
先定义一个类和静态方法用来提供给JNI调用:

public class JniHandle {

    public static String getStringFromStatic() {
return "string from static method in java";
}
}

在定义了一个native方法com.jjz.NativeUtil.callJavaStaticMethodFromJni,生成这个方法的JNI代码,在JNI代码中调用JniHandle类的静态方法:

JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callJavaStaticMethodFromJni(JNIEnv *env, jclass type) { jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle");
if (NULL == jniHandle) {
LOGW("can't find JniHandle");
return;
}
jmethodID getStringFromStatic = (*env)->GetStaticMethodID(env, jniHandle, "getStringFromStatic",
"()Ljava/lang/String;");
if (NULL == getStringFromStatic) {
(*env)->DeleteLocalRef(env, jniHandle);
LOGW("can't find method getStringFromStatic from JniHandle ");
return;
}
jstring result = (*env)->CallStaticObjectMethod(env, jniHandle, getStringFromStatic);
const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);
(*env)->DeleteLocalRef(env, jniHandle);
(*env)->DeleteLocalRef(env, result);
LOGW(resultChar); }

在Java中调用com.jjz.NativeUtil.callJavaStaticMethodFromJni可以该方法可以在logcat中看到string from static method in java,这样就完成了在JNI中调用了Java静态方法。

类方法的调用

调用类方法要更加的复杂一些,调用步骤:

  1. 通过findClass找到类
  2. 通过GetMethodID得到构造函数
  3. 通过调用构造函数得到一个类的实例
  4. 通过GetMethodID得到需要调用的方法
  5. 使用类的实例调用方法

先定义一个类方法:

public class JniHandle {
public String getStringForJava() {
return "string from method in java";
}
}

再定义一个native方法:com.jjz.NativeUtil.callJavaMethodFromJni,生成该方法的JNI代码,在JMI代码中实现调用JniHandle的类方法getStringForJava,代码如下:


JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callJavaMethodFromJni(JNIEnv *env, jclass type) { jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle");
if (NULL == jniHandle) {
LOGW("can't find jniHandle");
return;
}
jmethodID constructor = (*env)->GetMethodID(env, jniHandle, "<init>", "()V");
if (NULL == constructor) {
LOGW("can't constructor JniHandle");
return;
}
jobject jniHandleObject = (*env)->NewObject(env, jniHandle, constructor);
if (NULL == jniHandleObject) {
LOGW("can't new JniHandle");
return;
}
jmethodID getStringForJava = (*env)->GetMethodID(env, jniHandle, "getStringForJava",
"()Ljava/lang/String;");
if (NULL == getStringForJava) {
LOGW("can't find method of getStringForJava");
(*env)->DeleteLocalRef(env, jniHandle);
(*env)->DeleteLocalRef(env, jniHandleObject);
return;
}
jstring result = (*env)->CallObjectMethod(env, jniHandleObject, getStringForJava);
const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);
(*env)->DeleteLocalRef(env, jniHandle);
(*env)->DeleteLocalRef(env, jniHandleObject);
(*env)->DeleteLocalRef(env, result);
LOGW(resultChar); }

调用方法com.jjz.NativeUtil.callJavaMethodFromJni即可看到Java中的字符串传递给了JNI最后输出到了Logcat上。

在上面的代码中有一个方法叫做DeleteLocalRef,它的意思是释放局部引用,Android VM释放局部引用有两种方法:

  • 本地方法执行完毕之后VM自动释放
  • 通过调用DeleteLocalRef手动释放

既然上面说了VM会自动释放引用为什么还需要手动释放呢?
其实某些局部变量会阻止它所引用的对象被GC回收,它所引用的对象无法被GC回收,自己本身也就无法被自动释放,因此需要使用DeleteLocalRef。而这里使用了JNI Local Reference,在JNI中引用了Java对象,如果不使用DeleteLocalRef释放的话,引用无法回收,就会造成内存泄露。

源代码地址:https://github.com/jjz/android/tree/master/experimental

Android Studio NDK开发-JNI调用Java方法的更多相关文章

  1. Android NDK开发 JNI操作java构造方法,普通方法,静态方法(七)

    Android NDK开发 JNI操作java普通.静态.构造方法 1.Jni实例化一个Java类的实例jobject 1.通过FindClas( ),获取Java类的的jclass 2.通过GetM ...

  2. 【Android】Android Studio NDK 开发

    Android Studio NDK 开发 记录在Android Studio中NDK简单开发的步骤 用到的Android Studio版本为3.5. 配置NDK 下载NDK 一般在SDK下已经有自带 ...

  3. java native interface JNI 调用Java方法

    在上一篇文章中介绍了JNI.以及java调用JNI.这篇讲一下 JNI调用java方法. 通过使用合适的JNI函数,你能够创建Java对象,get.set 静态(static)和 实例(instanc ...

  4. HAL中通过JNI调用java方法【转】

    转载请注明本文出处:http://www.cnblogs.com/xl19862005 作者:Xandy 由于工作的需要,最近一直在研究HAL.JNI.Java方法之间互调的问题,并做了如下一些记录和 ...

  5. (原)使用android studio ndk开发流程

    先使用android stuido创建一个app工程,创建工程的时候,.gradle目录结构下为2.8目录.(note:2.10目录为后续更新结果出现.) 依次修改上述红色方框标注部分内容: 1)修改 ...

  6. Android Studio NDK 学习之接受Java传入的字符串

    本博客是基于Android Studio 1.3 preview版本,且默认你已经安装了Android SDK, Android NDK. 用Android Studio新建一个工程叫Prompt,其 ...

  7. Android Studio NDK 学习之接受Java传入的Int数组

    本博客是基于Android Studio 1.3 preview版本,且默认你已经安装了Android SDK, Android NDK. 用Android Studio新建一个工程叫AndroidJ ...

  8. Android Studio NDK开发入门

    从Android Studio 1.3 Beta1开始,就支持了NDK,我目前使用的版本是1.5.首先强调几点. 1.必须安装NDK并配置好环境变量(和配置JDK环境变量如出一辙:新建NDK_HOME ...

  9. android studio ndk 开发入门

    ndk 开发没什么神秘的ndk 说白了就是一个交叉编译的工具链,用它来生成各个CPU架构下的静态或动态链接库,linux 下就是 .a 和 .so 文件.原理就是 java 通过 JNI 和 C.C+ ...

随机推荐

  1. JS使用onscroll、scrollTop实现图片懒加载

    今天做到项目中的图片展示,由于每一页的图片数量都很多,因此需要为图片的展示设计一种懒加载的功能. 第一要做的当然就是给程序添加滚动监听事件. //触发拉取图片开关,保证正在拉取时不能再次触发 var ...

  2. vs2015 dx15开发教程一

  3. Docker学习记录--入门了解+安装

    Docker简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机制, ...

  4. Eclipse中ctrl+shift+r与ctrl+shift+t的区别

    eclipse中的两个常用的快捷键可以大大提升查找文件的效率,分别是: ctrl+shift+r : open resource, 打开资源. 它可以打开当前eclipse的工作区中所有(打开的)工程 ...

  5. Codeforces 837D Round Subset - 动态规划 - 数论

    Let's call the roundness of the number the number of zeros to which it ends. You have an array of n ...

  6. python---01.各类计算机语言,python历史,变量,常量,数据类型,if条件

    一.认识计算机 1.硬件组成: CPU(大脑)  + 内存(缓冲) + 主板(连接各部分) + 电源(心脏)      + 显示器 + 键盘 +鼠标+ 显卡 + 硬盘 2.操作系统 ①windows  ...

  7. topcoder srm 490 div1

    problem1 link 首先每$n*m$一定是一个循环,所以只需要考虑时间$[0,n*m-1]$即可.这个期间一共出现了$n$个,第i个的出现时间为$m*i$,离开的时间为$\left \lcei ...

  8. SVM学习笔记5-SMO

    首先拿出最后要求解的问题:$\underset{\alpha}{min}W(\alpha)=\frac{1}{2} \sum_{i,j=1}^{n}y^{(i)}y^{(j)}\alpha_{i}\a ...

  9. Android灯光系统通知灯【转】

    本文转载自:https://blog.csdn.net/danwuxie/article/details/82193880 一.通知灯应用程序的编写 1.首先实现一个按钮功能 <LinearLa ...

  10. 永久修改VS include目录

    原文:https://blog.csdn.net/sysprogram/article/details/49214727 VS2008在选项里可以设置全局的Include目录和Lib目录, 但是VS2 ...