[置顶] Android Studio3.0开发JNI流程------Java调用C++以及C++调用Java
2017-11-30 17:24
423 查看
上一章讲解了JNI中一些函数表的说明,这节开始讲解Java与C++互调的过程。
在Android Studio3.0中创建一个支持JNI开发的Android程序。
编写activity_main.xml布局:
编写MainActivity.java中
在native-lib.cpp中编写自己需要的程序。
首先第一个TextView实现Java调用C++代码,并在C++中编写代码。这是Java调用C++的过程。
第二个TextView实现C++与Java互相调用的过程,就是Java先执行C++的方法,再C++去执行Java的方法,这就是C++与Java互调的过程。
或许,你还在想为什么不在Java代码中执行Java方法呢,而是需要Java调用C++,再让C++去执行Java代码这么繁琐的过程呢?
原因有很多:一、在Android中利用NDK进行编程的时候,一般的都是Java层通过JNI调用C++的相关接口,而在有的应用中,需要通过底层C++调用Java层来实现相关功能。比如在进行OMX硬解码画图的时候,需要在底层不断发送请求给Java层,让其不断刷新GlSurfaceView。二、在Java层有很多C/C++不太容易实现的功能,比如,视频处理相关等。三、在Android中使用这种方法可以增加程序被反编译的安全性。即使apk被反编译成smali代码,但是有很多so库,会给反编译的人增加一定的难度。C++难以反编译的特性也可以为Android开发带来代码的保密,另外native特性也可以提高代码的运行效率。
先来说说jobject getInstance( )在执行什么流程,估计好多人不明白,不是java类没有这个方法吗,这个方法是做什么的?
其实这是java类的实例化
开发中都知道java需要 obj var = new obj();这样一个过程,我们在C++中调用java类的成员函数,当然也要实例化一个类。
示例化的函数如下所示
这个函数中的env表示环境参数,jclass表示一个java类的句柄。
jmethodID construction_id = env->GetMethodID(obj_class, “”, “()V”);
GetMethodID的参数分别为(类句柄,方法名称,参数名称)
这个是为了获取java类中某个方法的句柄,有一点需要特别注意的,在获取构造方法的句柄和别的方法的句柄是不一样的。
获取一般方法的句柄所填写“方法名称”参数直接就是这个方法的名称,而构造函数的话就必须填写”“。除了这点区别外,就没有区别了。
当然,也可不需要这么做。
在Java_fj_clover_testjni_MainActivity_stringFromJNI( )方法,无非就是执行以下几个步骤:
1.获取Java中的类,传入一个jclass对象表示native( )方法所在的类
jclass java_class = env->FindClass(“fj/clover/testjni/MainActivity”);
2.获取对应类中的java方法
jmethodID java_method = env->GetMethodID(java_class, “GetJavaString”, “()Ljava/lang/String;”);
注意:( )Ljava/lang/String;组成部分,结束分号(;)一定不能少,否则报错。
3.调用该方法
str = (jstring)env->CallObjectMethod(thiz, java_method);
程序运行结果:
使用JNIEnv提供的JNI方法,我们就可以获得属性和方法相对应的jfieldID和jmethodID。
分配对象(AllocObject/NewObject),并且控制对象的引用计数。
(NewGlobalRef/DeleteGlobalRef/DeleteLocalRef/IsSameObject/NewLocalRef)
获取类的定义(FindClass),并通过类的定义来获取获取类得方法和成员的ID(GetMethodID/GetFieldID)
通过方法ID调用类的普通方法(CallObjectMethod)和静态方法(CallStaticObjectMethod)
通过成员ID获取和设置类的普通成员(GetObjectField/SetObjectField)和静态成员(GetStaticObjectField/SetStaticObjectField)
下面是比较常用的方法:
查找该类:
取得方法的id:
查找需要调用的该类的方法:
取得静态方法的id
jmethodID xxx = (*env)->GetStaticMethodID(env,jclass, methodName,”(M)N”)
初始化该类的实例:
调用实例的某方法:
释放实例:
取得成员变量的id
取得静态成员变量的id
JNIEnv - 表示java的运行环境
jobject - 代表是非static的方法
jclass - 代表是static的方法
JNI接口指针是本地方法的第一个参数,其类型是JNIEnv;第二个参数随本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用。其余的参数对应于通常Java方法的参数。本地方法调用利用返回值将结果传回调用程序中。
非static的方法参数类型是jobject instance
而static的方法参数类型是jclass type
由于Java中存在重载函数,所以一个函数名不足以唯一指定一个函数,这时候就需要签名字符串来指定函数的参数列表和返回值类型了。
函数签名是一个字符串:”(M)N”
括号中的内容是函数的参数类型,括号后面表示函数的返回值。
例如:
(I)V 带一个int 类型的参数,返回值类型为void
( )D 没有参数,返回double
具体的每一个字符的对应关系如下:
数组则以”[“开始,用两个字符表示
如果Java函数的参数是class,则以”L”开头,以”;”结尾,中间是用”/” 隔开的包及类名。而其对应的C函数名的参数则为jobject
举个例子:
String类,其对应的类为jstring,其形式为
Socket类的形式为
如果Java函数位于一个嵌入类中,则可用$作为类名间的分隔符。
例如:
代码资源下载http://download.csdn.net/download/cloverjf/10139677
具体语言的查看之前的两篇文章:
JNI接口函数和指针:http://blog.csdn.net/cloverjf/article/details/78654366
JNI的类型和数据结构:http://blog.csdn.net/cloverjf/article/details/78655878
在Android Studio3.0中创建一个支持JNI开发的Android程序。
编写activity_main.xml布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context="fj.clover.testjni.MainActivity"> <TextView android:id="@+id/sample_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="20dp" android:text="Hello World!" /> <TextView android:id="@+id/sample_text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> </LinearLayout>
编写MainActivity.java中
package fj.clover.testjni; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 使用Java调用C++,在C++代码中编码自己需求 TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI1("Hello JNI")); //使用C++调用Java,在java代码中编写需求,但是在C++中执行调用java TextView tv1 = (TextView) findViewById(R.id.sample_text1); tv1.setText(stringFromJNI()); } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); public native String stringFromJNI1(String str); //定义一个方法 public String GetJavaString() { String str; str = "输出这些内容就是对的..."; return str; } }
在native-lib.cpp中编写自己需要的程序。
首先第一个TextView实现Java调用C++代码,并在C++中编写代码。这是Java调用C++的过程。
JNIEXPORT jstring JNICALL Java_fj_clover_testjni_MainActivity_stringFromJNI1(JNIEnv *env, jobject thiz, jstring js) { jstring str; // TODO:在这里编写自己的程序需求,在这里只做简单的输出js操作 str=js; return str; }
第二个TextView实现C++与Java互相调用的过程,就是Java先执行C++的方法,再C++去执行Java的方法,这就是C++与Java互调的过程。
或许,你还在想为什么不在Java代码中执行Java方法呢,而是需要Java调用C++,再让C++去执行Java代码这么繁琐的过程呢?
原因有很多:一、在Android中利用NDK进行编程的时候,一般的都是Java层通过JNI调用C++的相关接口,而在有的应用中,需要通过底层C++调用Java层来实现相关功能。比如在进行OMX硬解码画图的时候,需要在底层不断发送请求给Java层,让其不断刷新GlSurfaceView。二、在Java层有很多C/C++不太容易实现的功能,比如,视频处理相关等。三、在Android中使用这种方法可以增加程序被反编译的安全性。即使apk被反编译成smali代码,但是有很多so库,会给反编译的人增加一定的难度。C++难以反编译的特性也可以为Android开发带来代码的保密,另外native特性也可以提高代码的运行效率。
//Java中new对象的过程 jobject getInstance(JNIEnv* env, jclass obj_class){ jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V"); jobject obj = env->NewObject(obj_class, construction_id); return obj; } JNIEXPORT jstring JNICALL Java_fj_clover_testjni_MainActivity_stringFromJNI(JNIEnv *env,jobject thiz) { jstring str; //因为stringFromJNI不是静态的,所以传进来的就是调用这个函数的对象 //否则就传入一个jclass对象表示native()方法所在的类 //jclass java_class = env->GetObjectClass(thiz); //获取Java中的类,传入一个jclass对象表示native()方法所在的类 ,这种方法通用 jclass java_class = env->FindClass("fj/clover/testjni/MainActivity"); if(java_class==0){ return env->NewStringUTF("not find class..."); } //这种是构造模式。其实就是Java中new对象的过程 jobject java_obj = getInstance(env, java_class); if(java_obj==0){ return env->NewStringUTF("not find java OBJ"); } //获取对应类中的java方法 jmethodID java_method = env->GetMethodID(java_class, "GetJavaString", "()Ljava/lang/String;"); if(java_method==0){ return env->NewStringUTF("not find java method"); } //调用方法 //str =env->CallObjectMethod(java_obj,java_method); str = (jstring)env->CallObjectMethod(thiz, java_method); return str; }
先来说说jobject getInstance( )在执行什么流程,估计好多人不明白,不是java类没有这个方法吗,这个方法是做什么的?
其实这是java类的实例化
开发中都知道java需要 obj var = new obj();这样一个过程,我们在C++中调用java类的成员函数,当然也要实例化一个类。
示例化的函数如下所示
jobject getInstance(JNIEnv* env, jclass obj_class){ jmethodID construction_id = env->GetMethodID(obj_class, "<init>", "()V"); jobject obj = env->NewObject(obj_class, construction_id); return obj; }
这个函数中的env表示环境参数,jclass表示一个java类的句柄。
jmethodID construction_id = env->GetMethodID(obj_class, “”, “()V”);
GetMethodID的参数分别为(类句柄,方法名称,参数名称)
这个是为了获取java类中某个方法的句柄,有一点需要特别注意的,在获取构造方法的句柄和别的方法的句柄是不一样的。
获取一般方法的句柄所填写“方法名称”参数直接就是这个方法的名称,而构造函数的话就必须填写”“。除了这点区别外,就没有区别了。
当然,也可不需要这么做。
在Java_fj_clover_testjni_MainActivity_stringFromJNI( )方法,无非就是执行以下几个步骤:
1.获取Java中的类,传入一个jclass对象表示native( )方法所在的类
jclass java_class = env->FindClass(“fj/clover/testjni/MainActivity”);
2.获取对应类中的java方法
jmethodID java_method = env->GetMethodID(java_class, “GetJavaString”, “()Ljava/lang/String;”);
注意:( )Ljava/lang/String;组成部分,结束分号(;)一定不能少,否则报错。
3.调用该方法
str = (jstring)env->CallObjectMethod(thiz, java_method);
程序运行结果:
列出NDK开发一些常用到的知识。
_JNIEnv定义了一个虚拟机的接口,*env表示接口指针。通过这个接口可以访问虚拟机的所有功能。为了在C/C++中表示属性和方法,JNI在jni.h头文件中定义了jfieldID和jmethodID类型来分别代表Java对象的属性和方法。我们在访问或是设置Java属性的时候,首先就要先在本地代码取得代表该Java属性的jfieldID,然后才能在本地代码进行Java属性操作。同样的,我们需要调用Java对象方法时,也是需要取得代表该方法的jmethodID才能进行Java方法调用。使用JNIEnv提供的JNI方法,我们就可以获得属性和方法相对应的jfieldID和jmethodID。
分配对象(AllocObject/NewObject),并且控制对象的引用计数。
(NewGlobalRef/DeleteGlobalRef/DeleteLocalRef/IsSameObject/NewLocalRef)
获取类的定义(FindClass),并通过类的定义来获取获取类得方法和成员的ID(GetMethodID/GetFieldID)
通过方法ID调用类的普通方法(CallObjectMethod)和静态方法(CallStaticObjectMethod)
通过成员ID获取和设置类的普通成员(GetObjectField/SetObjectField)和静态成员(GetStaticObjectField/SetStaticObjectField)
下面是比较常用的方法:
查找该类:
jclass xxx = (*env)->FindClass(env, "Lclass_name;");
取得方法的id:
jmethodID xxx = (*env)->GetMethodID(env, jclass, methodName, "(M)N");
查找需要调用的该类的方法:
jmethodID xxx = (*env)->GetMethodID(env, jclass, "(M)N" );
取得静态方法的id
jmethodID xxx = (*env)->GetStaticMethodID(env,jclass, methodName,"(M)N")
jmethodID xxx = (*env)->GetStaticMethodID(env,jclass, methodName,”(M)N”)
初始化该类的实例:
jobject xxx = (*env)->NewObject(env, jclass, jmethodID );
调用实例的某方法:
(*env)->CallObjectMethod(env, jobject, jmethodID, [parameter1, parameter2,...]);
释放实例:
(*env)->DeleteLocalRef(env, xxx);
取得成员变量的id
jfieldID xxx = (*env)->GetFieldID(env , jclass , jfieldID , jfieldType) ;
取得静态成员变量的id
jfieldID xxx = GetStaticFieldID(env,jclass ,jfieldID,jfieldType);
JNIEnv - 表示java的运行环境
jobject - 代表是非static的方法
jclass - 代表是static的方法
JNI接口指针是本地方法的第一个参数,其类型是JNIEnv;第二个参数随本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用。其余的参数对应于通常Java方法的参数。本地方法调用利用返回值将结果传回调用程序中。
非static的方法参数类型是jobject instance
而static的方法参数类型是jclass type
函数与属性签名
在GetMethodID( )和GetFieldID( )这两个函数中,最后一个参数都是签名字符串,用来标识java函数和成员的唯一性。由于Java中存在重载函数,所以一个函数名不足以唯一指定一个函数,这时候就需要签名字符串来指定函数的参数列表和返回值类型了。
函数签名是一个字符串:”(M)N”
括号中的内容是函数的参数类型,括号后面表示函数的返回值。
例如:
(I)V 带一个int 类型的参数,返回值类型为void
( )D 没有参数,返回double
JNI 类型签名
“(M)N”,这里的M和N指的是该函数的输入和输出参数的类型签名(Type Signature)。具体的每一个字符的对应关系如下:
字符 | Java类型 | C类型 |
---|---|---|
V | void | void |
Z | jboolean | boolean |
I | jint | int |
J | jlong | long |
D | jdouble | double |
F | jfloat | float |
B | jbyte | byte |
C | jchar | char |
S | jshort | short |
数组 | 本地类型 | java类型 |
---|---|---|
[I | jintArray | int[] |
[F | jfloatArray | float[] |
[B | jbyteArray | byte[] |
[C | jcharArray | char[] |
[S | jshortArray | short[] |
[D | jdoubleArray | double[] |
[J | jlongArray | long[] |
[Z | jbooleanArray | boolean[] |
举个例子:
String类,其对应的类为jstring,其形式为
Ljava/lang/String; String jstring
Socket类的形式为
Ljava/net/Socket; Socket jobject
如果Java函数位于一个嵌入类中,则可用$作为类名间的分隔符。
例如:
(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z
代码资源下载http://download.csdn.net/download/cloverjf/10139677
具体语言的查看之前的两篇文章:
JNI接口函数和指针:http://blog.csdn.net/cloverjf/article/details/78654366
JNI的类型和数据结构:http://blog.csdn.net/cloverjf/article/details/78655878
相关文章推荐
- JNI NDK (AndroidStudio+CMake )实现Java调用C C++代码流程
- [置顶] Android Studio3.0开发JNI流程------JNI中字符串拼接的三种方式(C++)
- JNI NDK (AndroidStudio+CMake )实现C C++调用Java代码流程
- 安卓应用开发通过java调用c++ jni的图文使用方法
- android jni开发 把一段java代码转换成c++方式调用
- Android JNI/NDK开发(2)JNI实现C/C++与Android/JAVA相互调用
- Android-NDK开发之基础--Android JNI实例代码(一)-- 在JNI中执行Java方法--C/C++调用Java
- 【JNI开发】C++调用java函数的实现方法
- [置顶] Android Studio3.0开发JNI流程------Java多态性在JNI的表现形式
- [置顶] java后端 三方登录之微信登录 开发详细流程以及遇到过的坑 总结(非常详细) 2017-12-26
- android jni开发 把一段java代码转换成c++方式调用
- Android-NDK开发之基础--Android JNI实例代码(一)-- 在JNI中执行Java方法--C/C++调用Java
- Ubuntu Feisty 下开发 JNI 应用步骤 (Java调用C/C++的技术)
- Android-NDK开发之基础--Android JNI实例代码(一)-- 在JNI中执行Java方法--C/C++调用Java
- 【转载整理】eclipse 开发c/c++、Java使用JNI调用C程序、生成64位dll动态链接库
- Android开发 通过JNI实现JAVA与C/C++程序间的调用和回调
- Java使用JNI调用C++的完整流程
- Android NDK 开发 C++调用java数组 以及方法
- JNI ,C++调用java