Android Studio NDK 新手教程(5)--Java对象的传递与改动
概述
本文主要Java与C++之间的对象传递与取值。包括传递Java对象、返回Java对象、改动Java对象、以及性能对照。
通过JNIEnv完毕数据转换
Java对象是存在于JVM虚拟机中的,而C++是脱离JVM而执行的,假设在C++中訪问和使用Java中的对象。必定会使用JNIEnv这个桥梁。事实上通过以下的代码非常easy看出,这样的訪问方式和Java中的反射十分雷同。
这里定义一个简单Java对象用于下文測试:
package com.example.wastrel.hellojni;
/**
* Created by wastrel on 2016/8/24.
*/
public class Bean {
private String msg;
private int what;
public Bean(String msg,int what)
{
this.msg=msg;
this.what=what;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getWhat() {
return what;
}
public void setWhat(int what) {
this.what = what;
}
@Override
public String toString() {
return "Msg:"+msg+";What:"+what;
}
}
从C++中创建一个Java对象并返回
//Java中的native方法声明
public native Bean newBean(String msg,int what);
//C++中的方法实现
JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean
(JNIEnv *env, jobject obj, jstring msg,jint what){
//先找到class
jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
//在实际应用中应该确保你的class、method、field存在。
降低此类推断。
if(bean_clz==NULL)
{
LOGE("can't find class");
return NULL;
}
//获取构造函数。构造函数的返回值是void,因此这里方法签名最后为V
jmethodID bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V");
if(bean_init==NULL)
{
LOGE("can't find init function");
return NULL;
}
//然后调用构造函数获得bean
jobject bean=env->NewObject(bean_clz,bean_init,msg,what);
return bean;
}
注:假设提示找不到NULL 请
include<stddef.h>
C++中解析Java对象
//java方法Native声明
public native String getString(Bean bean);
//C++中的方法实现
JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getString
(JNIEnv *env, jobject obj,jobject bean){
jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
//这部分是通过get函数去获取相应的值
// jmethodID bean_getmsg=env->GetMethodID(bean_clz,"getMsg","()Ljava/lang/String;");
// jmethodID bean_getwhat=env->GetMethodID(bean_clz,"getWhat","()I");
// jstring jmsg=(jstring)env->CallObjectMethod(bean,bean_getmsg);
// jint what=env->CallIntMethod(bean,bean_getwhat);
//这部分是通过类的成员变量直接取获取值。你可能注意到在Java中定义的变量都是private修饰的。但在反射的调用下是毫无作用的。
jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");
jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");
jstring jmsg=(jstring)env->GetObjectField(bean,bean_fmsg);
jint what=env->GetIntField(bean,bean_fwhat);
//将拿到的值拼装一个String返回回去
const char * msg=env->GetStringUTFChars(jmsg,NULL);
char *str=new char[1024];
sprintf(str,"Msg:%s;What:%d(From C++)",msg,what);
jstring rs=env->NewStringUTF(str);
delete []str;
env->ReleaseStringUTFChars(jmsg,msg);
return rs;
}
注:sprintf函数包括在stdio.h头文件里
C++中改动Java对象属性值
//java方法Native声明
public native void ModifyBean(Bean bean);
//C++实现
JNIEXPORT void JNICALL Java_com_example_wastrel_hellojni_HelloJNI_ModifyBean
(JNIEnv *env, jobject obj,jobject bean){
jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");
jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");
jstring msg=env->NewStringUTF("Modify in C++");
//又一次设置属性
env->SetObjectField(bean,bean_fmsg,msg);
env->SetIntField(bean,bean_fwhat,20);
return;
}
结果图
//java中调用代码
HelloJNI helloJNI=new HelloJNI();
Bean bean=helloJNI.newBean("This is from C++ bean",10);
tv.setText(bean.toString());
bean=new Bean("This is from Java bean",15);
tv.append("\n"+helloJNI.getString(bean));
helloJNI.ModifyBean(bean);
tv.append("\n"+bean.toString());
Java中new Object和C++中new Object的性能对照
以下我们通过一个測试函数来比較通过两种方式的性能,这里能够毫无疑问的告诉你,Java一定比C++的快。那么这个对照的意义就在于,使用C++创建Java对象的时候会不会造成不可接受的卡顿。
这里使用的測试机是华为Mate7,详细硬件配置可自行百度。
測试函数例如以下:
void Test(int count)
{
long startTime=System.currentTimeMillis();
for (int i=0;i<count;i++)
{
new Bean("123",i);
}
long endTime=System.currentTimeMillis();
Log.e("Java","Java new "+count+"s waste "+(endTime-startTime)+"ms");
HelloJNI helloJNI=new HelloJNI();
startTime=System.currentTimeMillis();
for (int i=0;i<count;i++)
{
helloJNI.newBean("123",i);
}
endTime=System.currentTimeMillis();
Log.e("C++","C++ new "+count+"s waste "+(endTime-startTime)+"ms");
}
測试结果:
Java: Java new 5000s waste 3ms
C++: C++ new 5000s waste 38ms
Java: Java new 10000s waste 6ms
C++: C++ new 10000s waste 79ms
Java: Java new 50000s waste 56ms
C++: C++ new 50000s waste 338ms
Java: Java new 100000s waste 60ms
C++: C++ new 100000s waste 687ms
通过结果能够看出,通过C++来new对象比Java慢了足足10倍左右。可是从时间上来讲。假设仅仅是在C++中new一个Java对象。几个微秒的时间差距全然是能够忽略不计的。
或许有人就会说。C++慢那么多是由于每次都在FindClass,GetMethodId。而在程序执行过程中这两个值是不会改变的。听起来确实有这样一个原因。以下我们将C++中的代码稍作改动缓存jclass和jmethodId。
改动后的newBean函数:
//用静态变量缓存
static jclass bean_clz=NULL;
static jmethodID bean_init=NULL;
JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean
(JNIEnv *env, jobject obj, jstring str,jint what){
//先找到class
if(bean_clz==NULL)
{
jclass _bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
bean_clz=(jclass)env->NewGlobalRef(_bean_clz);
}
//获取构造函数。构造函数的返回值是void,因此这里方法签名最后为V
if(bean_init==NULL)
{
bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V");
}
//然后调用构造函数获得bean
jobject bean=env->NewObject(bean_clz,bean_init,str,what);
return bean;
}
你可能发现了缓存方法ID和缓存jclass似乎不一样。那是由于jclass事实上是java.lang.Class对象,而方法ID是JNI中定义的一个结构体。假设这里不使用
env—>NewGlobalRef()
函数声明其是一个全局引用的话,在执行的时候可能就会报错:JNI ERROR (app bug): accessed stale local reference 0x5900021
;表明在Jvm中该对象已经被回收了。引用已经失效了。而NewGlobalRef的作用就在于告诉JVM。C++中一直持有该引用,请不要回收。显然这又引发了另外一个问题。你须要在你不须要该引用的时候告诉JVM,那么就须要调用env->DelGlobalRef()
。当然你也能够不调用。那么该Java对象将在你的程序关闭的时候被回收。
測试结果:
Java: Java new 5000s waste 3ms
C++: C++ new 5000s waste 18ms
Java: Java new 10000s waste 5ms
C++: C++ new 10000s waste 24ms
Java: Java new 50000s waste 44ms
C++: C++ new 50000s waste 121ms
Java: Java new 100000s waste 65ms
C++: C++ new 100000s waste 259ms
这次的结果表明,假设缓存方法ID和jclass能缩短一半的时间。但仍然不如Java快。这也非常好理解。C++创建Java对象终于还是通过Java创建的,重复的通过反射去创建自然不如自身创建来得快。
总结
- JNI中想訪问Java Object方法签名、类名和变量名十分重要,一旦确定了就不要轻易单方面改动Java中的定义。由于这会导致JNI找不到相关的方法或类等,而引发JNI错误。
- 尽管JNI提供了各种方法来完毕Java的反射操作,可是请酌情使用,由于这会让Java代码与C++代码之间过度依赖。
- 当你须要返回C++中的结构体数据的时候,能够考虑把结构体转换成相应的Java对象返回。
Android Studio NDK 新手教程(5)--Java对象的传递与改动的更多相关文章
- Android Studio NDK 学习之接受Java传入的字符串
本博客是基于Android Studio 1.3 preview版本,且默认你已经安装了Android SDK, Android NDK. 用Android Studio新建一个工程叫Prompt,其 ...
- Android Studio NDK 学习之接受Java传入的Int数组
本博客是基于Android Studio 1.3 preview版本,且默认你已经安装了Android SDK, Android NDK. 用Android Studio新建一个工程叫AndroidJ ...
- Android Studio NDK开发-JNI调用Java方法
相对于NDK来说SDK里面有更多API可以调用,有时候我们在做NDK开发的时候,需要在JNI直接Java中的方法和变量,比如callback,系统信息等.... 如何在JNI中调用Java方法呢?就需 ...
- Android Studio NDK编程初探
继上一篇学习了如何使用NDK编译FFMPEG后,接下来就是要学习如何在Android Studio中使用了. 经过参考和一系列的摸索,记录下具体步骤. 创建C++ Support的Android St ...
- 第七章 : Git 介绍 (上)[Learn Android Studio 汉化教程]
Learn Android Studio 汉化教程 [翻译]Git介绍 Git版本控制系统(VCS)快速成为Android应用程序开发以及常规的软件编程领域内的事实标准.有别于需要中心服务器支持的早期 ...
- [Learn Android Studio 汉化教程]第三章:使用 Android Studio 编程
[Learn Android Studio 汉化教程]第三章:使用 Android Studio 编程 本章包含如何在 Android Studio 中书写或生成代码. Android Studio ...
- 第六章:Reminders实验:第二部分[Learn Android Studio 汉化教程]
Learn Android Studio 汉化教程 Reminders Lab: Part 2 This chapter covers capturing user input through the ...
- 第五章:Reminders实验:第一部分[Learn Android Studio 汉化教程]
Learn Android Studio 汉化教程 By now you are familiar with the basics of creating a new project, program ...
- 第三章:使用 Android Studio 编程[Learn Android Studio 汉化教程]
Learn Android Studio 汉化教程 Android Studio 本章包含如何在Android Studio中书写或生成代码. Android Studio 使用面向对象编程的思想来生 ...
随机推荐
- Python与金融分析基础
一 .ipython的简单使用 IPython:交互式的Python命令行 丰富的快捷键 TAB键自动完成 ?:内省.命名空间搜索 !:执行系统命令 魔术命令:以%开始的命令 %run:执行文件代码 ...
- UNREFERENCED_PARAMETER()的作用
告诉编译器,已经使用了该变量,不必警告. 在VC编译器下,当你声明了一个变量,而没有使用时,编译器就会报警告: “warning C4100: ''XXXX'' : unreferenced form ...
- [Leetcode Week11]Kth Largest Element in an Array
Kth Largest Element in an Array 题解 题目来源:https://leetcode.com/problems/kth-largest-element-in-an-arra ...
- [Leetcode Week3]Evaluate Division
Evaluate Division题解 原创文章,拒绝转载 题目来源:https://leetcode.com/problems/evaluate-division/description/ Desc ...
- Unordered load/store queue
A method and processor for providing full load/store queue functionality to an unordered load/store ...
- Linux内核官方文档atomic_ops.txt【摘自Linux 内核文档】
摘自Linux内核文档 Documentation/atomic_ops.txt,不是本人原创 Semantics and Behavior of Atomic and Bitmask Operati ...
- Linux下进程描述(1)—进程控制块【转】
转自:http://www.cnblogs.com/33debug/p/6705391.html 进程概念介绍 进程是操作系统对运行程序的一种抽象. • 一个正在执行的程序: • 一个正在计算机上执行 ...
- 学习OpenResty编程
1.Windows版本的下载位置 https://github.com/LomoX-Offical/nginx-openresty-windows Linux下OpenResty的下载和安装 http ...
- LCIS最长公共上升子序列
最长公共上升子序列LCIS,如字面意思,就是在对于两个数列A和B的最长的单调递增的公共子序列. 这道题目是LCS和LIS的综合. 在LIS中,我们通过两重循环枚举当序列以当前位置为结尾时,A序列中当前 ...
- Codeforces #439 Div2 E
#439 Div2 E 题意 给出二维平面,有多个询问: 把某一区域围起来(围墙之间无交点) 移除某一区域的围墙(此时保证围墙一定存在) 选定两个位置问是否可以互相到达 分析 看起来很复杂,其实这道题 ...