3. JNI 基本数据类型与字符串传递

JNI 基本数据类型与字符串传递 学习卡片

JNI 基本类型映射与字符串内存管理

JNI 的基本类型映射是 Java 与 C/C++ 互操作的基石。Java 的 int、boolean、float、double 在 native 层分别对应 jint、jboolean、jfloat、jdouble,它们都是值传递,直接按 C 类型使用即可。真正需要谨慎处理的是 jstring——它是不透明引用,必须通过 GetStringUTFChars 获取可读的 UTF-8 字符指针,使用完毕后必须调用 ReleaseStringUTFChars 释放,否则会造成内存泄漏。

Java 层 int age = 25 boolean flag = true float ratio = 3.14f double pi = 3.14159 String name = "NDK" nativeMethod(...) JNI 类型映射 jint (32位有符号整数) jboolean (0/1) jfloat (32位浮点) jdouble (64位浮点) jstring (不透明引用) JNIEnv* env, jobject thiz C/C++ 层 int age = 25 bool flag = true float ratio = 3.14f double pi = 3.14159 const char* utf8 JNIEnv* env GetStringUTFChars → 使用 → ReleaseStringUTFChars 必须成对调用,否则内存泄漏 基本类型:值传递,直接使用 | jstring:引用传递,需转换与释放

关键实现:基本类型与字符串互转

下面给出一个完整的 native 方法,接收 Java 传入的 int、boolean、float、double 和 String,在 C++ 层处理后返回一个拼接后的字符串。重点观察 GetStringUTFChars 与 ReleaseStringUTFChars 的配对使用。

// native-lib.cpp
#include <jni.h>
#include <string>
#include <sstream>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_processData(
        JNIEnv* env,
        jobject /* thiz */,
        jint age,
        jboolean flag,
        jfloat ratio,
        jdouble pi,
        jstring name) {

    // 1. 基本类型直接使用,无需转换
    int cAge = age;                    // jint -> int
    bool cFlag = (flag == JNI_TRUE);   // jboolean -> bool
    float cRatio = ratio;              // jfloat -> float
    double cPi = pi;                   // jdouble -> double

    // 2. jstring 必须转换为 const char*
    const char* utf8Name = env->GetStringUTFChars(name, nullptr);
    if (utf8Name == nullptr) {
        return env->NewStringUTF("GetStringUTFChars failed");
    }

    // 3. 在 C++ 层处理数据
    std::ostringstream oss;
    oss << "Name=" << utf8Name
        << ", Age=" << cAge
        << ", Flag=" << (cFlag ? "true" : "false")
        << ", Ratio=" << cRatio
        << ", Pi=" << cPi;

    // 4. 释放 UTF 字符指针(必须!)
    env->ReleaseStringUTFChars(name, utf8Name);

    // 5. 将 C++ 字符串转回 jstring 返回
    return env->NewStringUTF(oss.str().c_str());
}

对应的 Java 声明与调用:

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }

    public native String processData(
            int age, boolean flag, float ratio, double pi, String name);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String result = processData(25, true, 3.14f, 3.14159, "NDK");
        Log.d("JNI", result);
        // 预期输出: Name=NDK, Age=25, Flag=true, Ratio=3.14, Pi=3.14159
    }
}
内存管理要点:
1. GetStringUTFChars 返回的指针指向 JVM 内部或临时拷贝的 UTF-8 缓冲区,必须用 ReleaseStringUTFChars 释放。
2. 如果 GetStringUTFChars 返回 nullptr,说明内存分配失败,此时不能调用 ReleaseStringUTFChars。
3. 在 ReleaseStringUTFChars 之后,utf8Name 指针立即失效,不能再访问。
4. 若在 native 方法中提前 return,务必确保释放操作在 return 之前执行,或使用 RAII 封装。

验证与结果判读

Java 传入值JNI 类型C++ 接收类型预期输出片段
int 25jintintAge=25
boolean truejbooleanboolFlag=true
float 3.14fjfloatfloatRatio=3.14
double 3.14159jdoubledoublePi=3.14159
String "NDK"jstringconst char*Name=NDK

运行后查看 Logcat 输出,若结果与预期一致,说明基本类型映射与字符串转换链路正确。若出现乱码,检查 GetStringUTFChars 的第二个参数是否传入了 nullptr(表示不关心是否拷贝),以及 Java 源文件编码是否为 UTF-8。

常见错误排查:
• 崩溃在 GetStringUTFChars:检查传入的 jstring 是否为 nullptr,Java 侧是否传入了 null。
• 内存持续增长:检查是否遗漏了 ReleaseStringUTFChars,或异常路径下未释放。
• 返回字符串乱码:确认 NewStringUTF 接收的是有效的 UTF-8 编码字符串,且生命周期覆盖调用期间。