JNI开发基础篇:C语言调用Java中的方法

Life always has many things to bring you down.But what can really bring you down is just yourself.

这一篇来记录如何在C中实现java方法的调用(最基本的原理:JAVA reflect 反射)

在JNI 中,java调用C的流程步骤:(个人的理解整理,如有误,请包容也请指正)

 /**
    java  --->   C

    java--->System.loadLibrary()---->native method---->C method

     */

C调用Java

/**

    C ---->  Java

    java--->System.loadLibrary()---->native method ----> c method ----> java method

     */

所以,C语言调用java的时候,从android调用角度来讲:

1:创建Java native method.

2:创建相应的JNI c method 

3:java 中调用 native method,然后native method 调用C method 

4:C调用java方法

接下来,我完成一个实例。要求是:

1:创建一个带有整型参数的native方法,并创建C将要调用的java方法

2:然后调用C方法完成硬件中wife的关闭的操作或者启动

3:最后C中完成硬件启动后

4:最后调用java方法来输出成功还是失败。 

解释:在C语言中不像java中那样有Boolean和String类型,所以在自己创建方法的时候,也要符合C中的基本语法。(这个需要去另外学习的部分了。也是想要成为终端开发者的必经之路了。也会慢慢的靠近人工智能的发展方向。纯属个人理解,努力探索永不止步!)

OK,Let’s beginning

1:创建一个带有整型参数的native方法,并创建C将要调用的java方法

/*对于wifi的开启或关闭的button*/
public native String WifiButton();

/*C将调用的java方法*/
 public void returnTypeWife(int type){

        Log.i("com.wedfrend.jni.JNI", "returnTypeWife: "+type);

        if(type == 0){
             Log.i(TAG, "returnTypeWife: 开启");
        }
          Log.i(TAG, "returnTypeWife: 关闭");
    }

2: 完成C中操作硬件功能,然后调用Java方法,最后输出提示等

 /**
    C中方法声明,这个在前面一篇说过
    *@param env   jni 提供万能的二级指针
    *@param instance  可以理解为我们java中的上下文参数
    */
