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

Android JNI/NDK回调实现问题及代码求助

Android JNI 双向回调完整解决方案(修复你的现有代码+可运行示例)

先直接解决你现有代码里的几个核心问题,再实现你需要的两种Java/C交互模式:


一、先修复你现有代码的致命问题

1. 为什么InvokeFunction从未被调用?

你的JNI函数签名和Java侧的native声明不匹配,而且函数名不符合JNI规范:

  • 正确的JNI函数名应该是Java_包名_类名_方法名(比如你的类是org.foo.bar.MainActivity,方法是startTickThread,那函数名应该是Java_org_foo_bar_MainActivity_startTickThread)
  • extern "C"必须包裹整个函数声明,避免C++名字 mangling
  • Java侧必须声明对应的native方法,主动调用它

2. 为什么queryRuntimeInfo获取方法ID失败?

  • 类名大小写错误:你写的org/foo/bar/mainactivity,但Java类名是MainActivity,应该是org/foo/bar/MainActivity
  • 方法签名不匹配:比如getBuildVersion()的签名是()Ljava/lang/String;,要确保Java侧确实有这个静态方法
  • 建议用javap -s生成准确的方法签名,避免手动写错

3. 为什么UpdateTicks从未执行?

因为InvokeFunction没被调用,线程根本没创建。另外还要注意:

  • 线程创建后,AttachCurrentThread要正确处理,避免重复Attach
  • 方法ID应该缓存起来,不要每次循环都调用GetMethodID(影响性能)

二、完整可运行实现(两种交互模式)

第一步:Java侧代码(MainActivity.java)

package org.foo.bar;

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    // 加载JNI库
    static {
        System.loadLibrary("native-lib");
    }

    // 模式1:Java调用C函数,传入int参数(摇杆输入)
    public native void sendJoystickInput(int inputValue);

    // 模式2:C侧调用Java函数获取摇杆状态
    public int getJoystickState() {
        // 这里模拟摇杆输入,实际替换成你的摇杆逻辑
        Log.e("JniHandler", "getJoystickState WAS CALLED!!!");
        return 333; // 返回你的摇杆状态值
    }

    // 启动C侧的独立循环线程
    public native void startNativeLoop();

    // C侧回调Java的示例方法(比如更新UI)
    public void updateTimer() {
        Log.e("JniHandler", "updateTimer WAS CALLED from C!!!");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 测试模式1:Java调用C传int
        sendJoystickInput(100);

        // 启动模式2:C侧独立循环
        startNativeLoop();
    }
}

第二步:JNI侧代码(native-lib.cpp)

#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
#include <assert.h>
#include <sys/time.h>

// 日志宏,替换SDL_Log
#define LOG_TAG "JNI_DEBUG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

typedef struct tick_context {
    JavaVM *javaVM;
    jclass mainActivityClz; // 缓存MainActivity的类引用
    jobject mainActivityObj; // 缓存MainActivity的对象全局引用
    pthread_mutex_t lock;
    int done;
    // 缓存方法ID,避免每次循环重复获取
    jmethodID getJoystickStateId;
    jmethodID updateTimerId;
} TickContext;

TickContext g_ctx;

// JNI_OnLoad:JVM加载时初始化
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    memset(&g_ctx, 0, sizeof(g_ctx));
    LOGD("DEBUG -- JNI INIT!!!");
    g_ctx.javaVM = vm;

    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("JNI版本不支持");
        return JNI_ERR;
    }

    // 查找MainActivity类(注意类名是完整的,用/分隔)
    jclass clz = env->FindClass("org/foo/bar/MainActivity");
    if (!clz) {
        LOGE("找不到MainActivity类");
        return JNI_ERR;
    }
    g_ctx.mainActivityClz = (jclass)env->NewGlobalRef(clz);
    env->DeleteLocalRef(clz);

    // 初始化互斥锁
    pthread_mutex_init(&g_ctx.lock, NULL);
    g_ctx.done = 0;

    return JNI_VERSION_1_6;
}

// 模式1:Java调用C函数,传入int参数
extern "C" JNIEXPORT void JNICALL
Java_org_foo_bar_MainActivity_sendJoystickInput(JNIEnv *env, jobject instance, jint inputValue) {
    LOGD("Java调用C,传入的摇杆值:%d", inputValue);
    // 这里执行你的NDK逻辑,比如处理摇杆输入
    // ...
}

