概述

本文主要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对象的传递与改动的更多相关文章

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

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

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

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

  3. Android Studio NDK开发-JNI调用Java方法

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

  4. Android Studio NDK编程初探

    继上一篇学习了如何使用NDK编译FFMPEG后,接下来就是要学习如何在Android Studio中使用了. 经过参考和一系列的摸索,记录下具体步骤. 创建C++ Support的Android St ...

  5. 第七章 : Git 介绍 (上)[Learn Android Studio 汉化教程]

    Learn Android Studio 汉化教程 [翻译]Git介绍 Git版本控制系统(VCS)快速成为Android应用程序开发以及常规的软件编程领域内的事实标准.有别于需要中心服务器支持的早期 ...

  6. [Learn Android Studio 汉化教程]第三章:使用 Android Studio 编程

    [Learn Android Studio 汉化教程]第三章:使用 Android Studio 编程 本章包含如何在 Android Studio 中书写或生成代码. Android Studio ...

  7. 第六章:Reminders实验:第二部分[Learn Android Studio 汉化教程]

    Learn Android Studio 汉化教程 Reminders Lab: Part 2 This chapter covers capturing user input through the ...

  8. 第五章:Reminders实验:第一部分[Learn Android Studio 汉化教程]

    Learn Android Studio 汉化教程 By now you are familiar with the basics of creating a new project, program ...

  9. 第三章:使用 Android Studio 编程[Learn Android Studio 汉化教程]

    Learn Android Studio 汉化教程 Android Studio 本章包含如何在Android Studio中书写或生成代码. Android Studio 使用面向对象编程的思想来生 ...

随机推荐

  1. redis有string,hash,list,sets.zsets几种数据类型

    1.string数据类型 可包含任何数据,是二进制安全的,比如图片或者序列化的对象set key valueset name hkset age 20get name 得到"hk" ...

  2. BeeFramework 系列一 安装篇(Arc)

    http://ilikeido.iteye.com/blog/1881390 Beeframework 是一款iOS快速开发框架,它以UISignal强大的路由功能替代原有Delegate方式,完成复 ...

  3. gdb 调试 ncurses 全过程:

    转载地址: http://blog.jobbole.com/107759/ gdb 调试 ncurses 全过程: 发现网上的“gdb 示例”只有命令而没有对应的输出,我有点不满意.gdb 是 GNU ...

  4. 关于Solaris的一些小技巧

    关于Solaris的一些小技巧 http://blog.chinaunix.net/uid-9787800-id-2394301.html SunOS 操作命令及linux区别 http://blog ...

  5. Oracle基础 07 参数文件 pfile/spfile

    --查看数据库运行模式(spfile还是pfile)select decode(count(*),1,'spfile','pfile') from v$spparameterwhere rownum= ...

  6. delphi dispose释放内存的方法 New 和 GetMem 的区别

    来自:http://blog.sina.com.cn/s/blog_4bc47d2301018trf.html -------------------------------------------- ...

  7. ssl介绍以及双向认证和单向认证原理

    SSL安全证书可以自己生成,也可以通过第三方的CA(Certification Authority)认证中心付费申请颁发. SSL安全证书包括: 1.       CA证书,也叫根证书或中间级证书.单 ...

  8. 利其器之webstorm快捷键

    总结几个webstorm常用的快捷键(macbook下) 最实用: command + option + 左/右箭头           定位到历史记录中上次/下次编辑的位置 command + b ...

  9. JVM的分代思想

    Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代.老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存分代策略. 永久代是HotSpot虚拟机特有的概念,它采用永久 ...

  10. [BZOJ1176][Balkan2007]Mokia cdq+树状数组

    1176: [Balkan2007]Mokia Time Limit: 30 Sec  Memory Limit: 162 MBSubmit: 3134  Solved: 1395[Submit][S ...