Android Studio3.0开发JNI流程------Java调用C++以及C++调用Java

上一章讲解了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);

程序运行结果:
Android Studio3.0开发JNI流程------Java调用C++以及C++调用Java_第1张图片

列出NDK开发一些常用到的知识。

_JNIEnv定义了一个虚拟机的接口,*env表示接口指针。通过这个接口可以访问虚拟机的所有功能。为了在C/C++中表示属性和方法,JNI在jni.h头文件中定义了jfieldID和jmethodID类型来分别代表Java对象的属性和方法。我们在访问或是设置Java属性的时候,首先就要先在本地代码取得代表该Java属性的jfieldID,然后才能在本地代码进行Java属性操作。同样的,我们需要调用Java对象方法时,也是需要取得代表该方法的jmethodID才能进行Java方法调用。
使用JNIEnv提供的JNI方法,我们就可以获得属性和方法相对应的jfieldID和jmethodID。

  1. 分配对象(AllocObject/NewObject),并且控制对象的引用计数。
    (NewGlobalRef/DeleteGlobalRef/DeleteLocalRef/IsSameObject/NewLocalRef)

  2. 获取类的定义(FindClass),并通过类的定义来获取获取类得方法和成员的ID(GetMethodID/GetFieldID)

  3. 通过方法ID调用类的普通方法(CallObjectMethod)和静态方法(CallStaticObjectMethod)

  4. 通过成员ID获取和设置类的普通成员(GetObjectField/SetObjectField)和静态成员(GetStaticObjectField/SetStaticObjectField)

下面是比较常用的方法:

  1. 查找该类:
    jclass xxx = (*env)->FindClass(env, "Lclass_name;");
  2. 取得方法的id:
    jmethodID xxx = (*env)->GetMethodID(env, jclass, methodName, "(M)N");
  3. 查找需要调用的该类的方法:
    jmethodID xxx = (*env)->GetMethodID(env, jclass, "(M)N" );
  4. 取得静态方法的id
    jmethodID xxx = (*env)->GetStaticMethodID(env,jclass, methodName,"(M)N")
    jmethodID xxx = (*env)->GetStaticMethodID(env,jclass, methodName,”(M)N”)
  5. 初始化该类的实例:
    jobject xxx = (*env)->NewObject(env, jclass, jmethodID );
  6. 调用实例的某方法:
    (*env)->CallObjectMethod(env, jobject, jmethodID, [parameter1, parameter2,...]);
  7. 释放实例:
    (*env)->DeleteLocalRef(env, xxx);
  8. 取得成员变量的id
    jfieldID xxx = (*env)->GetFieldID(env , jclass , jfieldID , jfieldType) ;
  9. 取得静态成员变量的id
    jfieldID xxx = GetStaticFieldID(env,jclass ,jfieldID,jfieldType);
  • JNIEnv - 表示java的运行环境
  • jobject - 代表是非static的方法
  • jclass - 代表是static的方法

JNI接口指针是本地方法的第一个参数,其类型是JNIEnv;第二个参数随本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用。其余的参数对应于通常Java方法的参数。本地方法调用利用返回值将结果传回调用程序中。

  1. 非static的方法参数类型是jobject instance
  2. 而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[]

如果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

你可能感兴趣的:(Android,Studio,Java,Android,jni)