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 性能优化建议

  1. 缓存 ID:频繁使用的 jmethodID、jfieldID 应缓存
  2. 局部引用管理:及时使用 DeleteLocalRef() 释放
  3. 使用全局引用:跨线程使用时使用 NewGlobalRef()
  4. 避免频繁创建对象:尽量重用对象

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 资源,确保应用的稳定运行。