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 !");
下面编译执行,看看结果:
点击按钮后可以获取相应的输出:
补充内容
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:目前我自记得试验是不可行的,如果有人成功,请指教。
Q3:在C中获取MethodId的时候,对于方法签名,rebuild中是debug,那么在正式签名的时候,两者之间的签名结果是一样的吗?
A:1:在build.gradle中,我们设置一个release版本的keystore.android。并配置release版本,如下:
2:确认是否debug和release中的签名秘钥不同,执行如图所示的步骤
3:查看你的秘钥,如图:
4:所以目前的debug与release的keystore不同,MD5加密也不同,所以现在执行如下:
此时 项目中会生成两个编译版本,一个debug,一个release,如图:
然后我们按照查看签名的方式,在相应目录下执行`javap -s 全类名`
如图为Debug下的JNI.class
下图为Release下的JNI.class
所以可以看到,对于编译版本的keystore改变的情况下,对class中文件的方法签名没有影响。
以上的问题也是我在实践中跌跌撞撞,整理和实践,希望对大家有用。目前的基础入门篇告一段落。
在后面的延伸阶段中,会直接结合复杂的逻辑进行使用。由于工作的原因,工作处于FrameWork层,所以深入在后续的学习中持续更新。敬请期待