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 使用面向对象编程的思想来生 ...
随机推荐
- 如何在 Linux 上锁定虚拟控制台会话
当你在共享的系统上工作时,你可能不希望其他用户偷窥你的控制台中看你在做什么.如果是这样,我知道有个简单的技巧来锁定自己的会话,同时仍然允许其他用户在其他虚拟控制台上使用该系统.要感谢 Vlock(Vi ...
- 两个kernel.org国内镜像
两个kernel.org国内镜像 https://mirror.tuna.tsinghua.edu.cn/kernel/v4.x/testing/ http://mirror.bjtu.edu.cn/ ...
- javascript面试题(一)(转载)
1,判断字符串是否是这样组成的,第一个必须是字母,后面可以是字母.数字.下划线,总长度为5-20 var reg = /^[a-zA-Z][a-zA-Z_0-9]{4,19}$/; /*注意:1.要用 ...
- Spring:基于注解的依赖注入的使用
1.什么是pojo?什么是bean? 首先,在之前几篇Spring的介绍文章当中,自己都提到了一个名词叫做POJO类,但是在回顾Spring的注解的使用的时候,去形容java当中的对象还有一个名词是叫 ...
- magento 搬家
1.删除浏览记录: log_customer log_visitor log_visitor_info log_url log_url_info log_quote report_viewed_pro ...
- CF 862A Mahmoud and Ehab and the MEX【数组操作】
A. Mahmoud and Ehab and the MEX time limit per test 2 seconds memory limit per test 256 megabytes in ...
- HDU 2673 (排序)
Acmer in HDU-ACM team are ambitious, especially shǎ崽, he can spend time in Internet bar doing proble ...
- AMQ学习笔记 - 03. 消息的接收方式
概述 消息有两种接收方式:同步接收和异步接收. 同步接收:主线程阻塞式等待下一个消息的到来,可以设置timeout,超时则返回null. 异步接收:主线程设置MessageListener,然后继续做 ...
- Lambda演算(二)归约!归约!归约!
(一) 这里先不列出λ项的正式定义,只记住λ表达式语义上的构造方式为: x 一个单独的变量名是一个λ项表达式: (λx.M) 该λ表示一个函数.其中 M 是这个函数的函数体,M 本身也是一个 λ项. ...
- NULL always return false
Any comparison with null is false - = and <>, >, <, and so on. You cannot use null in an ...