概述
JNI开发主要是java层与c层的事件交互与数据互通,所有接口均在jni.h中可以找到,头文件可以在ndk开发包中找到,如:platforms/android-21/arch-arm64/usr/include/jni.h,在引用的时候可以直接#include<jni.h>.
java层代码特殊关键字native
对于jni接口,java层代码的接口必须添加关键字native,如下HelloWorld.java:
public class HelloWorld{
static {
System.loadLibrary("helloWorld"); //加载helloWorld.so
//System.load("/system/lib/helloWorld.so"); //绝对路径加载
}
//这个接口测试c反调java代码 c/c++ -> java
static void msgFromJNI(String str){
Log.d("test", "this message is from jni : " + str);
}
//给外部使用的接口
public void show(){
print();
}
//jni接口 java-> c/c++
private native void print();
}
C/C++层代码逻辑
注意c和c++在使用方式上基本一样,但是稍微有点区别是,很多接口参数上c++在使用env时,在参数上是省略了本身的,例如获取class对象:
c代码
(*env)->FindClass(env,className)
c++代码:
env->FindClass(className);
但是逻辑和使用基本一致不受影响.详细可以查看jni.h头文件的定义就很明了了.后续例子代码均采用c++编译,helloWorld.cpp
通过jni规定的接口名称进行映射java接口(我称作为静态接口)
#include <jni.h>
/*
* Class: com.example.HelloWorld
* Method: print
* Signature: ()V
*/
JNIEXPORT jboolean JNICALL Java_com_example_HelloWorld_print
(JNIEnv *env, jclass obj){
printf("Hello world\n");
}
上面这种方式就可以直接使用了,当编译成helloWorld.so之后,由HelloWorls.java去静态加载,之后就可以正常使用print 本地c++接口了.
动态注册jni接口
这种方式java部分代码不用改变.c/c++代码中不用按照特定的java代码中的包名_类名_接口名等规定样式编写接口,而且可以动态的注册和取消接口注册
#include <jni.h>
typedef struct
{
JavaVM *VM; //java虚拟机实例
jclass helloWorldCls; //用来存储java中HelloWorld对象
jmethodID msgFromJNI; //存储java中msgFromJNI方法id,调用接口时会用到
} helloWorld_t;
//这个是要注册的native接口,这里目前只注册了print.
//需要注册多个时,继续添加即可
/*
*第一参数:java上定义的jni接口名称
*第二个参数:方法标签,表示接口的参数和返回值()内部的表示参数,()外面的是返回值
*Ljava/lang/Object;代表的是字符串,V代表是void
*第三个参数函数指针,也就是对应调用的c函数
*/
static const JNINativeMethod gMethods[] = {
{"print", "(Ljava/lang/Object;)V", (void*)__print},
};
static helloWorld_t stHelloWorld;
static const char * HelloWorldClassName = "com.example.HelloWorld"
void regist_helloWorld(JNIEnv* env){
if(!env){
return JNI_FALSE;
}
//存储一下jvm
if(JNI_OK != env->GetJavaVM(&stHelloWorld.VM)){
return JNI_FALSE;
}
//获取class对象,你可以理解为java反射机制,通过包名类名获取classz.
stHelloWorld.helloWorldCls = env->FindClass(HelloWorldClassName);
//由于我们后续会一直使用这个class,这里需要保存为global全局对象,以便本次线程结束之后无法再次使用.
stHelloWorld.helloWorldCls = (jclass)env->NewGlobalRef(stHelloWorld.helloWorldCls);
if(NULL ==stHelloWorld.helloWorldCls)
{
return JNI_FAILE;
}
//获取msgFromJNI方法id
stHelloWorld.msgFromJNI = env->GetStaticMethodID(stHelloWorld.helloWorldCls "msgFromJNI", "(Ljava/lang/Object;)V");
//注册native接口,第一个参数注册的类,第二个参数:注册的方法列表,第三个参数方法数
if (env->RegisterNatives(stHelloWorld.helloWorldCls , gMethods, sizeof(gMethods)/sizeof(JNINativeMethod)) < 0)
{
return JNI_FALSE;
}
//以上完成了函数注册还有存储了一个java方法的id(msgFronJNI).
}
/*
*so被加载时被系统回调的第一个接口,我们可以在这个接口中通过jni.h的接口去注册本地方法
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
jint result = -1;
JNIEnv* env = NULL;
if((vm)->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
return result;
regist_helloWorld(env);
printf("--------JNI_OnLoad-----SUCCESS");
return JNI_VERSION_1_4; //这里很重要,必须返回版本,否则加载会失败。
}
static void JNICALL __print(JNIEnv *env, jobject jobj, jstring userid)
{
bool bRet = false;
const char* p8Userid = env->GetStringUTFChars(userid, 0);
if(!p8Userid){
return bRet;
}
printf("%s\n", p8Userid);
//这里我们主动释放内存
env->ReleaseStringUTFChars(userid, p8Userid);
//这里我们直接测试反调用HelloWorld.java里面的msgFromJNI方法.
char * testString = "I'm c string";
jstring jsr = env->NewStringUTF(testString);
//调用java中的msgFromJNI方法
env->CallStaticVoidMethod(stHelloWorld.helloWorldCls, stHelloWorld.msgFromJNI , jstr);
//使用完jstr之后,我们进行回收
env->ReleaseStringUTFChars(jstr, testString);
//这样我们简单完成了java->c/c++, 以及c/c++->java的流程了.
return bRet;
}