常德网站优化推广百度推广投诉电话
Android之JNI详解
- 简介
- 创建项目
- 注册
- 动态注册
- 静态注册
- 关键词解读
- 基础数据类型
- 引用java对象
- JNI引用与释放
- cmake配置文件
简介
JNI(Java Native Interface) 是 Java 提供的一种编程框架,用于在 Java 应用程序中调用和与用其他编程语言(如 C、C++ 等)编写的本地代码(Native Code)进行交互。它允许 Java 程序访问操作系统或硬件提供的底层功能。
创建项目
- 在AS中选择Native C++创建项目
- 项目结构
- native.cpp:实现本地代码(Native Code)的C++源文件。
- CmakeList.txt:是 CMake 工具使用的配置文件,用于指定如何构建一个 C/C++ 项目。CMake 是一个跨平台的构建系统生成器,它可以根据 CMakeLists.txt 文件生成适合特定平台和编译器的构建文件。
注册
动态注册
- 简介:与静态注册不同,动态注册通过调用 JNI 提供的RegisterNatives函数,在程序运行时显式地注册本地方法。
- 实现步骤:
重要点:构建JNINativeMethod结构体:
typedef struct {const char* name; // java类对应的方法名const char* signature; //函数签名void* fnPtr; //cpp方法的指针函数
} JNINativeMethod;
- 步骤一:
package com.hzh.jnidemo;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {static {System.loadLibrary("jnidemo");}public native String stringFromJNI();public native String jniTest();
}
- 步骤二:
//cpp交互方法,这里是属于native的命名
jstring native_stringFromJNI(JNIEnv *env,jobject thiz){return env->NewStringUTF("动态注册成功!");
}
jstring native_jniTest(JNIEnv *env, jobject thisz) {std::string str = "动态注册成功!";return env->NewStringUTF(str.c_str());
}// 需要动态注册native方法的类名
static const char *mClassName = "com/hzh/jnidemo/MainActivity";// 动态注册函数结构数组
static const JNINativeMethod methods[] = {{"jniTest", //对应java交互类的方法名"()Ljava/lang/String;", //对应方法名的函数签名(jstring *) native_jniTest //对应cpp交互的指针函数},{"stringFromJNI","()Ljava/lang/String;",(void *) native_stringFromJNI}
};JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv* env = NULL;// 1. 获取 JNIEnv,这个地方要注意第一个参数是个二级指针int result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);// 2. 是否获取成功if(result != JNI_OK){return JNI_VERSION_1_6;}// 3. 注册方法,通过FindClass寻找jclass classMainActivity = env->FindClass(mClassName);//RegisterNatives 函数接受三个参数://jclass clazz:Java 类的引用。//const JNINativeMethod* methods:指向 JNINativeMethod 结构体数组的指针,描述 Java 方法与本地方法的映射关系。//jint nMethods:映射关系的数量。result = env->RegisterNatives(classMainActivity,methods, 2);if(result != JNI_OK){return JNI_VERSION_1_2;}return JNI_VERSION_1_6;
}
JNI_OnLoad是在初始化的时候调用,参考activity的生命周期,通过FindClass找到对应的class,最后通过RegisterNatives来实现动态注册。需要留意的是JNINativeMethod结构体以及RegisterNatives的参数。
静态注册
- 简介:通过 JNI 规范中约定的命名规则,将 Java 方法与本地(Native)方法的实现直接映射。这种映射关系在编译时由编译器根据方法名和签名自动生成。
- Native命名规则:遵循Java_包名_类名_方法名的格式,包咯面的"."需要转换成“_”,例如Java 类 com.hzh.jnidemo中的方法 MainActivity,对应的 C/C++ 函数名应为com_hzh_jnidemo_MainActivity。
- 原理:Java 编译器会根据 native 方法的声明生成方法签名,而 C/C++ 编译器会根据命名规则生成符号表。JVM 在加载类时,通过方法签名匹配符号表中的函数名,完成绑定。
- 实现步骤:
- Java端的实现如下:
通过System.loadLibrary(“*****”)进行加载交互。
package com.hzh.jnidemo;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {static {System.loadLibrary("jnidemo");}...public native String stringFromJNI();
}
- Native端的实现:
#include <jni.h>
#include <string>extern "C" JNIEXPORT jstring JNICALL
//*遵循命名规则实现方法*
Java_com_hzh_jnidemo_MainActivity_stringFromJNI(JNIEnv* env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}
关键词解读
- extern “C”:在 c++ 中可以使用使用 c 代码
- JNIEXPORT:是一个宏,用于导出本地方法。
- JNICALL:是一个宏,用于指定调用约定(Calling Convention),通常是个空的宏定义。
- JNIEnv:是一个指向JNI函数的指针结构体。
作用:调用JNI函数,如创建Java对象、访问Java字段、调用Java方法等。
示例:
JNIEnv *env;
jclass clazz = (*env)->FindClass(env, "class");
- JavaVM:一个指向Java虚拟机的指针,表示整个JVM实例。
- jclass:表示Java类的引用。
- jobject:表示Java对象的引用。
- jmethodID:表示Java方法的标识符。
- jfieldID:表示Java字段的标识符。
基础数据类型
引用类型
类型签名
- 简介:是用于描述 Java 方法或字段的特定字符串。JNI 签名是方法描述符的一部分,它包含了方法的参数类型和返回值类型,确保本地代码能够正确调用 Java 方法或访问 Java 字段。每次GetMethodID 或 GetFieldID或者返回数据都会用到。
- 签名格式:
方法签名的格式为:(参数类型签名)返回值类型签名
示例:
int add(int a, int b) → (II)I
String getName() → ()Ljava/lang/String;
void print(String message) → (Ljava/lang/String;)V
引用java对象
Field/Method ID:JNIEvn 操作 java 对象时会使用 java 中的反射,引用某个属性都需要 field 和 method 的 id,而id都是指针类型。
实现步骤:
- 获取 JNIEnv 指针:
- 查找类:
- 使用 FindClass 函数获取 Java 类的引用。
- 获取方法 ID 或字段 ID:
- 使用 GetMethodID 或 GetFieldID 获取方法或字段的标识符。
- 操作 Java 对象:
- 使用 NewObject、CallObjectMethod、SetObjectField 等函数创建或操作 Java 对象。
示例:获取一个class的getName()方法
extern "C" JNIEXPORT jstring JNICALL
Java_com_hzh_jnidemo_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {// 1. 获取 thiz 的 class,也就是 java 中的 Class 信息jclass thisclazz = env->GetObjectClass(thiz);// 2. 根据 Class 获取 getClass 方法的 methodID//GetMethodID/GetFieldI的参数://jclass//filed/methon name //类型签名: JNI 使用 jvm 的类型签名jmethodID mid_getClass = env->GetMethodID(thisclazz, "getClass", "()Ljava/lang/Class;");// 3. 执行 getClass 方法,获得 Class 对象jobject clazz_instance = env->CallObjectMethod(thiz, mid_getClass);// 4. 获取 Class 实例jclass clazz = env->GetObjectClass(clazz_instance);// 5. 根据 class 的 methodIDjmethodID mid_getName = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");// 6. 调用 getName 方法jstring name = static_cast<jstring>(env->CallObjectMethod(clazz_instance, mid_getName));......}
JNI引用与释放
- 局部引用(Local Reference):参考java 中的局部变量
// 查找类jclass myClass = (*env)->FindClass(env, "com/hzh/jnidemo/MainActivity");// 获取方法 IDjmethodID myMethod = (*env)->GetMethodID(env, myClass, "stringFromJNI", "()Ljava/lang/String");// 调用 Java 方法(*env)->CallVoidMethod(env, obj, myMethod);
- 全局引用(Global Reference):参考java 中的全局变量
// 创建局部引用jobject localRef = (*env)->NewObject(env, /* 类和方法 ID 略 */);// 创建全局引用globalRef = (*env)->NewGlobalRef(env, localRef);//释放//局部的释放DeleteLocalRef(localRef)//全局的释放if (globalRef != NULL) {(*env)->DeleteGlobalRef(env, globalRef);globalRef = NULL;}
- 弱全局引用(Weak Global Reference):参考java 中的弱引用
// 创建引用jobject localRef =(*env)->NewObject(env, /* 类和方法 ID 略 */);;weakRef = (*env)->NewWeakGlobalRef(env, localRef);//释放DeleteLocalRef(localRef)
cmake配置文件
# 设置最低版本
cmake_minimum_required(VERSION 3.22.1)# 引用的项目名
project("jnidemo")# 创建的库名
add_library(# 库名字${CMAKE_PROJECT_NAME}# 设置是动态库(SHARED)还是静态库(STATIC)SHARED# 设置库文件native-lib.cpp)# 加载的库,比如第三方的库,脚本等
target_link_libraries(${CMAKE_PROJECT_NAME}androidlog)
导入android项目的配置:
externalNativeBuild {cmake {path file('src/main/cpp/CMakeLists.txt')version '3.22.1'}}