Android中JNI的使用
作者:小明
文章目录
一.前景提要
AS版本:Android Studio Chipmunk | 2021.2.1 Patch 1
JDK版本:1.8
SDK版本:32
二.JNI是用来做什么的?
1.JNI介绍
JNI (Java Native Interface,Java本地接口)是一种编程框架,使得Java虚拟机中的Java程序可以调用本地应用/或库,也可以被其他程序调用。 本地程序一般是用其它语言(C、C++或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序。
2.JNI的应用场景
- 平台相关的功能,通常是为了追求性能,需要对Java虚拟机进行扩展,需要使用Native的实现。
- Native的实现是已经存在的功能,使用JNI协议,方便使用已有的功能,不需要重新的Java实现。
- Java的.class文件安全性较差,增加安全性,将重要的逻辑在Native代码中实现。
3.JNI与NDK的关系
NDK可以为我们生成了C/C++的库,JNI是java和C/C++沟通的接口,两者与android没有半毛钱关系,只因为安卓是java程序语言开发,然后通过JNI又能与C/C++沟通,所以我们可以使用NDK+JNI来实现“Java+C”的开发方式。
用C语言生成一个库文件,在java中调用这个库文件的函数。JNI的过程比较复杂,生成.so需要大量操作,而NDK就是简化了这个过程。
JNI与NDK谁先出现:JNI先出现
2份JNI:jdk里一份,NDK里一份
三.AS创建JNI工程
1.选择模板
2.命名
3.C++Standard
四.JNI的基本使用
1.使用步骤
JNi的使用步骤:
a) java声明native函数
b) jni实现对应的c函数
c) 编译生成so库
d) java 加载so库,并调用native函数
2.语法
JNI数据类型
1.基本数据类型
JNI类型 Java类型
jboolean — boolean
jbyte — byte
jchar — char
jshort — short
jint — int
jlong — long
jfloat — float
jdouble — double
void — void
2.引用数据类型
JNI类型 — Java类型
jobject — Object
jclass — Class
jstring — String
jobjectArray — Object[]
jbooleanArray — boolean[]
jbyteArray — char[]
jshortArray — short[]
jintArray — int[]
jlongArray — long[]
jfloatArray — float[]
jdoubleArray — double[]
jthrowable — Throwable
语句
1.extern ”c“ 为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
JNIEnv 实际上就是提供了一些JNI系统函数(核心) JNINativeInterface的引用别名
JNINativeInterface C语言的结构体 300多个函数 采用c的编译方式
2.JNIEXPORT 标记为外部可调用
3.JNICALL 规则:约束函数入栈顺序,和堆栈内存的清理规则
5.包名 _ 类名 _ 方法名 转义下划线1
6.jobject MainActivityThis 实例调用的 static jclass == MainActivityClass类调用的
7.调出提示操作 Clangd
settings->Other Settings -> Clangd ->code Completion ->disable clangd completion
打印
#include <android/log.h>
#define TAG “kang”
#define LOGD(…) __android_log_print(ANDROID_LOG_DEBUG, TAG, VA_ARGS);
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR, TAG, VA_ARGS);
#define LOGI(…) __android_log_print(ANDROID_LOG_INFO, TAG, VA_ARGS);
LOGD(“jni打印(LOGD)”)
LOGE(“jni打印(LOGE)”)
LOGI(“jni打印(LOGI)”)
C与C++ 使用差异
c (*env)->xxx函数 JNIEnv *env 二级指针
c++ env->xxx 一级指针
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
(*env)->DeleteLocalRef(env,null);//C是没有对象的,想持有env环境,就必须传递出去
env->DeleteLocalRef(null)//C++是有对象的,本来就会持有this,所以不需要传
C语言不支持C++,反之可以
签名(Signature)
签名的作用是唯一标识。比如函数签名可以唯一识别函数,避免函数重载
Java类型签名
Boolean — Z
Byte — B
Char — C
Short — S
Long — J
Int — I
Float — F
Double — D
void — V
fully-qualified-class — Lfully-qualified-classt
ype[] ---- [type
method type (arg-type)ret-type
类的签名采用"L+包名+类名+;"的形式,将其中的.替换为/即可,比如java.lang.String,它的签名为Ljava/lang/String;
数组的签名就是[+类型签名,比如int数组,签名就是[I,多维数组就是[[I。
方法的签名为(参数类型签名)+返回值类型签名,例如:boolean fun1(int a,double b,int[] c),其中参数类型的签名为ID[I,返回值类型的签名为Z,所以这个方法的签名就是(ID[I)Z。
jfieldID ageFid = env->GetFieldID(mainActivityCls,"age","I");
jfieldID nameFid=env->GetFieldID(mainActivityCls, "name", "Ljava/lang/String;");
jmethodID addMid=env->GetMethodID(mainActivityCls, "add", "(II)I");
jmethodID showStringMid = env->GetMethodID(mainActivityCls,"showString","(Ljava/lang/String;I)Ljava/lang/String;");
数据类型的转换
1.调用方法时 (callxxxMethod SetXxxField) 引用类型=====全部命名为Object
2.String 要转为jstring int不需要转 double不需要转
//String必须要转jstring
jstring value =env->NewStringUTF("修改为zzz");
env->SetObjectField(thiz, nameFid,value);
//int不需要转
env->SetIntField(thiz,ageFid,12)
native与java交互操作
1.关于Java操作native:直接定义native方法,实现,调用
//定义:
public native void changeAge();
2.native操作Java
通过JNIEnv调用方法大致可以分为以下两步:
a、获取到对象的class,并且通过class获取成员属性
b、通过成员属性设置获取对应的值或者调用对应的方法
获取类的两种方式
//1
jclass mainActivityCls=env->FindClass("com/zmw/jnitest/MainActivity");
//2
jclass mainActivityCls=env->GetObjectClass(thiz);
获取属性,修改
//获取属性的fieldId
jfieldID ageFid = env->GetFieldID(mainActivityCls,"age","I");
jfieldID nameFid=env->GetFieldID(mainActivityCls, "name", "Ljava/lang/String;");
//获取属性值
jint age = env->GetIntField(mainActivityThis,ageFid);
jstring name = (jstring)env->GetObjectField(thiz,nameFid);//此处有编码转换问题未解决
//修改属性值
env->SetIntField(mainActivityThis, ageFid , 333);
env->SetObjectField(thiz, nameFid,value);
获取方法并调用。以方法ID为参数通过Call"Type"Method类函数调用实际的实例方法
//c++
jmethodID addMid=env->GetMethodID(mainActivityCls, "add", "(II)I");
int result=env->CallIntMethod(mainActivityThis, addMid, 1, 1);
//java
public int add(int number1,int number2){
System.out.println("c居然调用了我");
return number1+number2+90;
}
//************************************************************************************************************
//C++
jmethodID showStringMid = env->GetMethodID(mainActivityCls,"showString","(Ljava/lang/String;I)Ljava/lang/String;");
jstring value=env->NewStringUTF("李元霸");
jstring resultStr=(jstring)env->CallObjectMethod(mainActivityThis,showStringMid,value,9527);
const char * resultCstr = env->GetStringUTFChars(resultStr,NULL);
//Java方法
public String showString(String str,int value){
System.out.println("c居然调用了我 showString str:"+str+"value:"+value);
return "["+str+"]";
}
静态方法和静态属性操作
用CallStatic"Type"Field类函数调用静态方法,例如:
jstring staticMethodResult = env->CallStaticStringMethod(clazz,staticMethodId);
用GetStatic"Type"Field函数获得静态域。例如:
jstring staticField = (*env)->GetStaticObjectField(env,clazz,staticFieldId);
env->SetStaticIntField(clazz,ageFid,29)
五.总结
参考资料: