JNI 开发:静态注册与动态注册详解
JNI开发中,Java与Native代码通过函数注册交互,主要有静态与动态两种方式。静态注册由IDE自动生成,遵循特定命名规则,实现简单但首次调用效率较低。动态注册需手动建立方法映射表,灵活性高,运行效率更优,适合复杂项目。两种方式在函数签名、参数传递和资源管理上逻辑一致,开发者可根据项目规模与性能要求灵活选择。
博主博客
1. 概述
在 Android JNI 开发中,Java 层与 Native 层的通信是通过函数注册实现的。主要有两种注册方式:静态注册和动态注册。Android Studio 默认使用静态注册,但动态注册在实际开发中更为灵活。
2. 静态注册
2.1 项目结构
创建 Native C++ 项目时,Android Studio 会自动生成以下文件:
MainActivity.java
public class MainActivity extends AppCompatActivity {
// 加载本地库
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
// 本地方法声明
public native String stringFromJNI();
}
native-lib.cpp
#include <jni.h>
#include <string>
// 静态注册的 JNI 函数命名规则:
// Java_包名_类名_方法名
extern "C" JNIEXPORT jstring JNICALL
Java_com_uso6_MainActivity_stringFromJNI(
JNIEnv* env,
jobject thiz) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
2.2 静态注册特点
- 命名规则严格:函数名必须遵循
Java_包名_类名_方法名格式 - 自动生成:IDE 可以自动生成函数声明
- 效率较低:首次调用时需要根据函数名查找对应的本地函数
3. 动态注册
3.1 核心结构体:JNINativeMethod
typedef struct {
const char* name; // Java 方法名
const char* signature; // 方法签名
void* fnPtr; // JNI 函数指针
} JNINativeMethod;
3.2 数据类型映射表
基本数据类型
| Java 类型 | Native 类型 | 域描述符 | 说明 |
|---|---|---|---|
| boolean | jboolean | Z | 8位 |
| byte | jbyte | B | 8位 |
| char | jchar | C | 16位 |
| short | jshort | S | 16位 |
| int | jint | I | 32位 |
| long | jlong | J | 64位 |
| float | jfloat | F | 32位 |
| double | jdouble | D | 64位 |
| void | void | V | 无返回值 |
数组类型
| Java 类型 | Native 类型 | 域描述符 |
|---|---|---|
| boolean[] | jbooleanArray | [Z |
| byte[] | jbyteArray | [B |
| char[] | jcharArray | [C |
| short[] | jshortArray | [S |
| int[] | jintArray | [I |
| long[] | jlongArray | [J |
| float[] | jfloatArray | [F |
| double[] | jdoubleArray | [D |
引用类型
| Java 类型 | Native 类型 | 域描述符 | 示例 |
|---|---|---|---|
| Object | jobject | L完整类名; | Ljava/lang/String; |
| Class | jclass | L完整类名; | Ljava/lang/Class; |
| String | jstring | Ljava/lang/String; | 特殊类型 |
| Throwable | jthrowable | Ljava/lang/Throwable; | |
| Object[] | jobjectArray | [L完整类名; | [Ljava/lang/Object; |
3.3 JNI 函数默认参数
普通 native 方法
// Java: public native String getString();
JNIEXPORT jstring JNICALL native_method(
JNIEnv* env, // JNI 环境指针
jobject thiz // 调用该方法的对象实例
);
静态 native 方法
// Java: public static native int getCount();
JNIEXPORT jint JNICALL static_native_method(
JNIEnv* env, // JNI 环境指针
jclass clazz // 调用该方法的类对象
);
3.4 动态注册实现
native-lib.cpp(动态注册版)
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "JNI_DEMO"
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
// 定义 Java 层对应的类名
#define JNIREG_CLASS "com/uso6/MainActivity"
// ============== Native 方法实现 ==============
// 方法1:普通 native 方法
static jstring stringFromJNI(JNIEnv* env, jobject thiz) {
std::string hello = "Hello from dynamic registration!";
return env->NewStringUTF(hello.c_str());
}
// 方法2:带参数的 native 方法
static jint addNumbers(JNIEnv* env, jobject thiz, jint a, jint b) {
return a + b;
}
// 方法3:静态 native 方法
static void staticMethod(JNIEnv* env, jclass clazz) {
ALOGE("This is a static native method");
}
// ============== 方法注册表 ==============
static JNINativeMethod nativeMethods[] = {
// {Java方法名, 方法签名, Native函数指针}
{"stringFromJNI", "()Ljava/lang/String;", (void*)stringFromJNI},
{"addNumbers", "(II)I", (void*)addNumbers},
{"staticMethod", "()V", (void*)staticMethod}
};
// ============== 注册函数 ==============
static int register_native_methods(JNIEnv* env) {
// 1. 查找 Java 类
jclass clazz = env->FindClass(JNIREG_CLASS);
if (clazz == nullptr) {
ALOGE("Failed to find class: %s", JNIREG_CLASS);
return JNI_FALSE;
}
// 2. 注册 Native 方法
int methodCount = sizeof(nativeMethods) / sizeof(nativeMethods[0]);
if (env->RegisterNatives(clazz, nativeMethods, methodCount) < 0) {
ALOGE("Failed to register native methods for: %s", JNIREG_CLASS);
env->DeleteLocalRef(clazz);
return JNI_FALSE;
}
// 3. 释放局部引用
env->DeleteLocalRef(clazz);
return JNI_TRUE;
}
// ============== JNI_OnLoad 入口 ==============
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = nullptr;
// 1. 获取 JNIEnv
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
ALOGE("Failed to get JNI environment");
return JNI_ERR;
}
// 2. 注册 Native 方法
if (register_native_methods(env) != JNI_TRUE) {
ALOGE("Failed to register native methods");
return JNI_ERR;
}
// 3. 返回 JNI 版本
return JNI_VERSION_1_6;
}
// ============== JNI_OnUnload 清理 ==============
extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
JNIEnv* env = nullptr;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
// 可以在这里进行资源清理
ALOGE("JNI_OnUnload called");
}
3.5 方法签名详解
方法签名的格式:(参数类型)返回类型
常见示例:
// Java: String getName()
"()Ljava/lang/String;"
// Java: int calculate(int a, int b)
"(II)I"
// Java: void setValue(String key, Object value)
"(Ljava/lang/String;Ljava/lang/Object;)V"
// Java: byte[] getData(String query)
"(Ljava/lang/String;)[B"
// Java: boolean check(int[] array, String name)
"([ILjava/lang/String;)Z"
4. 两种注册方式对比
| 特性 | 静态注册 | 动态注册 |
|---|---|---|
| 实现难度 | 简单,IDE 支持自动生成 | 较复杂,需手动编写注册代码 |
| 函数名 | 必须遵循特定命名规则 | 可自定义函数名 |
| 运行效率 | 首次调用较慢(需查找) | 首次调用快(直接映射) |
| 维护性 | 函数名长,修改包名/类名需同步修改 | 方法表集中管理,维护方便 |
| 灵活性 | 低,编译时确定 | 高,可在运行时动态注册 |
| 代码分离 | Java 与 Native 耦合较紧 | 可实现较好的代码分离 |
| 适用场景 | 简单项目、快速原型 | 大型项目、Framework 开发 |
5. 常用 JNI 操作示例
5.1 对象操作
// 1. 创建对象
jclass dateClass = env->FindClass("java/util/Date");
jmethodID constructor = env->GetMethodID(dateClass, "<init>", "()V");
jobject dateObj = env->NewObject(dateClass, constructor);
// 2. 调用对象方法
jmethodID getTimeMethod = env->GetMethodID(dateClass, "getTime", "()J");
jlong time = env->CallLongMethod(dateObj, getTimeMethod);
// 3. 获取/设置字段
jclass personClass = env->FindClass("com/uso6/Person");
jfieldID nameField = env->GetFieldID(personClass, "name", "Ljava/lang/String;");
// 获取字段值
jstring name = (jstring)env->GetObjectField(thiz, nameField);
// 设置字段值
jstring newName = env->NewStringUTF("New Name");
env->SetObjectField(thiz, nameField, newName);
5.2 静态操作
// 1. 调用静态方法
jclass utilsClass = env->FindClass("com/uso6/Utils");
jmethodID staticMethod = env->GetStaticMethodID(utilsClass, "getVersion", "()I");
jint version = env->CallStaticIntMethod(utilsClass, staticMethod);
// 2. 获取/设置静态字段
jfieldID staticField = env->GetStaticFieldID(utilsClass, "MAX_COUNT", "I");
jint maxCount = env->GetStaticIntField(utilsClass, staticField);
env->SetStaticIntField(utilsClass, staticField, 100);
5.3 数组操作
// 1. 创建数组
jintArray intArray = env->NewIntArray(10);
jobjectArray objArray = env->NewObjectArray(5, env->FindClass("java/lang/String"), nullptr);
// 2. 操作基本类型数组
jint* elements = env->GetIntArrayElements(intArray, nullptr);
for (int i = 0; i < 10; i++) {
elements[i] = i * 2;
}
env->ReleaseIntArrayElements(intArray, elements, 0);
// 3. 操作对象数组
for (int i = 0; i < 5; i++) {
jstring str = env->NewStringUTF("Item");
env->SetObjectArrayElement(objArray, i, str);
env->DeleteLocalRef(str);
}
5.4 字符串操作
// 1. Java String -> C++ string
jstring javaStr = ...;
const char* cStr = env->GetStringUTFChars(javaStr, nullptr);
std::string cppStr = cStr;
env->ReleaseStringUTFChars(javaStr, cStr);
// 2. C++ string -> Java String
std::string cppStr = "Hello JNI";
jstring javaStr = env->NewStringUTF(cppStr.c_str());
// 3. 获取字符串长度
jsize len = env->GetStringLength(javaStr);
jsize utfLen = env->GetStringUTFLength(javaStr);
5.5 异常处理
// 1. 检查异常
if (env->ExceptionCheck()) {
env->ExceptionDescribe(); // 打印异常信息
env->ExceptionClear(); // 清除异常
}
// 2. 抛出异常
jclass exceptionClass = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(exceptionClass, "Invalid argument");
// 3. 确保释放资源(RAII 风格)
class JStringGuard {
private:
JNIEnv* env;
jstring str;
const char* cstr;
public:
JStringGuard(JNIEnv* e, jstring s) : env(e), str(s) {
cstr = env->GetStringUTFChars(str, nullptr);
}
~JStringGuard() {
if (cstr) {
env->ReleaseStringUTFChars(str, cstr);
}
}
const char* get() const { return cstr; }
};
// 使用示例
{
JStringGuard guard(env, javaString);
const char* cstr = guard.get();
// 安全使用 cstr
}
6. 最佳实践
6.1 性能优化建议
- 缓存 ID:频繁使用的 jmethodID、jfieldID 应缓存
- 局部引用管理:及时使用
DeleteLocalRef()释放 - 使用全局引用:跨线程使用时使用
NewGlobalRef() - 避免频繁创建对象:尽量重用对象
6.2 代码组织建议
// JNIHelper.h - 头文件
#ifndef JNI_HELPER_H
#define JNI_HELPER_H
#include <jni.h>
class JNIHelper {
public:
static bool registerMethods(JNIEnv* env);
static void init(JavaVM* vm);
static JNIEnv* getEnv();
private:
static JavaVM* sJavaVM;
// 缓存常用的类和方法ID
struct CachedIDs {
jclass stringClass;
jclass integerClass;
// ... 其他缓存
};
static CachedIDs sCachedIDs;
};
#endif // JNI_HELPER_H
6.3 线程安全
// 获取线程安全的 JNIEnv
JNIEnv* getJNIEnv() {
JNIEnv* env = nullptr;
if (sJavaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
// 当前线程未附加到 JVM,进行附加
JavaVMAttachArgs args = {JNI_VERSION_1_6, "NativeThread", nullptr};
if (sJavaVM->AttachCurrentThread(&env, &args) != JNI_OK) {
return nullptr;
}
}
return env;
}
7. 调试技巧
7.1 日志输出
#include <android/log.h>
#define LOG_TAG "JNI_Debug"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
7.2 检查 JNI 错误
void checkJNIError(JNIEnv* env, const char* location) {
if (env->ExceptionCheck()) {
LOGE("JNI error at %s", location);
env->ExceptionDescribe();
env->ExceptionClear();
}
}
总结
动态注册和静态注册各有优劣,在实际项目中应根据具体需求选择:
- 小型项目、快速开发:推荐使用静态注册,简单直接
- 大型项目、性能敏感:推荐使用动态注册,灵活高效
- Framework 开发:必须使用动态注册,便于维护
无论选择哪种方式,都应注意代码的健壮性和可维护性,合理管理 JNI 资源,确保应用的稳定运行。
评论