You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JNI函数向Android应用返回数据:ByteBuffer读取与写入问题

问题分析与解决方案

嘿,我看你遇到的核心问题是JNI解压后的数据明明已经写入目标ByteBuffer了,但Java层就是读不到——本质是ByteBuffer的position和limit状态没和实际写入的字节数同步,Java层不知道哪些是有效数据。结合你的代码和编辑内容,我给你梳理下解决思路和修正方案:

一、先搞定JNI到Java层的ByteBuffer状态同步

你的编辑2已经找对了方向,但还有几个细节要调整:

1. 别再乱转类型了!

lzfse_decode_buffer返回的size_t是实际写入的字节数,你的测试值是319488,这个数完全在jint(32位有符号整数,最大值2^31-1)的范围内,直接转jint就行。之前转jlong出现负数是因为size_t是无符号类型,强制转有符号jlong时高位被符号扩展了,完全没必要。

2. 用反射同步ByteBuffer的有效区间

在JNI里,我们需要调用Java ByteBuffer的limit()和position()方法,把limit设为实际解压的字节数,position设为0——这样Java层一拿到ByteBuffer,就知道从哪读到哪。

修正后的JNI代码:

JNIEXPORT jlong JNICALL Java_com_company_appName_MainActivity_decompress(JNIEnv* env, jclass cls, jobject src, jobject dst) {
    uint8_t* src_buffer = (*env)->GetDirectBufferAddress(env, src);
    const size_t src_size = (*env)->GetDirectBufferCapacity(env, src);
    uint8_t* dst_buffer = (*env)->GetDirectBufferAddress(env, dst);
    size_t dst_size = (*env)->GetDirectBufferCapacity(env, dst);

    // 执行解压,拿到实际写入的字节数
    size_t decompressed_size = lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
    ALOG("实际解压出的字节数: %zu", decompressed_size);

    // 通过反射获取ByteBuffer的类和方法ID
    jclass byteBufferCls = (*env)->GetObjectClass(env, dst);
    if (byteBufferCls == NULL) {
        return -1; // 类获取失败,返回错误码
    }

    // 获取limit(int)方法:参数是jint,返回Buffer对象
    jmethodID limitMethod = (*env)->GetMethodID(env, byteBufferCls, "limit", "(I)Ljava/nio/Buffer;");
    // 获取position(int)方法:参数是jint,返回Buffer对象
    jmethodID positionMethod = (*env)->GetMethodID(env, byteBufferCls, "position", "(I)Ljava/nio/Buffer;");

    if (limitMethod != NULL && positionMethod != NULL) {
        // 设置limit为实际解压大小,position归0,让Java层能直接读取有效数据
        (*env)->CallObjectMethod(env, dst, limitMethod, (jint)decompressed_size);
        (*env)->CallObjectMethod(env, dst, positionMethod, 0);
    }

    // 返回解压大小给Java层,方便做校验
    return (jlong)decompressed_size;
}

二、Java层正确读取并写入文件

现在JNI已经帮我们把ByteBuffer的状态调好了,Java层的代码也可以优化下,尤其是IO效率和源数据的正确性:

修正后的Java调用代码:

try {
    File inFile = new File(Environment.getExternalStorageDirectory().getPath() + "/1");
    ByteBuffer buf = ByteBuffer.allocateDirect((int) inFile.length());
    // 预估目标缓冲区大小,可根据实际压缩比调整
    ByteBuffer buf_out = ByteBuffer.allocateDirect((int) inFile.length() * 20);

    // 批量读取源文件到ByteBuffer(比单个字节读取快N倍)
    InputStream is = new FileInputStream(inFile);
    byte[] tempBuf = new byte[4096];
    int bytesRead;
    while ((bytesRead = is.read(tempBuf)) != -1) {
        buf.put(tempBuf, 0, bytesRead);
    }
    buf.flip(); // 重点!写入后position在末尾,flip后把position设为0,limit设为写入长度,JNI才能读到完整的源数据
    is.close();

    Log.d("nims", "源缓冲区有效数据长度: " + buf.limit());

    // 调用JNI解压
    Long decompressedSize = decompress(buf, buf_out);
    Log.d("nims", "实际解压字节数: " + decompressedSize);
    Log.d("nims", "目标缓冲区有效数据长度: " + buf_out.limit());

    // 写入文件:JNI已经把position设为0,limit设为有效长度,直接写就行
    File outFile = new File(Environment.getExternalStorageDirectory().getPath() + "/2");
    FileChannel channel = new FileOutputStream(outFile, false).getChannel();
    int writtenBytes = channel.write(buf_out);
    Log.d("nims", "实际写入文件的字节数: " + writtenBytes);
    channel.close();

} catch (IOException e) {
    Log.e("nims", "I/O错误: " + e.getMessage());
}

额外优化建议:修正源数据长度的传递

原来的JNI代码用GetDirectBufferCapacity获取源数据长度是错的——这个方法返回的是缓冲区的总容量,不是实际写入的源数据长度!比如你分配了100字节缓冲区,但只写入了50字节,GetDirectBufferCapacity还是返回100,会导致解压时读错数据。

建议修改Native方法,直接传入源数据的实际长度:

// 修改Native方法定义
public static native Long decompress(ByteBuffer src, int srcLength, ByteBuffer dst);

然后JNI代码里直接用传入的长度:

JNIEXPORT jlong JNICALL Java_com_company_appName_MainActivity_decompress(JNIEnv* env, jclass cls, jobject src, jint srcLength, jobject dst) {
    uint8_t* src_buffer = (*env)->GetDirectBufferAddress(env, src);
    const size_t src_size = (size_t)srcLength; // 直接用传入的实际长度
    // ... 后面的代码不变
}

Java层调用时传buf.limit()就行:

buf.flip();
Long decompressedSize = decompress(buf, buf.limit(), buf_out);

总结一下

  1. JNI层:解压后必须同步ByteBuffer的limit和position,告诉Java层哪些是有效数据;
  2. Java层:读取完源文件后一定要flip()源ByteBuffer,确保JNI拿到正确的源数据长度;
  3. 类型转换:根据实际数值大小选择合适的Java整数类型,避免无符号转有符号的坑。

内容的提问来源于stack exchange,提问作者Nimesh Chandramaniya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:13:49