Android之JNI详解
简介
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结构体:
java
typedef struct {
const char* name; // java类对应的方法名
const char* signature; //函数签名
void* fnPtr; //cpp方法的指针函数
} JNINativeMethod;
- 步骤一:
java
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();
}
- 步骤二:
java
//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("*****")进行加载交互。
java
package com.hzh.jnidemo;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("jnidemo");
}
...
public native String stringFromJNI();
}
- Native端的实现:
java
#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方法等。
示例:
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或者返回数据都会用到。
- 签名格式:
方法签名的格式为:(参数类型签名)返回值类型签名
示例:
java
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()方法
java
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 的 methodID
jmethodID 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 中的局部变量
java
// 查找类
jclass myClass = (*env)->FindClass(env, "com/hzh/jnidemo/MainActivity");
// 获取方法 ID
jmethodID myMethod = (*env)->GetMethodID(env, myClass, "stringFromJNI", "()Ljava/lang/String");
// 调用 Java 方法
(*env)->CallVoidMethod(env, obj, myMethod);
- 全局引用(Global Reference):参考java 中的全局变量
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 中的弱引用
java
// 创建引用
jobject localRef =(*env)->NewObject(env, /* 类和方法 ID 略 */);;
weakRef = (*env)->NewWeakGlobalRef(env, localRef);
//释放
DeleteLocalRef(localRef)
cmake配置文件
java
# 设置最低版本
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}
android
log)
导入android项目的配置:
java
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}