// C侧独立循环线程函数
void* NativeLoop(void* context) {
    LOGD("DEBUG -- NativeLoop IS CALLED");
    TickContext *pctx = (TickContext*) context;
    JavaVM *javaVM = pctx->javaVM;
    JNIEnv *env;
    jint res = javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);

    // 线程Attach到JVM
    if (res != JNI_OK) {
        res = javaVM->AttachCurrentThread(&env, NULL);
        if (JNI_OK != res) {
            LOGE("FAILED TO ATTACH THREAD");
            return NULL;
        }
    }

    // 缓存方法ID(只需要获取一次)
    if (!pctx->getJoystickStateId) {
        pctx->getJoystickStateId = env->GetMethodID(pctx->mainActivityClz, "getJoystickState", "()I");
        if (!pctx->getJoystickStateId) {
            LOGE("获取getJoystickState方法ID失败");
            goto exit;
        }
    }
    if (!pctx->updateTimerId) {
        pctx->updateTimerId = env->GetMethodID(pctx->mainActivityClz, "updateTimer", "()V");
        if (!pctx->updateTimerId) {
            LOGE("获取updateTimer方法ID失败");
            goto exit;
        }
    }

    // 独立循环逻辑
    struct timeval beginTime, curTime, usedTime, leftTime;
    const struct timeval kOneSecond = {1, 0};
    while(1) {
        gettimeofday(&beginTime, NULL);

        pthread_mutex_lock(&pctx->lock);
        int done = pctx->done;
        pthread_mutex_unlock(&pctx->lock);

        if (done) {
            break;
        }

        // 模式2:C调用Java函数获取摇杆状态
        jint joystickState = env->CallIntMethod(pctx->mainActivityObj, pctx->getJoystickStateId);
        LOGD("C调用Java获取摇杆状态:%d", joystickState);
        // 执行你的NDK逻辑,比如根据摇杆状态处理操作
        // ...

        // 回调Java的updateTimer方法
        env->CallVoidMethod(pctx->mainActivityObj, pctx->updateTimerId);

        // 控制循环频率(每秒一次)
        gettimeofday(&curTime, NULL);
        timersub(&curTime, &beginTime, &usedTime);
        timersub(&kOneSecond, &usedTime, &leftTime);
        struct timespec sleepTime;
        sleepTime.tv_sec = leftTime.tv_sec;
        sleepTime.tv_nsec = leftTime.tv_usec * 1000;
        if (sleepTime.tv_sec <= 1) {
            nanosleep(&sleepTime, NULL);
        } else {
            LOGE("NativeLoop processing too long!");
        }
    }

exit:
    // 线程Detach JVM
    javaVM->DetachCurrentThread();
    LOGD("NativeLoop线程退出");
    return context;
}

// 启动C侧独立循环的JNI方法
extern "C" JNIEXPORT void JNICALL
Java_org_foo_bar_MainActivity_startNativeLoop(JNIEnv *env, jobject instance) {
    LOGD("JNI CALL START NativeLoop");
    pthread_t threadInfo_;
    pthread_attr_t threadAttr_;
    pthread_attr_init(&threadAttr_);
    pthread_attr_setdetachstate(&threadAttr_, PTHREAD_CREATE_DETACHED);

    // 创建MainActivity对象的全局引用(因为线程中要使用)
    if (!g_ctx.mainActivityObj) {
        g_ctx.mainActivityObj = env->NewGlobalRef(instance);
        if (!g_ctx.mainActivityObj) {
            LOGE("创建MainActivity全局引用失败");
            return;
        }
    }

    // 创建线程
    int result = pthread_create(&threadInfo_, &threadAttr_, NativeLoop, &g_ctx);
    assert(result == 0);
    pthread_attr_destroy(&threadAttr_);
}

// JNI_OnUnload:释放资源
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return;
    }

    // 释放全局引用
    if (g_ctx.mainActivityClz) {
        env->DeleteGlobalRef(g_ctx.mainActivityClz);
        g_ctx.mainActivityClz = NULL;
    }
    if (g_ctx.mainActivityObj) {
        env->DeleteGlobalRef(g_ctx.mainActivityObj);
        g_ctx.mainActivityObj = NULL;
    }

    // 销毁互斥锁
    pthread_mutex_destroy(&g_ctx.lock);
    LOGD("JNI资源释放完成");
}

三、关键注意事项

  1. 方法签名准确性:

    • 用javap -s -p org.foo.bar.MainActivity生成准确的方法签名,避免手动写错
    • 比如getJoystickState()的签名是()I,updateTimer()是()V,sendJoystickInput(int)是(I)V
  2. 全局引用管理:

    • 线程中要使用的Java对象/类必须创建全局引用,不能用局部引用(局部引用在JNI函数返回后会被回收)
    • 在JNI_OnUnload中必须释放全局引用,避免内存泄漏
  3. 线程Attach/Detach:

    • 非JNI创建的线程(比如你用pthread创建的)必须先AttachCurrentThread才能调用JNI函数
    • 线程退出前必须DetachCurrentThread,避免资源泄漏
  4. 方法ID缓存:

    • GetMethodID/GetStaticMethodID是耗时操作,建议在初始化或第一次使用时缓存,不要每次循环都调用

四、验证运行

  1. 确保你的Android项目正确配置NDK(在build.gradle中指定NDK版本)
  2. 将JNI代码放在app/src/main/cpp/目录下
  3. 运行App,查看Logcat的JNI_DEBUG标签,应该能看到:
    • Java调用C的日志:Java调用C,传入的摇杆值:100
    • C调用Java的日志:getJoystickState WAS CALLED!!!、C调用Java获取摇杆状态:333
    • C回调Java的日志:updateTimer WAS CALLED from C!!!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 10:02:49