相对于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. django 函数装饰器 变为 类装饰器

    aaa  

  2. yii2 mysql根据多个字段的数据计算排序

    mysql根据多个字段的数据计算排序 select *,num1+num2*10+num3*100 num from $tableName order by num desc yii2框架活动记录ac ...

  3. jQuery实现省市联动

    未实现任何功能之前: 实现功能之后: 实现该功能的jQuery核心代码: <script> var provinceArr=new Array(5); provinceArr[0]=new ...

  4. Markdown语法学习(Github/git.oschina.net上README.md书写规范)(转)

    晚上在更新git.oschina.net项目时,突然想知道README.md后缀的来源,于是搜了下,发现README.md使用了一种小标记语言Markdown的语法,于是简单的看了一个,特转载如下,为 ...

  5. Zabbix poller processes more than 75% busy

    Centos7.5  在设置网络监控的时候zabbix提示Zabbix poller processes more than 75% busy 问题 原因 默认只开启一个Discoverers进程,就 ...

  6. B/S交互过程及tomcat体系结构

    浏览器与服务器交互过程: 1.浏览器根据主机名,如www.baidu.com,去操作系统的hosts文件中查找主机名对应的ip地址. 2.如果查找不到,则会去互联网上的dns服务器上查找主机名对应的i ...

  7. Ubuntu package system is broken

    原文链接安装软件的时候出现错误,遇到包管理系统损坏的问题.在askubuntu上找到解决办法,这里进行记录总结:首先:sudo apt-get -f install 强制安装没有下载完成的文件.然后: ...

  8. # bzoj2215: [Poi2011]Conspiracy 2-sat

    bzoj2215: [Poi2011]Conspiracy 2-sat 链接 https://www.lydsy.com/JudgeOnline/problem.php?id=2215 思路 一个点的 ...

  9. LuoguP2680 运输计划

    题目地址 题目链接 题解 二分答案,那么大于答案的路径都需要有一条公共边,maxlen-val>=二分出来的x.val是边权. 考虑树剖,对每条大于答案的路径都+1(线段树里),枚举边,如果(线 ...

  10. LuoguP1041 传染病控制

    题目地址 题目链接 题解 这里讲一个非正解--贪心+随机化. 贪心的想法是什么? 我们dfs一遍处理出每个节点子树内的节点数量,记为\(siz\). 贪心的砍掉\(siz\)最大的那个子树,在树的形态 ...