概述

本文主要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. kernelchina.org内核研究

    kernelchina.org 内核研究 转自:http://www.kernelchina.org

  2. python进程池pool的starmap的使用

    #!/usr/bin/env python3 from functools import partial from itertools import repeat from multiprocessi ...

  3. gdrive无限网盘挂载systemd文件

    我的博客新地址:www.liuquanhao.com --------------------------------------------------------------------- 首先应 ...

  4. shell 通过ANSI转换颜色

    格式: echo -e "\033[字背景颜色;字体颜色m字符串\033[控制码" 如果单纯显示字体颜色可以固定控制码位0m. 格式: echo -e "\033[字背景 ...

  5. 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件

        好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...

  6. 移动开发之css3实现背景渐变效果

    前段时间由于手机项目需要实现一个背景渐变功能,  开始是想切个小背景图平铺下,  后来想到css3可以实现,如是用下面的代码就实现了. .sec_case_list li  span{ backgro ...

  7. 【C++】多重继承

    1. 多重继承时的二义性 当使用多重继承时,如果多个父类都定义了相同名字的变量,则会出现二义性.解决方法:使用 :: 声明作用域 #include <iostream> using nam ...

  8. python的一致性(1)sorted和len

    每个语言,都有自己的特性,而一致性,是python语言特性重要的一个环节. 比如排序,我们不是aaa.sort(),而是 sorted(aaa),比如len,不是aaa.length(),而是len( ...

  9. Mysql中使用存储过程返回查询多个表的数据信息

    -- 测试手机号 call P_Base_CheckLogin('); -- 测试登录名 call P_Base_CheckLogin('sch000001') -- 测试身份证号 call P_Ba ...

  10. 浙江省第十二届省赛 Beauty of Array(思维题)

    Description Edward has an array A with N integers. He defines the beauty of an array as the summatio ...