JNI是Java Native Interface的缩写,意思是Java的本地接口,这个本地接口主要指Java可以通过本地接口去和其他的编程语言通信,有时在开发某个功能时想使用之前的技术积累或封装好的模块,但不幸的是之前不是用Java开发的,那对于此中情况该如何处理呢?对于经过时间验证的可靠程序不可能轻易重写和修改,所以就需要JNI作为程序的中转枢纽;
既然Jni是Java和其他语言的沟通桥梁,那么它既必须有一套基础协议作为与Java代码沟通的基础,这个基础就是类型的映射和签名,类型映射就是在Java类型和Jni中的类型建立一一对应关系,从而实现二者的类型可读性和唯一性,签名指Java中类型、方法、属性等在Java中的展现形式,根据最终的签名查找方法的对应关系;
public static native String action(short s , int i, long l, float f, double d, char c,
boolean z, byte b, String str, Object obj, ArrayList<String> array,
int[] arr, Action action);
//生成的Jni对应的代码
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_action
(JNIEnv *, jclass, jshort, jint, jlong, jfloat, jdouble, jchar, jboolean, jbyte, jstring, jobject, jobject, jintArray, jobject);
//native
public native void test();
//jni
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_test (JNIEnv *, jobject);
上面是Java代码中声明的test()转换后的Jni方法,此方法名称在编译javah文件时生成,在实现的C文件中重写并实现即可,方法的命名规则:
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest
(JNIEnv *env, jobject cls, jstring j_str) {
}
方法签名:(Ljava/lang/Object,Ljava/lang/String)Lava/lang/String
静态注册JNI方法很简单,我们在Java中声明native方法后,会执行Java命令编译和生成Jni方法:
javac ***
javah ***
在执行javah的命令后,系统会在之间文件处创建.h文件,当我们在Java层调用native方法时,系统就会根据JNI方法命名规则,按照JNI方法名寻找对应的方法,在找到JNI方法后保存JNI指针建立方法间的联系,下次调用时直接使用函数指针就可以,不过静态注册在初次调用时需要查找再建立关联,影响效率,与静态注册对应的就是动态注册,不需要编译和查找关联;
JNIEXPORT jstring JNICALL native_method(JNIEnv *env, jobject) {
return env->NewStringUTF("Register method in Jni");
};
static JNINativeMethod methods[] = {
//参数:1、Java声明的native方法名;2、方法签名;3、c中实现的方法名
{"method", "()Ljava/lang/String;", (void *) native_method},
};
// 声明动态注册对应的Java类的路径
static const char *const PACKAGE_NAME = "com/alex/kotlin/jni/JniTest";
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
JNIEnv *env; //获取JNIEnv
if (JNI_OK != vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6)) {
return JNI_ERR;
}
jclass jclass1 = env->FindClass(PACKAGE_NAME); //根据类名获取jclass
if (jclass1 == NULL) {
return JNI_ERR;
}
jclassGlobal = static_cast<jclass>(env->NewWeakGlobalRef(jclass1)); //创建全局缓存jclass
env->DeleteLocalRef(jclass1); //释放局部变量
if (JNI_OK != env->RegisterNatives(jclassGlobal, method, 1)) { //注册方法
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
在创建的C++文件中重写Jni.h中的JNI_OnLoad()方法,在JNI_OnLoad中首先根据报名获取Java类的jclass对象,然后全局缓存jclass对象,最后调用RegisterNatives()方法传入jclass和关系数组实现方法的注册
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
env->UnregisterNatives(jclassGlobal); //解除注册
env->DeleteGlobalRef(jclassGlobal); //释放全局变量
}
在介绍完JNI基础知识后,一起来学习下JNI在开发中的基本使用,也就是Jni的基本语法,其实在上面动态注册时已经使用到了一些,这里根据使用频率介绍下最常用的方法;
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest
(JNIEnv *env, jobject cls, jstring j_str) {
const char *c_str = NULL;
char buff[128] = {};
jboolean copy;
c_str = env->GetStringUTFChars(j_str, ©); // 字符串访问
if(c_str == NULL){
return NULL;
}
sprintf(buff, "Jni %s", c_str); //字符串输出
env->ReleaseStringUTFChars(j_str, c_str); //字符串释放
return env->NewStringUTF(buff); // 字符串创建
}
env->NewStringUTF(“this is string !");
const jchar *c_str = NULL;
c_str = env->GetStringChars(j_str, ©);
if (c_str == NULL) {
return NULL;
}
sprintf(buff,"Jni %s",c_str); //将字符串缓存到buff中
env->ReleaseStringChars(j_str, c_str);
jsize lenUtf = env->GetStringLength(j_str);
jsize lenUtf = env->GetStringUTFLength(j_str);
const jchar *c_str = NULL;
char buff[128] = {};
jboolean copy;
c_str = env->GetStringCritical(j_str, ©); //读取字符串
if (c_str == NULL) {
return NULL;
}
sprintf(buff,"Jni %s",c_str);
env->ReleaseStringCritical(j_str, c_str); //释放字符串
return env->NewStringUTF(buff);
JNIEXPORT jstring JNICALL Java_com_alex_kotlin_jni_JniTest_setTest(JNIEnv *env, jobject cls, jstring j_str) {
char buff[128] = {};
jsize lenUtf = env->GetStringUTFLength(j_str); //读取字符串长度
env->GetStringUTFRegion(j_str, 0, 4, buff); //截取字符串0~4缓存到buff中
return env->NewStringUTF(buff); //创建字符串
}
//输入 "From Native !” ,输出结果:“From”
Get<PrimitiveType>ArrayElements(ArrayType array, jboolean *isCopy)
* 0:将 elems 内容回写到 Java 数组并释放 elems 占用的空间,回写的意思是会去修改原Java中的数组
* JNI_COMMIT:将 elems 内容回写到 Java 数组,但不释放 elems 的空间;
* JNI_ABORT:不回写 elems 内容到 Java 数组,释放 elems 的空间。
jint *array ;
jboolean jboolean1;
array = env->GetIntArrayElements(jintArray1, &jboolean1); //获取集合
if (array == NULL){
return;
}
array[2] = 5; //修改集合中的参数
env->ReleaseIntArrayElements(jintArray1,array,0); //释放array集合并写入Java集合
//在Java中调用Jni
int[] number = new int[]{1,2,3,4,5};
Log.e("Before Jni=====",number[2] + "");
test.getAction(number); //调用Jni方法
Log.e("After Jni=====",number[2] + "");
2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/Before Jni=====: 3
2019-04-29 22:09:07.698 15989-15989/com.alex.kotlin.jni E/After Jni=====: 5 //3改变程5
//使用JNI_ABORT
2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/Before Jni=====: 3
2019-04-29 22:11:55.485 16108-16108/com.alex.kotlin.jni E/After Jni=====: 3 //不变
由上面的运行结果知道,在ReleaseIntArrayElements()传入0时,Java层传入的数组在执行方法后被改变,从而验证了将数据回写到Java的结论,在使用JNI_ABORT时,Java中数据并未发生改变;
jbyte* data = env->GetByteArrayElements(javaArray, NULL);
if (data != NULL) {
memcpy(buffer, data, len);
env->ReleaseByteArrayElements(javaArray, data, JNI_ABORT);
}
env->GetByteArrayRegion(javaArray, 0, len, buffer);
public class Action {
public static void actionStatic(){
Log.e("=======","Static method from action in Java ") ;
}
}
//Jni方法
JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test
(JNIEnv *env, jobject) {
jclass cls = NULL;
jmethodID method_Id = NULL;
cls = env->FindClass("com/alex/kotlin/jni/Action”); //查找Action类文件
method_Id = env->GetStaticMethodID(cls, "actionStatic", "()V”); //根据类、方法名、参数条件获取MethodId
env->CallStaticVoidMethod(cls,method_Id); //调用静态方法
env->DeleteGlobalRef(cls); //释放class
}
2019-04-27 17:25:27.580 10961-10961/com.alex.kotlin.jni E/=======: Static method from action in Java
public class Action {
public void action(){
Log.e("=======","Method from action in Java ") ;
}
}
//Jni中代码
jclass clss = NULL;
jmethodID method_Id = NULL;
jmethodID construct_Id = NULL;
jobject obj = NULL;
clss = env->FindClass("com/alex/kotlin/jni/Action”); //查找Action类
construct_Id = env->GetMethodID(clss, "" , "()V”); //获取构造函数方法的Id
obj = env->NewObject(clss, construct_Id); //创建构造Action实例
method_Id = env->GetMethodID(clss, "action", "()V”); //创建action()方法的Id
env->CallVoidMethod(obj,method_Id); //调用action()方法
2019-04-27 17:42:31.774 11880-11880/com.alex.kotlin.jni E/=======: Method from action in Java
public class Action {
private static int number = 100;
public int getNumber() {
return number;
}
public void setNumber(int number) {
Action.number = number;
}
}
public native void getStaticInstance();
cls = env->FindClass("com/alex/kotlin/jni/Action”); //获取功能类
field_id = env->GetStaticFieldID(cls, "number", "I”); //获取静态变量Id
number = env->GetStaticIntField(cls, field_id); //从类中获取静态变量
jint num = 555;
env->SetStaticIntField(cls, field_id, num); //为静态变量赋值
Action action = new Action();
action.setMessage("Message in Java");
action.setNumber(123);
Log.e("Before======", action.getNumber() + "");
test.getStaticInstance();
Log.e("After Jni======", action.getNumber() + "");
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: 123
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: 555
public class Action {
private String message = null;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
public native void getInstance(Action action);
cls = env->GetObjectClass(obj); //从参数object中获取class
field_id = env->GetFieldID(cls, "message", "Ljava/lang/String;”); //根据参数名和类型获取Field_Id
str = static_cast<jstring>(env->GetObjectField(obj, field_id)); //根据field_Id从obj中获取实例
new_str = env->NewStringUTF("Message in Jni”); //创建新的String
env->SetObjectField(obj, field_id, new_str); //设置Obj中的数值
Log.e("Before======", action.getMessage() + "");
test.getInstance(action);
Log.e("After Jni======", action.getMessage() + "");
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/Before======: Message in Java
2019-04-27 21:32:51.592 15022-15022/com.alex.kotlin.jni E/After Jni======: Message in Jni
public class Action {
public void action(){
Log.e("==========","Action in Action");
}
}
public class ChildAction extends Action {
@Override
public void action() {
Log.e("==========","Action in ChildAction");
}
}
cls = env->FindClass("com.alex.kotlin.jni.Action”); //1、
jmethodID1 = env->GetMethodID(cls, "" , "()V”); //2、
jobject1 = env->NewObject(cls, jmethodID1); //3、
cls_parent = env->FindClass("com.alex.kotlin.jni.ChildAction”); //4、
jmethodID2 = env->GetMethodID(cls_parent, "action()", "()V”); //5、
env->CallNonvirtualVoidMethod(jobject1, cls_parent, jmethodID2); //6、
static {
System.loadLibrary("AccessCache");
initIDs();
}
//Java 抛出异常代码
public void actionException() throws Exception {
throw new Exception("Java 抛出异常");
}
//C文件中调用
jmethodID1 = env->GetMethodID(cls, "actionException", "()V");
env->CallVoidMethod(obj, jmethodID1);
if (env->ExceptionCheck()) { //检查并抛出异常
env->ExceptionDescribe();
env->ExceptionClear();
env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 抛出异常");
}
//运行结果
2019-04-29 19:12:54.919 32350-32350/com.alex.kotlin.jni W/System.err: java.lang.Exception: Java 抛出异常
2019-04-29 19:12:54.920 32350-32350/com.alex.kotlin.jni E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.alex.kotlin.jni, PID: 32350
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.alex.kotlin.jni/com.alex.kotlin.jni.MainActivity}: java.lang.Exception: Jni 抛出异常
env->CallVoidMethod(obj, jmethodID1);
if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
env->ExceptionClear();
env->ThrowNew(env->FindClass("java/lang/Exception"), "Jni 抛出异常");
}
if ((*env)->ExceptionCheck(env)) { /* 异常检查 */
(*env)->ReleaseStringChars(env, jstr, cstr); // 发生异常后释放前面所分配的内存
return;
}
对于我们正常开发来说,直接使用Jni的场景很少,一般Jni的方法都会封装在So库中供Java层调用,现在就根据上面的Jni知识利用AS生成一个So库。
ndk.dir=/Users/wuliangliang/Library/Android/sdk/ndk-bundle
cmake_minimum_required(VERSION 3.4.1) :配置So库的最小版本
add_library( //每个C文件具备一个add_library
jni-lib // 配置So库名称
SHARED //设置So库设置为Shared共享库
test.cpp) // 源C文件的相对路径
find_library( //,
log-lib
log)
target_link_libraries(
jni-lib // 指定目标库
${log-lib}) // 将目标库连接到NDK的日志库
上面配置中:
android{
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt” //配置CMakeList的文件路径
}
}
sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } } //配置Jni文件输出路径
}
public class JniTest {
static {
System.loadLibrary("jni-lib”); //引入so库
}
public static native String test(); //配置native方法
}
JNIEXPORT void JNICALL Java_com_alex_kotlin_jni_JniTest_test
(JNIEnv *env, jobject) {
jclass cls = NULL;
jmethodID method_Id = NULL;
cls = env->FindClass("com/alex/kotlin/jni/Action");
if (cls == NULL) {
return;
}
method_Id = env->GetStaticMethodID(cls, "test", "()V");
env->CallStaticVoidMethod(cls, method_Id);
env->DeleteGlobalRef(cls);
}
这里调用FindClass()根据类名获取Class对象,然后使用全局变量保存Class对象,然后查找并调用actionStatic()
JniTest test = new JniTest();
tv.setText(test.test());
关于Jni的基本知识点和用法基本介绍完了,在一般的开发中可能使用的不多,但想做进一步的功能或优化时就经常会使用到,所以Jni也成为Android高级开发这必备基础知识,希望此篇文章的总结对需要的同学有所帮助;