概述
今天继续学习JNI,前几篇学习了一些基本的内容,今天我们全部实践一下,这篇文章仅作为笔记,以防以后忘记
JNI访问Java对象的成员
先来看下需要用到的Api
获取jclass
jclass GetObjectClass(JNIEnv *env, jobject obj);
- jobject:代表java的对象
- 函数返回一个class对象
获取jfieldID
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
- JNIEnv:JNIEnv指针
- jclass:class对象,可以通过
GetObjectClass
或者FindClass
获取 - name:class对象中某个变量的名字
- sig:变量的类型签名
- 返回一个class对象的变量,类型为jfieldID
获取变量的值
根据变量的类型,会有不同的函数来获取变量的值,函数的基本形式如下:
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
函数名 | 返回值类型 |
---|---|
GetBooleanField() | jboolean |
GetByteField() | jbyte |
GetCharField() | jchar |
GetShortField() | jshort |
GetIntField() | jint |
GetLongField() | jlong |
GetFloatField() | jfloat |
GetDoubleField() | jdouble |
GetObjectField() | jobject |
前八项是基本数据类型,第九项是获取所有引用数据类型
例如获取int变量,如下:
jint GetIntField(JNIEnv * env, jobject obj, jfieldID fieldID);
- jobject:代表一个java对象
- jfieldID:代表class的一个变量,通过
GetFieldID
获取 - 返回一个jint
设置变量的值
根据变量的类型,有不同的函数设置变量的值
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,
NativeType value);
函数名 | 参数类型 |
---|---|
SetBooleanField() | jboolean |
SetByteField() | jbyte |
SetCharField() | jchar |
SetShortField() | jshort |
SetIntField() | jint |
SetLongField() | jlong |
SetFloatField() | jfloat |
SetDoubleField() | jdouble |
SetObjectField() | jobject |
前八项对应基本数据类型,第九项java所有引用类型
例如,int的函数原型
void setIntField(JNIEnv * env, jobject obj, jfieldID fieldID, jint value);
- value:你要设置int的值,其他参数跟上方都一样,不在重复说
获取jmethodID
jmethodID GetMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig);
- name:获取class对象中方法的名字
- sig:获取方法的签名
- 返回值:如果存在这个方法返回jmethodID,如果不存在返回NULL
- 如果获取java的构造方法,const char *name的值为,参数const char *sig的值为void(V)
调用对象的方法
根据java方法返回值得不同,jni有不同的函数来调用java对象的方法,基本的形式有三种
NativeType Call<type>Method(JNIEnv *env, jobject obj,
jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj,
jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj,
jmethodID methodID, va_list args);
这三个的方法的区别在与传递参数的不同,最常用的是第一个,我们只讲第一个
如果方法返回的是int,那么函数原型为
jint CallIntMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
如果方法的返回值是引用数据类型
jobject CallObjectMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
如果java的返回值类型为void
void CallVoidMethod(JNIEnv * env, jobject obj, jmethodID methodID, ...);
- jmethodID:对象的方法,可以通过
GetMethodID
获取 - …:可变参数,你需要传递给java方法的参数
下面我们看下实现的完整代码
首先定义一个java类,里面有个native方法,当创建对象是会调用
public class Student {
public String name = "小红";
static {
System.loadLibrary("studentlib");
}
native void native_init();
public Student() {
native_init();
}
public void print() {
Log.d("mmm", name);
}
}
在jni注册这个java的native方法,然后用C实现它
#include <jni.h>
#include <iostream>
#include <android/log.h>
static void native_init(JNIEnv *env, jobject jobject1) {
//1 获取java对象的变量的值,并重新为他设置新的值
//获取class对象
jclass jclass_student = env->GetObjectClass(jobject1);
//从class中获取变量
jfieldID jfieldId = env->GetFieldID(jclass_student, "name", "Ljava/lang/String;");
//从java对象obj中获取name变量的值
jstring name = static_cast<jstring>(env->GetObjectField(jobject1, jfieldId));
const char *char_name = env->GetStringUTFChars(name, JNI_FALSE);
//打印出原来的name变量的值
__android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__,
char_name);
const char native_name[] = "小明";
jstring jstring_name = env->NewStringUTF(native_name);
//通过jni重新设置变量name的值
env->SetObjectField(jobject1, jfieldId, jstring_name);
//2 调用java对象中的方法
//获取class对象中的print方法
jmethodID print = env->GetMethodID(jclass_student, "print", "()V");
//调用java对象中的print方法
env->CallVoidMethod(jobject1, print);
}
static const JNINativeMethod nativeMethod[] = {
{
"native_init", "()V", (void *) native_init},
};
static int registNativeMethod(JNIEnv *env) {
int result = -1;
jclass class_text = env->FindClass("com.taobao.alinnkit.ndk1.Student");
if (env->RegisterNatives(class_text, nativeMethod,
sizeof(nativeMethod) / sizeof(nativeMethod[0])) == JNI_OK) {
result = 0;
}
return result;
}
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
int result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
if (registNativeMethod(env) == JNI_OK) {
result = JNI_VERSION_1_6;
}
return result;
}
}
动态注册上篇文章讲了,这里就不多说了,主要的实现逻辑就在native_init
的实现里,大概逻辑就是,获取Student
对象中的变量name
的值,并打印出来,然后重新设置name
变量的值,最后调用Student
对象的print
方法,打印出name
变量的值
最后就是调用,直接new就可以触发
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Student student = new Student();
}
看下打印结果
/mmm: method = native_init, msg = 小红
/mmm: 小明
JNI访问java的静态成员
先看下用带的Api
FindClass
获取class对象
jclass FindClass(JNIEnv *env, const char *name);
- const char *name:全限定的class名
GetStaticFieldID
获取class对象的静态字段
jfieldID GetStaticFieldID (JNIEnv *env,
jclass clazz,
const char *name,
const char *sig);
- name:静态字段的名字
- sig:静态字段的类型签名
GetStaticField
获取静态字段的值,根据静态字段的类型不同,jni有不同的方法获取静态字段的值
方法名 | 返回值 |
---|---|
GetStaticBooleanField | jboolean |
GetStaticByteField | jbyte |
GetStaticCharField | jchar |
GetStaticShortField | jshort |
GetStaticIntField | jint |
GetStaticLongField | jlong |
GetStaticFloatField | jfloat |
GetStaticDoubleField | jdouble |
GetStaticObjectField | jobject |
设置静态变量的值
void SetStatic<type>Field(JNIEnv *env,
jclass clazz,
jfieldID fieldID,
NativeType value);
- 最后一个参数就是你需要设置的值
方法名 | NativeType |
---|---|
SetStaticBooleanField | jboolean |
SetStaticByteField | jbyte |
SetStaticCharField | jchar |
SetStaticShortField | jshort |
SetStaticIntField | jint |
SetStaticLongField | jlong |
SetStaticFloatField | jfloat |
SetStaticDoubleField | jdouble |
SetStaticObjectField | jobject |
调用静态方法
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
这俩个跟之前调用普通方法用法一样就是换了个函数名
完整代码
java代码
public class Student {
public static String text = "没改过";
static {
System.loadLibrary("studentlib");
}
native void native_Text();
public static void printText() {
Log.d("mmm", text);
}
}
c代码
static void native_text(JNIEnv *env, jobject jobject1) {
//获取class
jclass jclass_student = env->FindClass("com.taobao.alinnkit.ndk1.Student");
//获取静态变量text
jfieldID jfieldId_text = env->GetStaticFieldID(jclass_student, "text", "Ljava/lang/String;");
//获取静态变量text的值
jstring text = (jstring) env->GetStaticObjectField(jclass_student, jfieldId_text);
const char *char_name = env->GetStringUTFChars(text, JNI_FALSE);
__android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__,
char_name);
jstring jstring1 = env->NewStringUTF("改过了");
//修改静态变量text的值
env->SetStaticObjectField(jclass_student, jfieldId_text, jstring1);
//获取静态方法printText
jmethodID jmethodId = env->GetStaticMethodID(jclass_student, "printText", "()V");
//调用静态方法printText
env->CallStaticVoidMethod(jclass_student, jmethodId);
}
static const JNINativeMethod nativeMethod[] = {
{
"native_Text", "()V", (void *) native_text}
};
static int registNativeMethod(JNIEnv *env) {
int result = -1;
jclass class_text = env->FindClass("com.taobao.alinnkit.ndk1.Student");
if (env->RegisterNatives(class_text, nativeMethod,
sizeof(nativeMethod) / sizeof(nativeMethod[0])) == JNI_OK) {
result = 0;
}
return result;
}
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
int result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
if (registNativeMethod(env) == JNI_OK) {
result = JNI_VERSION_1_6;
}
return result;
}
}
主要的实现就是native_text
方法,主要做了获取静态变量text
的值,修改静态变量text
的值,调用静态方法printText
调用
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Student student = new Student();
student.native_Text();
}
打印数据
/mmm: method = native_text, msg = 没改过
/mmm: 改过了
参考
https://juejin.im/post/5d23fb066fb9a07eb15d7b29
https://blog.csdn.net/afei__/article/details/81016413
https://www.jianshu.com/p/67081d9b0a9c