JNIEXPORT void JNICALL
Java_ltd_xiamenwelivetechnologyco_myapplication_JNI_WifiButton(JNIEnv * env, jobject instance) {

   /**
   1:现在首先完成C语言中处理硬件的方法

   由于具体操控硬件的相关代码复杂,而我目前并没有完全掌握。

   所以这设置一个int 值,0 表示wifi开启 ,1表示wifi关闭。

   (后续的进阶篇会详细的来聊聊硬件)
   */   

  int type = 0;//表示开启wifi

/*2:C语言中调用Java中的方法,那么具体的实现原理为Java的反射机制*/

/*
    2.1:获得想要调用Java方法的类

    jclass      (*FindClass)(JNIEnv*, const char*);

    @param JNIEnv

    @param const char* 调用的具体类的路径 这里需要将java中的 "."改为"/",表示具体路径下的文件
*/

jclass jClass = (*env)->FindClass(env,"com/wedfrend/jni/JNI");

/*
   2.2:2:实例化该类

   jobject     (*AllocObject)(JNIEnv*, jclass);

   @param jclass 2.1得到的jclass
*/
jobject jObject = (*env)->AllocObject(env,jClass);

/*
  2.3:得到你将要调用java类中的方法

 jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); 

  @param const char* 第一个为调用java的方法名。
                     比如说要调用java中的returnTypeWife方法

  @param const char* 第二个表示该方法的函数签名,对于这个函数签名。


  穿插说明:函数签名。如何获得一个java函数的函数签名。

  1:将自己的项目rebuild

  2:成功后,在项目中 `app/build/intermediates/classes/debug`目录下生成相应的class文件

  3:使用cmd命令进入相应的文件目录下

  4:执行命令  javap -s 全类名。如下是我的命令
    F:\"编译项目成功之后的文件路径"\debug> javap -s com.wedfrend.jni.JNI

  5:会显示这样的结果 

      Compiled from "JNI.java"
      public class com.wedfrend.jni.JNI {
      public com.wedfrend.jni.JNI();
       descriptor: ()V   //这个便是签名

     public native java.lang.String WifiButton();
     descriptor: ()Ljava/lang/String;



    public void returnTypeWife(int);
    descriptor: (I)I
}

 那么我的调用那个方法,用那个的签名,比如我们现在要用的是  

 public int returnTypeWife(int);
    descriptor: (I)I

那么签名自然是 (I)I
*/

jmethodID jMethodId = (*env)->GetMethodID(env,jClass,"returnTypeWife","(I)I");

/**
2.4:方法调用
void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);

相应的参数在上面已经得到,那么最后的可变参数为我们方法中需要传入的参数
*/
(**env).CallIntMethod(env,jObject,jMethodId,type);

//OK,It's done!

 LOGD("C called the java method !");

下面编译执行,看看结果:

JNI开发基础篇:C语言调用Java中的方法_第1张图片

点击按钮后可以获取相应的输出:

这里写图片描述

补充内容

1:C语言调用java中的static方法

以上便是C调用java中的方法。那么C中对java中的静态方法的调用,相比上面简单一点:

// TODO
    //1:获得想要调用Java方法的类
    // jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jClass = (*env)->FindClass(env,"xx/xxx/JNI");

    //3:获取我们想调用的方法 GetStaticMethodID
    //jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
    jmethodID jMethodId = (*env)->GetStaticMethodID(env,jClass,"AlarmClock","()V");

    // 调用静态的方法 CallStaticVoidMethod
    //void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
    (*env)->CallStaticVoidMethod(env,jClass,jMethodId);

只是将相应的方法改为调用静态方法即可。

2:C语言处理后调用java方法改变UI界面

我们一直没有使用生成方法中的一个参数:jobject instance

改变UI界面的时候,我们是需要在Activity中执行操作,那么相应的方法,是需要放在具体的Activity中。

所以在需要修改的UI的Activity中,需要定义native方法,并定义C要掉用的方法。

之后在C中进行调用:

 //1:获得想要调用Java方法的类
    // jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jClass = (*env)->FindClass(env,"com/wedfrend/jni/MainActivity");

    //3:获取我们想调用的方法
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    jmethodID jMethodId = (*env)->GetMethodID(env,jClass,"setUi","()V");

    //4: 方法调用
    /*
    这里的jobject我们直接使用方法中的带有参数  instance 便可
    */
    // jboolean    (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    (**env).CallVoidMethod(env,instance,jMethodId);

问题总结:

Q1:在普通调用的时候,是否也可以使用方法中的自带参数 jobject,从而省略掉在C中实例化类。

A:可以这样使用。

Q2:需要通过C语言调用java方法的时候,需要改变UI状态,我的调用类写在Activity中,但是native方法与其他的类写在同一个文件。

A:目前我自记得试验是不可行的,如果有人成功,请指教。

JNI开发基础篇:C语言调用Java中的方法_第2张图片

Q3:在C中获取MethodId的时候,对于方法签名,rebuild中是debug,那么在正式签名的时候,两者之间的签名结果是一样的吗?


A:1:在build.gradle中,我们设置一个release版本的keystore.android。并配置release版本,如下:

JNI开发基础篇:C语言调用Java中的方法_第3张图片

2:确认是否debug和release中的签名秘钥不同,执行如图所示的步骤

JNI开发基础篇:C语言调用Java中的方法_第4张图片

3:查看你的秘钥,如图:

JNI开发基础篇:C语言调用Java中的方法_第5张图片

4:所以目前的debug与release的keystore不同,MD5加密也不同,所以现在执行如下:

JNI开发基础篇:C语言调用Java中的方法_第6张图片

此时 项目中会生成两个编译版本,一个debug,一个release,如图:

JNI开发基础篇:C语言调用Java中的方法_第7张图片

然后我们按照查看签名的方式,在相应目录下执行`javap -s 全类名`

如图为Debug下的JNI.class

JNI开发基础篇:C语言调用Java中的方法_第8张图片

下图为Release下的JNI.class

JNI开发基础篇:C语言调用Java中的方法_第9张图片

所以可以看到,对于编译版本的keystore改变的情况下,对class中文件的方法签名没有影响。

以上的问题也是我在实践中跌跌撞撞,整理和实践,希望对大家有用。目前的基础入门篇告一段落。

在后面的延伸阶段中,会直接结合复杂的逻辑进行使用。由于工作的原因,工作处于FrameWork层,所以深入在后续的学习中持续更新。敬请期待

你可能感兴趣的:(android,进阶之路)