JNI可以做什么
JNI是Java平台的一个非常有用的特性,它让同时使用JAVA和C/C++协作开发应用程序成为可能。
JNI (Java Native Interface ,Java本地接口) 是一种编程框架,可以使Java虚拟机或Java程序调用本地应用或库。
你的第一个JNI程序
新建一个支持C++的项目
添加如下代码:
static {
System.loadLibrary("native-lib");
}
//传递基本类型
public native void testArray(String[] strs, int[] ints);
然后在native-lib.cpp中编写代码
对于不熟悉JNI的人可以使用快捷键自动生成C++的代码模版
ALT + ENTER/OPTION + ENTER(mac)
或者通过命令生成
javah -o [输出文件名] [全限定名]
会给你生成如下代码模版:
extern "C"
JNIEXPORT void JNICALL
Java_com_jnimode1_MainActivity_testArray(JNIEnv *env, jobject instance, jobjectArray strs,
jintArray ints_) {
}
这个相当easy了,多敲几遍就搞定了。下面来理解这些关键字的含义。
先看这一段代码:
Java_com_jnimode1_MainActivity_testArray
其实就是方法的全类名:
com.jnimode1.ManiActivity.testArray
最开始加了一个Java 表示是Java中的方法。
然后再看一下两个宏(JNIEXPORT JNICALL)是什么意思,这只是固定的写法,简单了解一下即可。
JNIEXPORT
Windows 中,定义为__declspec(dllexport)。因为Windows编译 dll 动态库规定,如果动态库中的函数要被外部调用,需要在函数声明中添加此标识,表示将该函数导出在外部可以调用。
在 Linux/Unix/Mac os/Android 这种 Like Unix系统中,定义为__attribute__ ((visibility ("default")))
GCC 有个visibility属性, 启用这个属性gcc -fvisibility=xx:
当-fvisibility=hidden时
动态库中的函数默认是被隐藏的即 hidden. 除非显示声明为__attribute__((visibility("default"))).
当-fvisibility=default时
动态库中的函数默认是可见的.除非显示声明为__attribute__((visibility("hidden"))).
JNICALL:
在类Unix中无定义,在Windows中定义为:_stdcall ,一种函数调用约定
类Unix系统中这两个宏可以省略不加。
JNIEnv:由Jvm传入与线程相关的变量。定义了JNI系统操作、Java交互等方法。
jobject:表示当前调用对象,即this,如果是静态的native方法,则获得jclass
至于 extern "C" 代码使用C++写的,要以C的方式编译.(PS:这是C语言的基础了)
我们先写一个简单的例子,C++ 获取Java传递的int 和 string,编码如下
JNIEXPORT jstring JNICALL Java_com_jni_ExampleUnitTest_test(JNIEnv *env,jobject o,jint i,jstring s){
//获得c字符串
//开闭内存x,拷贝Java字符串到x中 返回指向x的指针
//提供一个boolean(int) 指针,用于接收jvm传给我们到字符串是否是拷贝
const char *k = env->GetStringUTFChars(s, JNI_FALSE);//获得 C 字符串
printf("C 获得 Java传递过来的值:%d,%s",i,k);
char returnStr[100];
//格式化字符串
sprintf(returnStr, "C++ string:%d,%s",i,k);
//释放掉内存x
env->ReleaseStringUTFChars(s, k);
//返回Java字符串
return env->NewStringUTF(returnStr);
}
需要注意的地方:jstring
*const char k = env->GetStringUTFChars(s, JNI_FALSE); 需要将Java中的String转换为C的字符串,才能在C中打印以及使用。
因为转换的时候开辟了内存,不用就需要释放掉
env->ReleaseStringUTFChars(s, k);
至于jint可以直接使用。
在Java代码中,首先声明这个方法:
native String test(int i, String value);
然后直接调用即可:
String java = test(1, "java");
Run,可以看到C++中的日志输出:

但是这个日志是在android中是看不到的,那么能否在Android中看到这些log呢?
需要在C++代码中定义一个宏(宏其实就是一种文本替换)
同时要导入一个头文件
#include <android/log.h>
//log 宏定义
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "JNI_TEST", __VA_ARGS__);
这样使用即可
LOGE("获取String:%s", getStr)
C/C++ 获取Java中的数组
//注意如果用C++写到,需要在每个方法中加 extern "C"
extern "C"
//C/C++ 获取Java中的数组
JNIEXPORT jstring JNICALL Java_com_jni_ExampleUnitTest_array(JNIEnv *env,jobject instance,jobjectArray a_,jintArray b_){
//1 获得字符串数组
//获得数组的长度
int32_t str_length = env->GetArrayLength(a_);
printf("字符串数组长度:%d",str_length);
//获得字符串数组的数据
for (int i = 0; i < str_length; i++) {
//获取jstring
jstring str = static_cast<jstring>(env->GetObjectArrayElement(a_, i));
//将jstring 转换为c字符串,获得c字符串
const char *c_str = env->GetStringUTFChars(str, JNI_FALSE);
printf("字符串有:%s",c_str);
//使用完释放
env->ReleaseStringUTFChars(str, c_str);
}
//2 获取基本数据类型数组
//获取数组长度
int32_t int_length = env->GetArrayLength(b_);
printf("int 数组的长度:%d",int_length);
//获取jint
//true 进行拷贝一个新的数据(相当于新申请了一块内存空间);false 就是使用的Java的数组(地址)
jint *b = env->GetIntArrayElements(b_, JNI_FALSE);
for (int i = 0; i < int_length; i++) {
printf("int 数组有:%d",b[i]);
}
//使用完后释放
env->ReleaseIntArrayElements(b_, b, 0);
//返回Java字符串
return env->NewStringUTF("returnStr");
}
其实代码非常简单,需要注意几点:
- 遍历字符串数组的时候,由于JNI没有提供jstringArray,只有jobjectArray需要先将,转换为jstring,然后将jstirng --> char *,使用完之后要注意释放,int数组就相当简单了,将Java数组转换为jint * 指针。
- 在释放env->ReleaseIntArrayElements(b_, b, 0);
最后一个参数的意思:(大家可以改变试一下)
//0 刷新Java数组并释放 c/c++数组 如果在C++中改变数组的值 Java中数组的值也会改变;
//JNI_COMMIT 1 只刷新Java数组 如果在C++中改变数组的值 Java中数组的值也会改变
//JNI_ABORT 2 只释放并没有刷新Java数组 如果在C++中改变数组的值 Java中数组的值不会改变
C/C++反射Java
反射Java类的方法
C++调用Java类中的方法
Java 类
public class Helper {
private static final String TAG = "Helper";
int a = 10;
String s = "test";
public Helper() {
Log.e(TAG, "Helper: 构造方法被调用");
}
//C/C++ 反射创建的Java对象,调用Java方法
public void instanceModth(String a, int c, boolean b) {
Log.e(TAG, "instanceModth: a= " + a + " c= " + c + " b= " + b);
}
public static void staticModth(String a, int c, boolean b) {
Log.e(TAG, "instanceModth: a= " + a + " c= " + c + " b= " + b);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_jnimode1_MainActivity_invokeHelper(JNIEnv *env, jobject instance, jobject helper) {
jclass helperClass;
//判断传递的对象是否为空
if (helper != NULL) {
//获得Helper类
LOGE("helper对象不为空")
helperClass = env->GetObjectClass(helper);
} else {
LOGE("helper对象为空")
helperClass = env->FindClass("com/jnimode1/Helper");
}
//构造方法 基本数据类型的签名采用一系列大写字母来表示
jmethodID constructMethod = env->GetMethodID(helperClass, "<init>", "()V");
if (helper == NULL) {
//创建helper对象并调用构造方法
helper = env->NewObject(helperClass, constructMethod);
}
//调用普通方法
jmethodID instanceMethod = env->GetMethodID(helperClass, "instanceModth",
"(Ljava/lang/String;IZ)V");
LOGE("准备调用方法instanceModth")
//将字符串转换为Java的调用方式
jstring string = env->NewStringUTF("C++调用");
LOGE("开始调用instanceModth")
env->CallVoidMethod(helper, instanceMethod, string, 20, 0);
//调用静态方法
jmethodID staticMethod = env->GetStaticMethodID(helperClass, "staticModth",
"(Ljava/lang/String;IZ)V");
jstring staticString = env->NewStringUTF("C++调用静态方法");
env->CallStaticVoidMethod(helperClass, staticMethod, staticString, 30, 1);
env->DeleteLocalRef(helperClass);
env->DeleteLocalRef(staticString);
//主要需要释放对象及引用
env->DeleteLocalRef(helper);
env->DeleteLocalRef(string);
}
上述代码,可能稍微优点复杂,其实就是对API的调用。
这一段代码大家可能不理解是什么意思(Ljava/lang/String;IZ)V
,其实相当于一个签名,翻译成Java就是void (String,int,boolean).
Run,结果如下:

确实调用了Java类中的方法。
Java类型 | 签名 |
---|---|
boolean | Z |
short | S |
float | F |
byte | B |
int | I |
double | D |
char | C |
long | J |
void | V |
引用类型 | L + 全限定名 + ; |
数组 | [+类型签名 |
可以使用javap来获取反射方法时的签名
cd 进入 class所在的目录 执行: javap -s 全限定名,查看输出的 descriptor
反射Java类的属性
道理是一样的,熟悉相关的API,代码中都有注释。
C++ 来修改Java类属性的值,编码如下:
//反射Java中类的属性
extern "C"
JNIEXPORT void JNICALL
Java_com_jnimode1_MainActivity_fiedHelper(JNIEnv *env, jobject instance, jobject helper) {
jclass helperClass;
//判断传递的对象是否为空
if (helper != NULL) {
//获得Helper类
LOGE("helper对象不为空")
helperClass = env->GetObjectClass(helper);
} else {
LOGE("helper对象为空")
helperClass = env->FindClass("com/jnimode1/Helper");
}
jfieldID aId = env->GetFieldID(helperClass, "a", "I");
//获得属性值
jint a = env->GetIntField(helper, aId);
LOGE("a:%d", a);
//修改属性值
env->SetIntField(helper, aId, 100);
//获得属性值
jint a1 = env->GetIntField(helper, aId);
LOGE("a1:%d", a1);
jfieldID sId = env->GetFieldID(helperClass, "s", "Ljava/lang/String;");
//获取字符串的值
jstring s = static_cast<jstring>(env->GetObjectField(helper, sId));
const char *as = env->GetStringUTFChars(s, 0);
LOGE("获取s的值%s", as);
//不用之后释放
env->ReleaseStringUTFChars(s, as);
//修改字符串的值
jstring new_str = env->NewStringUTF("ssss");
env->SetObjectField(helper, sId, new_str);
//注意释放引用
env->DeleteLocalRef(new_str);
//获取字符串的值
jstring s1 = static_cast<jstring>(env->GetObjectField(helper, sId));
const char *as1 = env->GetStringUTFChars(s1, 0);
LOGE("修改后s的值%s", as1);
//不用之后释放
env->ReleaseStringUTFChars(s1, as1);
env->DeleteLocalRef(helperClass);
}
网友评论