您的位置:首页 > 移动开发 > Android开发

[置顶] 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布局:

<?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类型
Vvoidvoid
Zjbooleanboolean
Ijintint
Jjlonglong
Djdoubledouble
Fjfloatfloat
Bjbytebyte
Cjcharchar
Sjshortshort
数组则以”[“开始,用两个字符表示

数组本地类型java类型
[IjintArrayint[]
[FjfloatArrayfloat[]
[BjbyteArraybyte[]
[CjcharArraychar[]
[SjshortArrayshort[]
[DjdoubleArraydouble[]
[JjlongArraylong[]
[ZjbooleanArrayboolean[]
如果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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: