上一章讲解了JNI中一些函数表的说明,这节开始讲解Java与C++互调的过程。
在Android Studio3.0中创建一个支持JNI开发的Android程序。
编写activity_main.xml布局:
<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, "" , "()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, "
" , "()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);
_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);
JNI接口指针是本地方法的第一个参数,其类型是JNIEnv;第二个参数随本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用。其余的参数对应于通常Java方法的参数。本地方法调用利用返回值将结果传回调用程序中。
- 非static的方法参数类型是jobject instance
- 而static的方法参数类型是jclass type
在GetMethodID( )和GetFieldID( )这两个函数中,最后一个参数都是签名字符串,用来标识java函数和成员的唯一性。
由于Java中存在重载函数,所以一个函数名不足以唯一指定一个函数,这时候就需要签名字符串来指定函数的参数列表和返回值类型了。
函数签名是一个字符串:”(M)N”
括号中的内容是函数的参数类型,括号后面表示函数的返回值。
例如:
(I)V 带一个int 类型的参数,返回值类型为void
( )D 没有参数,返回double
“(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[] |
如果Java函数的参数是class,则以”L”开头,以”;”结尾,中间是用”/” 隔开的包及类名。而其对应的C函数名的参数则为jobject
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